Skip to content

fix: refuse stale-answer replay in OpenCode and Pi - #284

Merged
ualtinok merged 5 commits into
mainfrom
ci/fail-closed-trailing-assistant-20261002
Oct 3, 2026
Merged

ualtinok merged 5 commits into
mainfrom
ci/fail-closed-trailing-assistant-20261002

Conversation

@antauth-alfonso

@antauth-alfonso antauth-alfonso Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Summary

Refuse meaningful trailing assistant history instead of silently deleting it and replaying the previous question.

  • Share one Core guard between OpenCode and Pi. Remove only provably empty trailers; reject meaningful text, tools, thinking, and unknown content before signing or model dispatch.
  • Return a terminal local OpenCode 400. Private response provenance prevents account fallback even when upstream 400 responses are configured for fallback; genuine provider responses retain that policy.
  • Validate Pi history before and after conversion so lowering cannot hide meaningful content.
  • Bind the mock relay explicitly to IPv4 loopback and verify exclusive endpoint ownership.

Verification

  • Core: 325 tests; OpenCode: 1,726 tests; Pi: 155 tests.
  • Isolated process E2E: 52/52, including a real OpenCode child proving no extra model request and a non-retryable local error.
  • Independent mutations fail when the history guard, Pi pre-conversion guard, local-response provenance, or mock endpoint ownership is removed.
  • Workspace typechecks, packed Node CLI/TUI startup, real Pi tool round-trip, emitted-import validation, and workspace lock checks pass.

Draft for clean-runner CI. No live host placement, credential migration, main integration, or package release is included. This guard prevents replay; it does not repair the host's non-atomic message/parts persistence.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Refuses meaningful trailing assistant history in OpenCode and Pi instead of silently discarding it and replaying the previous question.

  • A shared Core guard removes only provably empty trailers and rejects text, tools, thinking, and unknown content before billing, caching, signing, or model dispatch.
  • OpenCode returns a terminal local 400 with private response provenance, so configured upstream 400 fallback policies cannot route the request to another account; genuine provider 400s keep their existing policy.
  • Pi validates history before and after message conversion so lowering cannot hide meaningful content.
  • The mock relay now binds exclusively to IPv4 loopback, with an endpoint-ownership test covering the failure mode.
  • Core, OpenCode, Pi, and isolated end-to-end tests pass, including verification that refused requests make no model request and do not enter retry state.

Written for commit 9e8419a. Summary will update on new commits.

Review in cubic

Only provably empty trailing assistant messages are removed before signing.
Any other trailing assistant content raises TrailingAssistantHistoryError,
which escapes rewriteRequestBody's catch and is answered locally on both the
OAuth and API-key routes as a terminal 400 invalid_request_error before any
model dispatch.

The previous tests 'strips single/multiple trailing assistant message' and
'strips trailing assistant after tool_result + assistant' asserted the
unsafe deletion; their scenarios are kept but now expect refusal, because
deleting a completed answer resends the earlier question. The hybrid
n-2 anchor test ended on a meaningful assistant only to exercise stripping;
its trailer is now a provably empty assistant so the post-strip shape is
unchanged.
@ualtinok
ualtinok merged commit 9e8419a into main Oct 3, 2026
4 checks passed
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.

1 participant