feat(backends): completion-check continuation loop + review fixes#798
Merged
zbigniewsobiecki merged 1 commit intodevfrom Mar 14, 2026
Merged
Conversation
…dence module
Introduces a continuation loop in both Claude Code and OpenCode backends so agents
that fail a post-completion check (e.g. no authoritative PR sidecar written) can
resume the session and retry rather than immediately failing the run.
## Core changes
### Shared completion module (`src/backends/completion.ts`)
- Extract `applyCompletionEvidence()` from OpenCode's local scope to the shared
`completion.ts` module so both backends share identical evidence-upgrade logic.
### Claude Code backend (`src/backends/claude-code/index.ts`)
- Add `consumeStream()` helper: processes the SDK stream, returns `turnCount` and
`toolCallCount` (replaces the `StreamConsumptionContext` mutable-ref pattern).
- Add `countToolCalls()` helper to count `tool_use` blocks without deep nesting.
- Add `decideContinuation()` helper: encapsulates the completion-failure check,
max-turns guard, and continuation-warning log; keeps `execute()` complexity low.
- Add `cleanupPersistedSession()`: removes `~/.claude/projects/<encoded-cwd>` after
each worker run. Encodes cwd with `replaceAll(path.sep, '-')` to match the SDK's
actual directory naming (fix: plain `path.join` silently produced a wrong path).
- Continuation loop in `execute()`: on completion failure, re-prompt the existing
session with `{ continue: true }` up to `maxContinuationTurns` times.
- Fix log message: "execution completed" → "turn completed" (accurate for both
initial and continuation turns).
- Fix spread pattern: `...(bool && obj)` → `...(bool ? obj : {})`.
- Log `toolCallCount` in the continuation warning (parity with OpenCode).
### OpenCode backend (`src/backends/opencode/index.ts`)
- Remove local `applyCompletionEvidence()` in favour of the shared import.
- Fix `maxContinuationTurns` fallback: `1` → `0` (match Claude Code; if
`completionRequirements` is absent there is nothing to check).
### Adapter (`src/backends/adapter.ts`)
- Bump `maxContinuationTurns` from 1 → 2.
## Tests
- `tests/unit/backends/completion.test.ts` (new): covers `applyCompletionEvidence`
for all cases (no sidecar, sidecar upgrades text evidence, adds missing prUrl,
default command fallback).
- `tests/unit/backends/claude-code.test.ts`: five new continuation-loop scenarios
(success after retry, exhausted turns, non-success stops immediately, cost
accumulation, no-op when completionRequirements absent). Adds `beforeEach` reset
and renames inner helper to `queueStream` to avoid shadowing the outer `mockStream`.
- `tests/unit/backends/adapter.test.ts`: update `maxContinuationTurns` expectation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
{ continue: true }up tomaxContinuationTurnstimes (now 2) before failing the run.applyCompletionEvidence: Extracted from OpenCode's local scope into the sharedcompletion.tsmodule — both backends now share identical sidecar-upgrade logic.cleanupPersistedSessionwas usingpath.joinwhich strips the leading/, producing a path that never exists. Fixed to encode cwd the same way the SDK does:replaceAll(path.sep, '-').Changes
src/backends/completion.tsapplyCompletionEvidence()— reads sidecar files and upgrades text-based PR evidence to authoritative (shared by both backends).src/backends/claude-code/index.tsconsumeStream()— processes the SDK stream; returns{ assistantMessages, resultMessage, turnCount, toolCallCount }(replaces theStreamConsumptionContextmutable-ref anti-pattern).countToolCalls()— countstool_useblocks per assistant message (avoids deep nesting inconsumeStream).decideContinuation()— encapsulates the completion-failure check, max-turns guard, and continuation-warning log (keepsexecute()under complexity limits).cleanupPersistedSession()— removes~/.claude/projects/<encoded-cwd>with correct path encoding."execution completed"→"turn completed"(accurate for both initial and continuation turns)....(bool && obj)→...(bool ? obj : {}).toolCallCountto continuation warning log (parity with OpenCode).src/backends/opencode/index.tsapplyCompletionEvidence(), use the shared import.maxContinuationTurnsfallback:1→0(matches Claude Code; safe default whencompletionRequirementsis absent).src/backends/adapter.tsmaxContinuationTurns1 → 2.Tests
tests/unit/backends/completion.test.ts— covers allapplyCompletionEvidencecases.tests/unit/backends/claude-code.test.ts— five continuation-loop scenarios +beforeEachmock reset +queueStreamrename to avoid shadowing.tests/unit/backends/adapter.test.ts—maxContinuationTurnsexpectation updated.Test plan
npm run typecheck— no errorsnpm run lint— no warningsnpm test— 480 tests passing🤖 Generated with Claude Code