Skip to content

fix(session): discard a failed attempt's parts before retrying and stop retrying past an executed tool - #205

Merged
filipeforattini merged 1 commit into
mainfrom
retry-attempt-cleanup
Sep 11, 2026
Merged

fix(session): discard a failed attempt's parts before retrying and stop retrying past an executed tool#205
filipeforattini merged 1 commit into
mainfrom
retry-attempt-cleanup

Conversation

@filipeforattini

@filipeforattini filipeforattini commented Sep 11, 2026

Copy link
Copy Markdown

Problem

Effect.retry(SessionRetry.policy) in packages/redcode/src/session/processor.ts wraps the whole stream consumption. The parts a failed attempt had already persisted (text, reasoning, step-start, tool parts) stayed in the assistant message, so the retried stream appended duplicates next to them. Worse, the retry replayed the unchanged streamInput, so a tool the model had already executed in the failed attempt (a gateway ending with finish_reason: network_error after a completed edit, see llm/ai-sdk.ts) was requested and executed a second time. The existing retry test only checked in-memory reasoning state, not what was persisted.

Fix

  • The processor mints part ids through nextPartID(), which records them in ctx.attemptParts for the attempt in flight (reasoning, tool, step-start, step-finish, patch, text).
  • When the retry policy decides to retry (its set hook, before the wait starts), discardAttempt settles the pending tool calls and removes the recorded parts through Session.removePart, which the core projector turns into a DB delete. The message never shows the failed attempt's output next to the retry's.
  • updateToolCall flags ctx.attemptExecuted once a tool part leaves pending. A failure after that skips the retry layer and goes straight to halt, so the turn ends with the error visible and no tool executes twice. The check happens before the schedule so no misleading retry status is published.
  • Terminal (non-retryable) failures keep their partial parts as before.

Test

Two regression tests in packages/redcode/test/session/processor-effect.test.ts, both failing on main and passing here:

  • discard the failed attempt's parts before retrying: a text chunk then a midstream server_error, followed by a clean reply. Before: ["", "two"] text parts and two step-starts. After: exactly ["two"], one step-start, two provider calls, no error.
  • do not retry after a tool call already ran: a completed tool call then finish_reason: network_error. Before: continue, a second provider request. After: stop, one provider call, tool executed once and still completed, message.error set.

mise x -- bun test --timeout 30000 test/session: 514 pass, 0 fail. mise x -- bun run typecheck: clean.

https://claude.ai/code/session_01KAcoL6wpgVEs2ebUrUGpHS


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

…op retrying past an executed tool

`Effect.retry(SessionRetry.policy)` wraps the whole stream consumption, but
the parts a failed attempt had already persisted (text, reasoning, step-start,
tool parts) were left in the assistant message, so the retried stream appended
duplicates next to them. Worse, the retry replayed the same request, so a tool
the model had already executed in the failed attempt (a gateway closing with
`finish_reason: network_error` after a completed edit) was requested and run a
second time.

The processor now remembers the part ids it mints during an attempt and, as
soon as the retry policy decides to retry, removes them through
`Session.removePart` (projected to a DB delete) and settles the pending tool
calls, before the wait starts. A failure after any tool call reached running
skips the retry layer and goes straight to `halt`, so the turn ends with the
error visible and no tool executes twice.

Two regression tests cover this: a midstream server_error after a text chunk
leaves exactly one text part and one step-start after the retry; a completed
tool call followed by `network_error` makes no second provider request, keeps
the tool at completed, and ends the message with an error.

Claude-Session: https://claude.ai/code/session_01KAcoL6wpgVEs2ebUrUGpHS
@filipeforattini
filipeforattini merged commit 0bc8b09 into main Sep 11, 2026
12 of 15 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