fix(coding-agent): assert goal_updated payload instead of emit arity - #3637
Conversation
The immutable per-attempt scope facility (Yeachan-Heo#3608) routes every session extension event through emit(event, undefined, deliveryScope). The goal mode integration test still asserted the single-argument call shape via toHaveBeenCalledWith, so it fails on current dev with "Number of calls: 2" even though the product behaves correctly: goal_updated is session-level and legitimately carries no attempt scope. Assert the delivered payload rather than the call arity. The test now locates the terminal goal_updated event the throwing hook received and proves it carries goal.status === "complete" and state.mode === "exiting", which is what the test's name actually claims. This is coupled to product behaviour rather than to the emit signature: dropping state from the goal_updated emit in agent-session.ts makes the new assertion fail. No product change. expect() calls in the file go from 80 to 82.
Fix post-merge regressions from the AttemptScope facility merge (#3608): 1. forceAbort no longer requires logicalRunId — falls back gracefully when AttemptScope handle is not registered. 2. setAttemptRecordStore injection uses typeof guard for mock ExtensionRunners. 3. forceAbort overload simplified to single optional signature. 4. abort-timeout: tries managed logicalRunId then active, catches fallback. External #3637 owns the goal-mode assertion fix; this branch does not duplicate it. Lore-id: attemptscope-postmerge-repair-v2 Tested: goal/cancel/retry/fallback/attemptscope/handoff/compaction suites pass Confidence: high Scope-risk: narrow Reversibility: additive
|
Heads up on an overlap I noticed after opening this PR, so you can close whichever one is redundant. #3638 ("AttemptScope post-merge compatibility repairs") touches the same file and the same assertion. The two changes differ: #3638's approach — pin the new arity: expect(emit).toHaveBeenCalledWith(expect.objectContaining({ type: "goal_updated" }), undefined, undefined);This PR's approach — assert the payload, ignore arity: const goalUpdates = emit.mock.calls.map(([event]) => event).filter(event => event.type === "goal_updated");
expect(goalUpdates.length).toBeGreaterThan(0);
const terminalUpdate = goalUpdates[goalUpdates.length - 1];
expect(terminalUpdate?.goal?.status).toBe("complete");
expect(terminalUpdate?.state?.mode).toBe("exiting");Both make the test pass. The difference in what they detect:
That said, #3638 is the more complete change — it carries the product-side repairs for So: please treat #3638 as authoritative and close this one if you prefer to keep your version. I am not attached to it. If you would rather keep the payload assertion, the two changes are a clean textual merge — only the one Sorry for the duplicated review effort. I opened this before #3638 existed and did not re-check ownership immediately before pushing. Two things I found while verifying, which may be useful either way:
|
PR #3637 hostile exact-head review: MERGE_READY (P0=0, P1=0). Test-only fix, product-coupled to AttemptScope 3-arg emit shape. CI 13/13 green. — |
Fix post-merge regressions from the AttemptScope facility merge (#3608): 1. forceAbort no longer requires logicalRunId — falls back gracefully when AttemptScope handle is not registered. 2. setAttemptRecordStore injection uses typeof guard for mock ExtensionRunners. 3. forceAbort overload simplified to single optional signature. 4. abort-timeout: tries managed logicalRunId then active, catches fallback. External #3637 owns the goal-mode assertion fix; this branch does not duplicate it. Lore-id: attemptscope-postmerge-repair-v2 Tested: goal/cancel/retry/fallback/attemptscope/handoff/compaction suites pass Confidence: high Scope-risk: narrow Reversibility: additive
Fix post-merge regressions from the AttemptScope facility merge (#3608): 1. forceAbort no longer requires logicalRunId — falls back gracefully when AttemptScope handle is not registered. 2. setAttemptRecordStore injection uses typeof guard for mock ExtensionRunners. 3. forceAbort overload simplified to single optional signature. 4. abort-timeout: tries managed logicalRunId then active, catches fallback. External #3637 owns the goal-mode assertion fix; this branch does not duplicate it. Lore-id: attemptscope-postmerge-repair-v2 Tested: goal/cancel/retry/fallback/attemptscope/handoff/compaction suites pass Confidence: high Scope-risk: narrow Reversibility: additive
…) (#3638) Fix post-merge regressions from the AttemptScope facility merge (#3608): 1. forceAbort no longer requires logicalRunId — falls back gracefully when AttemptScope handle is not registered. 2. setAttemptRecordStore injection uses typeof guard for mock ExtensionRunners. 3. forceAbort overload simplified to single optional signature. 4. abort-timeout: tries managed logicalRunId then active, catches fallback. External #3637 owns the goal-mode assertion fix; this branch does not duplicate it. Lore-id: attemptscope-postmerge-repair-v2 Tested: goal/cancel/retry/fallback/attemptscope/handoff/compaction suites pass Confidence: high Scope-risk: narrow Reversibility: additive Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
Summary
packages/coding-agent/test/goals/goal-mode-integration.test.tsfails on currentdev(b6198e748) with a stale call-arity assertion. This is a test-only repair; no product file changes.The immutable per-attempt scope facility (#3608) routes every session extension event through the three-argument shape:
The test still asserted the single-argument shape:
toHaveBeenCalledWithmatches the full argument list, so the extraundefined, undefinedmakes it fail withNumber of calls: 2.I checked whether
deliveryScope === undefinedis itself the defect, and I do not believe it is.goal_updatedis emitted from#emitSessionEvent(agent-session.ts:2768) with no attempt scope attached, anddeliveryScoperesolves asscope ?? event.scope. Goal state is session-level rather than attempt-level, so no scope is the correct value here. The product looks right and the assertion is the stale part.Change
Assert the delivered payload instead of the call arity. The test now locates the terminal
goal_updatedevent the throwing hook actually received and proves it carries the completion state:This is what the test's name already claims, and it is stronger than what it replaced: the old line only proved some
goal_updatedfired, while this proves the throwing hook received the terminal completion payload.expect()calls in the file go from 80 to 82. The spy's parameter type is widened structurally, so there is no cast and no private access.Verification
On
b6198e748, darwin-arm64:bun test packages/coding-agent/test/goals/goal-mode-integration.test.ts— 17 pass / 0 fail, 82expect()calls (was 16 pass / 1 fail, 80 calls)bun run check:types(tsc -p packages/coding-agent/tsconfig.json --noEmit) — exit 0bun x @biomejs/biome check packages/coding-agent/test/goals/goal-mode-integration.test.ts— exit 0Two-sided proof, since a fixture-side change deserves the scrutiny:
state: event.statefrom thegoal_updatedemit inagent-session.tsmakes the new assertion fail (16 pass / 1 fail); restoring the product returns 17 pass / 0 fail.The second proof is the one I care about. I did not want to hand you an assertion that merely accommodates whatever the code currently does — it is bound to product behaviour and fails when that behaviour regresses.
Notes
I found this while checking why
test:@gajae-code/coding-agent:shard-1-of-8is red. It was the only remaining failing test in that shard on my open PRs (#3618, #3620) after the earlier topology failures cleared. The file is not inCODING_AGENT_SHARD_ONE_COVERAGE_PATHSand, as far as I can tell from the open PR list, is not claimed by any open PR — if it overlaps work you already have in flight, please close this and I will drop it without argument.Happy to adjust the assertion shape if you would prefer a different convention here; there was no existing precedent in the repo for asserting the three-argument
emitshape, so I picked one.