Skip to content

fix: apply assistant-prefill guard to Anthropic models in interactive mode#469

Open
octo-patch wants to merge 3 commits into
usestrix:mainfrom
octo-patch:fix/issue-416-anthropic-prefill-in-interactive-mode
Open

fix: apply assistant-prefill guard to Anthropic models in interactive mode#469
octo-patch wants to merge 3 commits into
usestrix:mainfrom
octo-patch:fix/issue-416-anthropic-prefill-in-interactive-mode

Conversation

@octo-patch
Copy link
Copy Markdown
Contributor

Fixes #416

Problem

When running Strix in interactive (TUI) mode with an Anthropic model (e.g. Claude Sonnet 4.6), users eventually encounter:

LLM Request Failed
litellm.BadRequestError: AnthropicException — "This model does not support assistant message prefill. The conversation must end with a user message."

The root cause: _prepare_messages contains a guard that appends a synthetic <meta>Continue the task.</meta> user message whenever the conversation history ends with an assistant message. However, since commit 1404864 ("feat: add interactive mode"), this guard was gated behind and not self.config.interactive, so it was silently skipped for the TUI.

The Anthropic API rejects trailing assistant messages regardless of whether the call originates from interactive or non-interactive mode. Any edge case that leaves the conversation ending with an assistant message (cancelled request, state recovery, sub-agent resume after timeout) would trigger the error in TUI mode.

Solution

Extend the condition to also apply the guard when self._is_anthropic() is true:

# Before
if messages[-1].get("role") == "assistant" and not self.config.interactive:

# After
if messages[-1].get("role") == "assistant" and (
    not self.config.interactive or self._is_anthropic()
):

This ensures:

  • Anthropic models (both modes): guard always applied — matches API requirement
  • Non-Anthropic in non-interactive mode: guard applied — existing behaviour unchanged
  • Non-Anthropic in interactive mode: guard not applied — preserves assistant-prefill for models that support it

Testing

Added tests/llm/test_prepare_messages.py with three unit tests covering each branch of the condition. All tests pass.

tests/llm/test_prepare_messages.py::test_non_interactive_anthropic_adds_user_message PASSED
tests/llm/test_prepare_messages.py::test_interactive_anthropic_adds_user_message PASSED
tests/llm/test_prepare_messages.py::test_interactive_non_anthropic_does_not_add_user_message PASSED

octo-patch and others added 2 commits April 24, 2026 09:38
…ix#294)

Two related fixes for reports not being saved when Strix exits:

1. Call tracer.cleanup() in main.py's finally block before posthog.end()
   so that run data is always written to disk regardless of whether the
   atexit handlers registered in cli.py/tui.py fire (they can be skipped
   on Windows when the asyncio event loop tears down).

2. Replace the strict all-or-nothing validation in finish_actions.py with
   a graceful fallback: missing fields get the placeholder
   "[Not provided by model]" so partial reports are saved rather than
   silently discarded when the LLM omits a section.

