feat(agent): withhold ask_user_question from headless runs (no user to answer)#739
Conversation
…o answer) ask_user_question has no server execute — only a streaming chat UI fulfills it. In headless/async runs (customer-prompt-task, /api/chat/runs) there's no user to answer, so the agent dead-ends: verified on prod (chat 6d7ef610) the agent scraped real data, recommended "send honest report with real data only", then called ask_user_question instead of sending — and the run hung with nothing delivered (recoupable/chat#1833). Gate the tool behind an `interactive` flag (default true) on buildAgentTools, threaded via RunAgentWorkflowInput → runAgentStep. handleStartChatRun (headless) passes interactive:false; handleChatWorkflowStream (interactive chat) keeps the default. With no ask-the-user escape, a headless agent must act — send an honest result or stop — instead of asking a question no one will answer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (5)
📝 WalkthroughWalkthroughAn optional ChangesInteractive flag propagation
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
No issues found across 6 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Architecture diagram
sequenceDiagram
participant Browser as Interactive Chat (Browser)
participant API as Chat API Routes
participant InputBuilder as buildRunAgentInput()
participant Workflow as runAgentWorkflow()
participant Step as runAgentStep()
participant ToolBuilder as buildAgentTools()
participant AskTool as ask_user_question (tool)
participant LLM as Agent Model (LLM)
Note over Browser,LLM: Two entry points – interactive vs headless
Browser->>API: POST /api/chat (interactive)
API->>InputBuilder: interactive (default true)
InputBuilder->>Workflow: RunAgentWorkflowInput { interactive: true }
Workflow->>Step: runAgentStep({ interactive: true })
Step->>ToolBuilder: buildAgentTools({ interactive: true })
ToolBuilder->>ToolBuilder: interactive = true → include ask_user_question
ToolBuilder-->>Step: tools { ask_user_question, ... }
Step->>LLM: streamText() with ask_user_question available
LLM->>AskTool: toolUse(ask_user_question)
AskTool->>Browser: question delivered via streaming UI
Browser-->>AskTool: user answer (streamed)
AskTool-->>LLM: answer response
LLM-->>Step: continues with user input
Note over API,Step: Headless run (no user present)
participant Headless as Headless Client (API/cron)
Headless->>API: POST /api/chat/runs (headless)
API->>InputBuilder: interactive = false (explicit)
InputBuilder->>Workflow: RunAgentWorkflowInput { interactive: false }
Workflow->>Step: runAgentStep({ interactive: false })
Step->>ToolBuilder: buildAgentTools({ interactive: false })
ToolBuilder->>ToolBuilder: interactive = false → omit ask_user_question
ToolBuilder-->>Step: tools { ... } (no ask_user_question)
Step->>LLM: streamText() without ask_user_question
Note over LLM: Agent cannot ask question, must act (send result or stop)
LLM-->>Step: honest result or stop
Auto-approved: Adds interactive flag to withhold ask_user_question tool in headless runs; updates types and tests.
Re-trigger cubic
Preview verification — headless run has no
|
Preview verification (end-to-end) — headless run now delivers an honest report, no
|
| check | result |
|---|---|
ask_user_question calls |
0 (removed from headless toolset) |
| API calls | ✅ succeed against test-recoup-api.vercel.app (no 401s) |
| real data scraped | ✅ @Apachelasminas: 1,010,000 subs · 404.6M views · 275 videos · latest video "Ego" 29,345 views / 1,100 likes |
fabricated CPM/revenue $ figures |
0 |
| email sent | ✅ delivered to staff inbox — Resend id 235b7356-2a90-418f-a3cd-d5c33d5b9ec8, EXIT:0 |
Agent's own summary (chat 9ccfabbb):
"Rather than fabricate CPM numbers, brand-partner match scores, or demographic charts, I built the report around the real data that does exist: Live channel stats (1,010,000 subscribers, 404.6M lifetime views, 275 videos)… Real stats on the latest video ('Ego' — 29,345 views…)… A flagged data-quality issue (an unrelated Apache Software Foundation X account mistakenly linked to this artist's roster)… A one-click YouTube connect link."
The full behavior chain, verified live: no ask_user escape → agent scrapes real data + checks connectors + mints a YouTube connect link → recognizes CPM/brand-affinity aren't sourceable → builds and sends an honest report grounded in real numbers (zero fabrication), with the connect link so the customer can add YouTube Analytics for the next run.
vs. the original prod run (chat c38d62cd): fabricated CPM/revenue and emailed it. This run (#739 + Sonnet 5): real data, honest about the gaps, connect link, delivered.
Two ENV notes (separate from #739, surfaced by the run)
- The sandbox credential is test-scoped, so both the API base and the email-helper's
RECOUP_API_BASEmust targettest-recoup-api.vercel.app. The agent handled the send correctly by settingRECOUP_API_BASE. Worth making the preview sandbox auto-target the matching env base URL. - The agent flagged
x.com/theasf(Apache Software Foundation) as wrongly linked to this artist's roster — a real data-quality bug worth cleaning up.
Net: #739 verified end-to-end — removing ask_user in headless runs converts the OneRPM benchmark from "paused question, nothing delivered" into "honest, real-data report, delivered."
Part of recoupable/chat#1833. Base:
main.Why
ask_user_questionhas no server execute — only a streaming chat UI fulfills it. In headless/async runs (customer-prompt-task,/api/chat/runs) there is no user to answer, so it's a dead-end.Verified on prod (chat
6d7ef610, Sonnet 5, Apache w/ YouTube): the agent scraped real data (1.54M subs, 1.11B views), correctly reasoned CPM isn't sourceable, labeled "Send honest report with real data only" as Recommended — and then calledask_user_questioninstead of sending. The run hung with nothing delivered. The tool's mere availability blocked the (correct, self-recommended) email.Fix
buildAgentToolsgains aninteractiveflag (defaulttrue);ask_user_questionis only registered when interactive.RunAgentWorkflowInput→runAgentStep→buildAgentTools.handleStartChatRun(headless/api/chat/runs) passesinteractive: false;handleChatWorkflowStream(interactive chat) keeps the default → unchanged.With no ask-the-user escape, a headless agent must act — send an honest result or stop — rather than ask a question no one will answer. Pairs with the
runAgentStepheadless honest-send item.Tests (TDD)
buildAgentTools: omitsask_user_questionwheninteractive:false; keeps it by default / explicit true.lib/agent+app/lib/workflowssuites green (353); tsc adds no new errors (3 pre-existing baseline errors unchanged); eslint clean.🤖 Generated with Claude Code
Summary by cubic
Prevent headless runs from hanging by omitting
ask_user_questionwhen no user is present. Adds aninteractiveflag so only interactive chat exposes the tool; headless APIs passinteractive: false.ask_user_questionbehindinteractive(default true); omit in/api/chat/runsandcustomer-prompt-task.interactiveviaRunAgentWorkflowInput→runAgentStep→buildAgentTools;handleStartChatRunsets false; interactive chat unchanged.Written for commit a0d55f4. Summary will update on new commits.