Skip to content

fix(opencode): bound 4R frozen-context transport across restart #1710

Description

@doblas

Pre-flight Checklist

  • I searched open and closed issues for context overflow/compaction, 4R, resume/relaunch, capture-result, partial results, untracked scope, and immutable diff prompts; this is not a duplicate
  • I understand that PRs will be rejected if the linked issue does not have status:approved

Bug Description

A high-risk OpenCode review can enter an infinite restart loop when the parent session exceeds its context limit before any foreground lens reaches the managed capture hook.

On restart, native authority correctly reports the same reviewing lineage. However, review status --next-transition serializes the complete immutable candidate diff and changed-path manifest once inside every missing lens input. Canonical 4R therefore puts four copies in one parent-visible response. The managed OpenCode plugin then independently preflights and appends the same full frozen context to each foreground task prompt.

If OpenCode fails with Session too large to compact - context exceeds model limit even after stripping media before any tool.execute.after capture, native authority has zero captured artifacts. The next session sees all four lenses missing and repeats the same oversized fan-out indefinitely.

This is separate from #1250: default workspace projection can inflate scope with unrelated untracked files, but this defect is repeated per-lens transport plus no parent-failure-safe checkpoint before relaunch.

Audited remote main: 1f3fda46311030abb351ff81971d905b78d40cac. Installed production commit 8881b60b01c7cbf80da072d95579fe66b3e3a63b is an ancestor and contains the relevant behavior.

Steps to Reproduce

  1. On Windows, use OpenCode with Gentle AI 1.49.1-0.20260722122850-8881b60b01c7.
  2. Prepare a high-risk candidate. The observed target had 25 paths, 1,708 changed lines, and correction budget 200.
  3. Start or resume negotiated native review.
  4. Run review status --contract gentle-ai.review-integration/v1 --next-transition for the active lineage.
  5. Observe four reviewer_result inputs, each containing the complete candidate_diff and changed_path_manifest.
  6. Launch all four bound foreground tasks. The managed hook appends GENTLE_AI_FROZEN_CANDIDATE_CONTEXT with the full diff/manifest to every task prompt.
  7. Let parent context exceed OpenCode's compaction limit before any lens completes and invokes tool.execute.after.
  8. Start a fresh orchestrator session and query the same lineage.
  9. Observe all four lenses still missing, all four relaunched, and the parent overflowing again.

Expected Behavior

  • Restart output carries immutable candidate context once, or bounded provider references resolvable outside the parent transcript.
  • Parent-visible lifecycle messages remain bounded for moderate high-risk diffs.
  • Each completed lens is independently durable and restart launches only missing lenses.
  • Failure before the first capture does not recreate the same guaranteed-overflow fan-out; collection can checkpoint sequentially or use bounded transport.
  • The same lineage, target, and budget make forward progress across restarts.

Actual Behavior

  • review status --next-transition duplicates full context per missing lens (internal/cli/review_next_transition.go:218-253); TestNegotiatedRestartStatusSuppliesFrozenContextForEveryMissingReviewer explicitly requires equivalent context for every input (internal/cli/review_next_transition_test.go:280-333).
  • Status expands one frozen context through all missing inputs (internal/cli/review_facade.go:478-527).
  • The OpenCode hook performs full preflight and appends that context to each foreground prompt (internal/assets/opencode/plugins/review-result-artifacts.ts:124-184, 253-290).
  • Capture becomes durable only after the after-hook: native code atomically publishes each lens artifact (internal/cli/review_artifact.go:328-419) and restart discovers captured orders (internal/cli/review_artifact.go:639-681; internal/cli/review_next_transition.go:218-232).
  • Partial resumability works after at least one capture; parent failure before the first callback leaves no checkpoint and repeats all four.

Sanitized authority evidence:

{
  "lineage_id": "review-<redacted>",
  "state": "reviewing",
  "risk_level": "high",
  "selected_lenses": [
    "review-risk",
    "review-resilience",
    "review-readability",
    "review-reliability"
  ],
  "lens_results": [],
  "target": {
    "path_count": 25,
    "changed_lines": 1708
  },
  "correction_budget": 200
}

lens_results: [] alone does not prove capture absence because compact lens results merge at finalize. The decisive symptom is restart artifact discovery returning all four lenses as missing.

Gentle AI Version

1.49.1-0.20260722122850-8881b60b01c7 (audited against remote main 1f3fda46311030abb351ff81971d905b78d40cac)

Operating System

Windows

AI Agent / Client

OpenCode

Affected Area

Other (native review payload and managed OpenCode capture hook)

Logs / Error Output

Session too large to compact - context exceeds model limit even after stripping media

lineage: review-<redacted>
state: reviewing
selected lenses: review-risk, review-resilience, review-readability, review-reliability
captured/missing after restart: 0 / 4

Additional Context

Root cause and contributing factors

Primary boundary: Gentle AI/OpenCode integration. OpenCode owns the hard compaction failure, but Gentle AI amplifies it by serializing four full contexts into one parent response, injecting another full copy into each prompt, and requiring bound review tasks to run in the foreground.

Scope contributor: workspace START intentionally discovers every non-ignored untracked path (internal/cli/review_facade.go:870-877; internal/reviewtransaction/snapshot.go:533-552). --projection staged excludes unstaged/untracked content (internal/cli/review_facade_test.go:23-75). Exact staged/manifest concerns remain in #1250.

This is not all-or-nothing native persistence: tool.execute.after invokes capture-result per completed lens, capture atomically publishes that slot, and status omits captured orders. Current-main focused tests passed:

go test ./internal/cli -run 'TestNegotiatedRestartStatusSuppliesFrozenContextForEveryMissingReviewer|TestReviewCaptureResultConcurrentSelectedLenses|TestReviewCaptureResultStrictBindingReplayAndFinalize' -count=1
ok github.com/gentleman-programming/gentle-ai/internal/cli

Proposed fix directions and tradeoffs

  • Preferred: shared frozen context once at transition level plus per-lens subjects/bindings. Removes O(lenses x diff) parent growth but needs a versioned schema/consumer change.
  • Alternative: provider-owned opaque context references resolved directly by the trusted plugin. Keeps parent messages small but needs lifecycle/availability guarantees.
  • Tactical mitigation: collect/capture one missing foreground lens at a time. Creates restart checkpoints but adds latency and does not remove each child prompt's required context.
  • Do not reconstruct from live Git or broaden reviewer filesystem/shell permissions.

Acceptance criteria

  • Canonical 4R restart carries at most one full diff/manifest in parent-visible JSON, or only bounded references.
  • Managed OpenCode review does not retain four full context copies in the parent transcript.
  • Completed lenses capture immediately and independently; restart launches only missing orders.
  • Failure before first completion cannot cause an unbounded identical four-lens loop.
  • Lineage, target, subject hash, lens, order, and revision remain strictly bound.
  • Restart creates no new lineage or budget.
  • Windows/OpenCode regression uses at least 1,700 lines / 25 paths, interrupts before and after individual captures, and proves bounded payload plus forward progress.
  • Unit coverage proves restart payload growth is O(diff x missing lenses).
  • Existing capture, artifact validation, and finalization tests remain green.

Shell: PowerShell 5.1 (Other where a shell dropdown exists).

Related but not duplicate: #1250 (candidate scope), #1454 (artifact availability), #1411 (capture incidents), #1037 (generic OpenCode context snowballing), and #1051 (optional pruning).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions