Skip to content

fix(session): end at the step cap with a toolless answer, and report empty task results - #1632

Draft
RaghwendraSingh wants to merge 1 commit into
mainfrom
fix/step-cap-final-answer
Draft

RaghwendraSingh wants to merge 1 commit into
mainfrom
fix/step-cap-final-answer

Conversation

@RaghwendraSingh

@RaghwendraSingh RaghwendraSingh commented Nov 1, 2026 •

Copy link
Copy Markdown

Issue for this PR

No GitHub issue yet.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

An agent with a steps cap is meant to end with a text answer. On the Altimate gateway it ended with no answer instead, and a capped subagent handed its parent an empty result.

1. The request at the cap failed (session/prompt.ts).
At step N the loop appended MAX_STEPS as a trailing assistant message and still sent every tool, with no toolChoice. On the gateway that request failed with UnknownError "Could not get a response from the agent" and 0 tokens, so the session ended without an answer. In our headless runs these were the only two gateway errors in about 1,000 requests, and the only two requests sent at the cap. The answer-echo path a few lines above already notes that a conversation must end on a user turn.

Fix: at the cap the instruction is a user turn, sent with no tools and toolChoice: "none", the same toolless shape the answer echo, compaction and title requests use; llm.ts then flattens historical tool calls, which the gateway requires for a toolless request. A json_schema request keeps its tools at the cap, because its answer is the StructuredOutput tool call.

2. The cap counted iterations, not requests (session/prompt.ts).
step also increments on loop iterations that run a compaction or a subtask and continue. A capped agent that compacted twice reached its cap several requests early. The cap now uses a separate modelSteps counter that advances only on the normal model-call path. step is unchanged for everything else that reads it (title, telemetry, plan-mode checks).

3. An errored or silent child returned an empty result (tool/task.ts, session/task-output.ts).
The parent received only the child's last text part, so a child that failed at its cap produced <task_result>\n\n</task_result> with status completed, which reads as "done, nothing found". Now, when the child produced no final text, the result says why ("Task stopped with an error before giving a final answer (UnknownError: …)" or "Task ended without a final text answer") and carries the child's last written text if there is one. The <task_result> tag itself is unchanged, since cli/cmd/run/tool.ts parses it.

Not in this PR:

  • The step count still restarts when a child is resumed with task_id. Making it persist per session would also cap interactive sessions for good after N steps in total, so that belongs with a per-run budget that counts child sessions.
  • run --max-turns: passing the remaining turns into the loop would make the last allowed turn the toolless answer, but it changes the documented --max-turns contract (N work turns, then an abort and one toolless finalization reply, exit 1) that budget-finalization.test.ts pins, so it is left for a separate change.

How did you verify your code works?

  • New prompt.test.ts cases (the step cap asks for a final text answer …), plain and with a usage-triggered compaction, on an OpenAI-compatible mock: the last request ends with a user message carrying MAXIMUM STEPS REACHED and declares no tools; no earlier request carries it; with a compaction the agent still gets its full step count. Both fail on main (role assistant; one request short with the compaction).
  • New test/tool/task-empty-result.test.ts: an errored child with earlier text, a child with no text, and a normal answer. The first two fail on main.
  • bun test test/session test/tool test/agent: 2,131 pass, 0 fail. test/cli: the same 2 --time-budget failures in json-output.test.ts as main. budget-finalization.test.ts: 46/46. An apply_patch case timed out once under load and passes alone (27/27).
  • Typecheck clean; script/check-tracker-leaks.ts and analyze.ts --markers --strict pass.

Cost side effects

None added. The toolless final request sends no tool schemas, so it does not reuse the cached tools prefix; it is one request per capped session, and it replaces a request that failed and a parent turn that continued without the child's findings.

Screenshots / recordings

N/A

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR
  • I checked for cost side effects (model calls, queries or cloud resources that grow with user data) and described them, or wrote "none"

🤖 Generated with Claude Code


Summary by cubic

Fixes agent sessions at the steps cap so they end with a toolless text answer instead of failing with no answer, and makes capped or errored child tasks report why they stopped instead of returning an empty result.

  • At the cap the final instruction is now a user turn with tools off; previously it was a trailing assistant message with all tools offered, which the Altimate gateway rejected.
  • The cap counts only model requests. Loop iterations that ran a compaction or a subtask no longer consume a step, so capped agents no longer stop early.
  • A child task that ends on an error or without a final text answer now tells the parent why, and carries the child's last written text when there is one.

Sections

  • A json_schema request keeps its tools at the cap because its answer is the StructuredOutput tool call.
  • The step count still restarts when a child is resumed with task_id; a per-run budget that counts child sessions is left for a separate change, as is adjusting --max-turns to make the last allowed turn the toolless answer.

Written for commit a674b86. Summary will update on new commits.

View guided diff Turn on auto-fix

…empty task results

- At an agent's `steps` cap the max-steps instruction was sent as a trailing
  assistant message with every tool still offered. The Altimate gateway
  rejected that request, so the session ended with no answer. It is now a
  user turn with no tools and toolChoice "none", the same toolless shape the
  answer echo, compaction and title requests use.
- The cap counts model requests only. Loop iterations that ran a compaction
  or a subtask and continued were counted too, so capped agents stopped early.
- A child task that ends on an error or without text no longer reaches the
  parent as an empty <task_result>: the result says why it stopped and carries
  the child's last written text when there is one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 1, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

Share

@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • No issue referenced. Please add Closes #<number> linking to the relevant issue.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant