CB-118: completion misattribution guard defeated for assistant blocks over the scrape cap #2

Closed
opened 2026-07-16 09:11:03 +02:00 by ltms · 0 comments
Owner

Origin

Surfaced by the fan-out issue-hunt E2E (1 primary → 3 concurrent workers, e2e/issue_hunt_test.py). The worker assigned CompletionResolver.java reported it; verified against the code.

Bug

CompletionResolver guards against misattributing a stale completion to a send (CB-115) by comparing the delivery-time pane baseline to the turn-completion scrape, byte-for-byte:

  • captureBaseline stored the unclipped lastAssistantBlock(read).
  • resolve compares against clip(lastAssistantBlock(read)), capped at MAX_SCRAPE_CHARS = 4000.

For an assistant block longer than 4000 chars, baseline.equals(tail) is always false even when the pane never changed — the two sides are different capped representations. The CB-115 suppression then fails to fire, so a rapid back-to-back send can be resolved with the previous turn's stale answer.

Fix

Clip the baseline in captureBaseline too, so both sides compare the same capped representation:

baseline = clip(lastAssistantBlock(agents.read(target, SCRAPE_SOURCE)));

Regression test: CompletionResolverTest.suppressesAnUnchangedCompletionEvenWhenTheBlockExceedsTheScrapeCap — an unchanged >cap block stays suppressed.

Same hunt — two findings rejected after verification

  • WorkerService.stop() "locatePane before the try/catch" — FALSE POSITIVE. WorkspaceControl.locatePane already wraps pane.get in try/catch and returns null on an already-gone pane; stop()'s idempotency holds.
  • Rendezvous.complete() "never removes the resolved waiter" — FALSE POSITIVE. The owning MessageService.send() removes it in finally { rendezvous.close(target, reply) }, keyed to that exact future; per-session send serialization means the next open() starts clean. The proposed in-complete() remove is redundant and would blur the sender-owns-open+close / fallbacks-are-waiter-specific (CB-116) model.

Severity: low (needs a >4000-char assistant block on a rapid back-to-back send) but the fix is a trivial one-liner that makes the guard sound.

## Origin Surfaced by the fan-out issue-hunt E2E (1 primary → 3 concurrent workers, `e2e/issue_hunt_test.py`). The worker assigned `CompletionResolver.java` reported it; verified against the code. ## Bug `CompletionResolver` guards against misattributing a stale completion to a send (CB-115) by comparing the delivery-time pane baseline to the turn-completion scrape, byte-for-byte: - `captureBaseline` stored the **unclipped** `lastAssistantBlock(read)`. - `resolve` compares against `clip(lastAssistantBlock(read))`, capped at `MAX_SCRAPE_CHARS = 4000`. For an assistant block longer than 4000 chars, `baseline.equals(tail)` is **always false** even when the pane never changed — the two sides are different capped representations. The CB-115 suppression then fails to fire, so a rapid back-to-back send can be resolved with the **previous turn's stale answer**. ## Fix Clip the baseline in `captureBaseline` too, so both sides compare the same capped representation: ```java baseline = clip(lastAssistantBlock(agents.read(target, SCRAPE_SOURCE))); ``` Regression test: `CompletionResolverTest.suppressesAnUnchangedCompletionEvenWhenTheBlockExceedsTheScrapeCap` — an unchanged >cap block stays suppressed. ## Same hunt — two findings rejected after verification - **`WorkerService.stop()` "locatePane before the try/catch"** — FALSE POSITIVE. `WorkspaceControl.locatePane` already wraps `pane.get` in try/catch and returns `null` on an already-gone pane; `stop()`'s idempotency holds. - **`Rendezvous.complete()` "never removes the resolved waiter"** — FALSE POSITIVE. The owning `MessageService.send()` removes it in `finally { rendezvous.close(target, reply) }`, keyed to that exact future; per-session send serialization means the next `open()` starts clean. The proposed in-`complete()` remove is redundant and would blur the sender-owns-open+close / fallbacks-are-waiter-specific (CB-116) model. Severity: low (needs a >4000-char assistant block on a rapid back-to-back send) but the fix is a trivial one-liner that makes the guard sound.
ltms closed this issue 2026-07-16 09:11:30 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: fleet/fleetd#2