Skip to content

fix: send max-steps instruction as user message, not assistant - #36970

Closed
rguliyev wants to merge 1 commit into
anomalyco:devfrom
rguliyev:fix/max-steps-assistant-prefill
Closed

fix: send max-steps instruction as user message, not assistant#36970
rguliyev wants to merge 1 commit into
anomalyco:devfrom
rguliyev:fix/max-steps-assistant-prefill

Conversation

@rguliyev

@rguliyev rguliyev commented Jul 15, 2026

Copy link
Copy Markdown

Fixes #32548

Problem

When an agent hits its configured steps cap mid-turn, the loop forces
a text-only response by appending the "wrap up now" instruction
(MAX_STEPS_PROMPT) to the outgoing message array as a role: "assistant"
message:

messages: [
  ...modelMsgs,
  ...(isLastStep ? [{ role: "assistant" as const, content: MAX_STEPS_PROMPT }] : []),
],

This leaves the request ending on an assistant turn. Anthropic (and other
providers) treat a trailing assistant message as a response prefill, and
Claude models with thinking enabled reject it outright:

400 This model does not support assistant message prefill. The conversation
must end with a user message.

Independently confirmed in production logs across every agent/model
combination in proportion to usage, all with zero cost/output tokens on
the failing turn, all traced back to this exact isLastStep branch —
matches #32548's 100%-reproducible repro steps exactly.

Fix

The max-steps instruction is directed at the model, not written by it —
it belongs on a user turn:

...(isLastStep ? [{ role: "user" as const, content: MAX_STEPS_PROMPT }] : []),

Tool results already present in modelMsgs are untouched; only the role
of the appended instruction changes.

Testing

  • Added a regression test in prompt.test.ts that configures
    agent: { build: { steps: 1 } }, drives the loop to the cap, and
    asserts the appended max-steps message has role: "user" (fails if
    reverted to role: "assistant").
  • bun run lint clean on both changed files (no new warnings).
  • bun turbo typecheck passes (ran via the pre-push hook, 30/30 packages).
  • bun test test/session/prompt.test.ts — new test passes; the file has
    2 pre-existing flaky/timeout failures unrelated to this change,
    confirmed to occur identically on a clean checkout before this patch.

When an agent hits its configured steps cap mid-turn, the loop appended
the "wrap up now" instruction as a role: assistant message, leaving the
outgoing request ending on an assistant turn. Providers that reject
assistant-message prefill bounce this immediately with "This model does
not support assistant message prefill." The instruction is directed at
the model, so it belongs on a user turn, not tacked onto the assistant
role.

Adds a regression test asserting the max-steps message is sent as
role: user.
@github-actions

Copy link
Copy Markdown
Contributor

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 added the needs:compliance This means the issue will auto-close after 2 hours. label Jul 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

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

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

Potential duplicate found:

This appears to be addressing the exact same issue — converting the max-steps instruction from role: "assistant" to role: "user" to avoid provider rejection on assistant message prefill. The title and scope match PR #36970 closely, suggesting this may be a prior attempt or related fix for the same problem.

@rguliyev

Copy link
Copy Markdown
Author

Closing in favor of #34276, which is more complete — it also fixes the
parallel occurrence in the V2 loop (packages/core/src/session/runner/llm.ts)
that this PR missed, and has direct verification via captured-request replay.

Leaving our independent confirmation over there as supporting evidence
for #34276, since it's been open a while without a maintainer pass.

@rguliyev rguliyev closed this Jul 15, 2026
Anooshiravan added a commit to Anooshiravan/opencode that referenced this pull request Jul 28, 2026
Add regression tests locking in the fix from the previous commit:

- packages/opencode/test/session/prompt.test.ts: assert the V1 loop
  sends the MAXIMUM STEPS REACHED notice with role "user".
- packages/core/test/session-runner.test.ts: update the existing V2
  assertion (which locked in the buggy "assistant" role) to expect
  "user".

Verified:
- bun test test/session/prompt.test.ts (57 pass)
- bun test test/session-runner.test.ts (80 pass)
- bun turbo typecheck (29/29)

V1 test borrowed from anomalyco#36970.

Co-authored-by: rguliyev <rguliyev@users.noreply.github.com>
Anooshiravan added a commit to Anooshiravan/opencode that referenced this pull request Jul 28, 2026
Add regression tests locking in the fix from the previous commit:

- packages/opencode/test/session/prompt.test.ts: assert the V1 loop
  sends the MAXIMUM STEPS REACHED notice with role "user".
- packages/core/test/session-runner.test.ts: update the existing V2
  assertion (which locked in the buggy "assistant" role) to expect
  "user".

Verified:
- bun test test/session/prompt.test.ts (57 pass)
- bun test test/session-runner.test.ts (80 pass)
- bun turbo typecheck (29/29)

V1 test borrowed from anomalyco#36970.

Co-authored-by: rguliyev <rguliyev@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs:compliance This means the issue will auto-close after 2 hours.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Step-cap assistant message causes 400 on Claude models with thinking enabled

1 participant