Co-Authored-By: Octopus <liyuan851277048@icloud.com>
… mode (fixes usestrix#416)

When interactive mode is enabled (TUI), the guard that prevents sending a
trailing assistant message to the Anthropic API was incorrectly skipped. The
Anthropic API rejects such requests with
"This model does not support assistant message prefill." regardless of
whether the caller is in interactive or non-interactive mode.

Updated the condition to also apply the guard when `_is_anthropic()` is
true, so Anthropic models are always protected while non-Anthropic models
that may support assistant prefill continue to be unaffected in interactive
mode.

Added three unit tests covering all three branches of the new condition.

Co-Authored-By: Octopus <liyuan851277048@icloud.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 28, 2026

Greptile Summary

This PR fixes the Anthropic assistant-prefill error in interactive mode by extending the trailing-assistant-message guard in _prepare_messages to apply whenever _is_anthropic() is true. The fix is correct and well-tested. It also bundles two unrelated changes: adding tracer.cleanup() before posthog.end() in main.py, and replacing hard validation errors in finish_scan with silent placeholder substitution.

  • The finish_actions.py change removes the model's ability to self-correct empty report fields — the scan now completes with \"[Not provided by model]\" placeholders and success: True, giving the model no signal to retry.

Confidence Score: 3/5

Safe to merge the core Anthropic guard fix; the finish_actions.py behavioral change around validation warrants review before merging

The primary fix in llm.py is correct and tested. However, the finish_actions.py change introduces a P1 behavioral regression: empty required report fields are silently substituted with placeholders and returned as success, removing the model's opportunity to self-correct and potentially producing incomplete scan reports without any in-session indication.

strix/tools/finish/finish_actions.py — validation-to-placeholder behavior change needs deliberate design decision

Important Files Changed

Filename Overview
strix/llm/llm.py Core fix: extends the trailing-assistant-message guard to always apply for Anthropic models regardless of interactive mode; logic is correct
strix/tools/finish/finish_actions.py Replaces hard validation errors for empty report fields with silent placeholder substitution, removing the model's opportunity to self-correct and potentially producing incomplete scan reports
strix/interface/main.py Adds tracer.cleanup() call before posthog.end() in the finally block — correct ordering
tests/llm/test_prepare_messages.py New tests covering all three branches of the updated condition; well-structured and verifiable
Prompt To Fix All With AI
This is a comment left during a code review.
Path: strix/tools/finish/finish_actions.py
Line: 102-106

Comment:
**Silent placeholder substitution removes model self-correction opportunity**

Previously, if the model provided empty strings for required report fields, the function returned a structured error (`{"success": False, ...}`), giving the model a chance to retry with actual content. After this change, empty fields are silently replaced with `"[Not provided by model]"` and the scan is marked `success: True`. The model never learns that its output was incomplete, so it cannot retry — the user receives a scan report with placeholder text and no in-session opportunity for the model to fill in the missing data.

If the intent is to prevent infinite retry loops, a middle path would be to return a soft warning in the success response, or to log/surface the placeholder substitution to the user at completion time.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix: apply assistant-prefill guard to An..." | Re-trigger Greptile

Comment on lines +102 to +106
_NOT_PROVIDED = "[Not provided by model]"
executive_summary = (executive_summary or "").strip() or _NOT_PROVIDED
methodology = (methodology or "").strip() or _NOT_PROVIDED
technical_analysis = (technical_analysis or "").strip() or _NOT_PROVIDED
recommendations = (recommendations or "").strip() or _NOT_PROVIDED
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Silent placeholder substitution removes model self-correction opportunity

Previously, if the model provided empty strings for required report fields, the function returned a structured error ({"success": False, ...}), giving the model a chance to retry with actual content. After this change, empty fields are silently replaced with "[Not provided by model]" and the scan is marked success: True. The model never learns that its output was incomplete, so it cannot retry — the user receives a scan report with placeholder text and no in-session opportunity for the model to fill in the missing data.

If the intent is to prevent infinite retry loops, a middle path would be to return a soft warning in the success response, or to log/surface the placeholder substitution to the user at completion time.

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/tools/finish/finish_actions.py
Line: 102-106

Comment:
**Silent placeholder substitution removes model self-correction opportunity**

Previously, if the model provided empty strings for required report fields, the function returned a structured error (`{"success": False, ...}`), giving the model a chance to retry with actual content. After this change, empty fields are silently replaced with `"[Not provided by model]"` and the scan is marked `success: True`. The model never learns that its output was incomplete, so it cannot retry — the user receives a scan report with placeholder text and no in-session opportunity for the model to fill in the missing data.

If the intent is to prevent infinite retry loops, a middle path would be to return a soft warning in the success response, or to log/surface the placeholder substitution to the user at completion time.

How can I resolve this? If you propose a fix, please make it concise.

Greptile review noted the placeholder substitution was silent — adding
a warning log when fields are missing makes the substitution visible
to operators while keeping the partial-report save behavior intact.

The placeholder approach (vs returning a validation error) is
intentional — losing the entire scan because the model omitted one
section was the original bug from usestrix#294, so the trade-off favors
saving the report.

Co-Authored-By: Octopus <liyuan851277048@icloud.com>
@octo-patch
Copy link
Copy Markdown
Contributor Author

Good catch — partially addressed in 7259ffe by adding a logging.warning when any field gets the placeholder substitution, so operators can see when this happens.

Keeping the placeholder behavior (rather than returning a validation error) is intentional for this PR — see the commit message on 8e25743 and the issue #294 it fixes. The original strict validation was silently discarding entire scans when the model omitted one section, which was worse than saving a partial report. The model retry path was effectively never exercised because the scan was already winding down by the time finish_scan is called.

If the partial-report behavior turns out to mask real model failures in practice, surfacing the placeholder count back to the user at completion time (per your middle-path suggestion) is a clean follow-up.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]

1 participant