Refactor run completion coordination out of hostMain#70
Conversation
|
/coder-agents-review |
There was a problem hiding this comment.
Three P3s, four nits, clean on mechanics and correctness. This is a well-executed extraction that preserves the state machine semantics, simplifies hostMain, and comes with good unit test coverage. The coordinator's two-step prepare/register API models the actual ordering constraint nicely, and the callback interface keeps the dependency boundary narrow.
Hisoka spotted that the refactor silently fixes a TOCTOU window in the old sentinel-uniqueness check: old code yielded between the uniqueness check and registration, letting two concurrent RPCs pass identical sentinels. New code moves both into the synchronous body of registerWaitedRun(). The collision probability was ~2^-32 per pair, so this was dormant, but it is a real improvement.
Takumi traced all concurrency paths (timeout races, exit ordering, microtask sequencing, timer cleanup, pre-wait sentinel arrival) and found no issues. Mafuuu modeled all four lifecycle modes and confirmed behavioral equivalence. Pariston tried to argue against the extraction and could not. The 2 integration test failures (snapshot exit 9) are pre-existing on the base SHA.
"I tried to build a case against this refactor and could not." (Pariston)
Severity summary: 0 P0, 0 P1, 0 P2, 3 P3, 4 Nit, 3 Note.
🤖 This review was automatically generated with Coder Agents.
|
/coder-agents-review |
There was a problem hiding this comment.
All seven R1 findings verified as fixed. One new P3, two nits. This is ready to merge after the test gap below.
The fix commit (0ca44b0) is clean and scoped: doc comments added, assertRunMarker deduplicated, postamble sanitization test added, types renamed, inputRunSeq tightened, invariant messages improved, method renamed to resetForExit. No regressions introduced. All 10 reviewers confirmed the fixes.
Bisky found one remaining test gap: the sanitizer-buffered postamble bytes flushed on exit. The existing "flushes pending non-completed sentinel bytes" test covers only the scanner flush, not the sanitizer flush. These are separate code paths in flushPtyDataOnExit().
Process note: the fix commit subject (review: address run completion feedback) uses a non-standard type prefix. Consider refactor: or fix: per project convention.
Severity summary: 0 P0, 0 P1, 0 P2, 1 P3, 2 Nit.
🤖 This review was automatically generated with Coder Agents.
|
/coder-agents-review |
There was a problem hiding this comment.
All R2 fixes verified. No new findings from any reviewer across round 3. This PR is clean.
13 findings across 3 rounds: 4 P3, 6 Nit, 3 Note. All P3s and Nits fixed. Notes are informational.
The extraction is faithful, the test suite is thorough (12 coordinator tests covering all state machine transitions), and the coordinator boundary is well-designed. The two-step prepare/register protocol, the narrow appender interface, and the private-field encapsulation are all improvements over the old closure-based approach.
"I tried to build a case against this refactoring and could not." (Pariston, R3)
🤖 This review was automatically generated with Coder Agents.
60e8299 to
38b64a4
Compare
|
/coder-agents-review |
There was a problem hiding this comment.
Post-rebase verification. PR rebased onto new base (includes merged #68 and #69). The PR's own files are byte-identical to R3. CI is green (11 passed). All prior fixes hold.
No new findings across 4 rounds. 13 total findings (4 P3, 6 Nit, 3 Note), all resolved. Ship it.
🤖 This review was automatically generated with Coder Agents.
Summary
hostMaininto a dedicatedRunCompletionCoordinator.hostMainfocused on PTY/RPC/session/rendering orchestration while delegating sentinel, postamble, waiter, timeout, exit, andrun_completecoordination.Fixes #62.
User-facing / automation-facing behavior
No public CLI JSON, protocol schema, message shape, or event-log shape changes are intended. Waited
runcompletion, timeout, and session-exit semantics remain the same.Validation
Dogfood proof
Used an isolated
AGENT_TTY_HOME, then destroyed the session and removed the isolated home after copying proof artifacts.Proof bundle:
Artifacts:
Verified completed waited run flags, timed-out run flags, late output after timeout, artifact cleanliness, and
input_run/run_complete.inputRunSeqrelationships.📋 Implementation Plan
Plan: Issue #62 — Move run completion coordination out of
hostMainGoal
Refactor waited-run completion bookkeeping out of
src/host/hostMain.tsinto a dedicated host-side coordinator module while preserving all public CLI JSON, protocol/event-log shapes, PTY ingestion ordering, timeout behavior, session-exit behavior, replay/export semantics, and artifact cleanliness.Evidence and constraints
run_completeappends out ofhostMain, while preserving the existing sentinel scanner module.src/host/hostMain.tsActiveRunCompletion, waiter/result types, signal sentinel generation, postamble construction)sentinelScanner.feed(...)andappendSentinelPieces(...)sentinelScanner.flush()and postamble sanitizer flushrunRPC path that appendsinput_run, registers completion tracking, writes the command/postamble, waits, then best-effort catches renderer upsrc/host/runCompletionSentinel.tssrc/protocol/messages.tsandsrc/protocol/schemas.tsRunParams,RunResult,input_run, orrun_completeschemastest/unit/host/runCompletionSentinel.test.tsandtest/integration/run.test.tsagent-ttyto wait until the command's completion signal is observed.Agreed design
New module boundary
Create
src/host/runCompletionCoordinator.tswith aRunCompletionCoordinatorthat owns waited-run completion coordination only.The coordinator owns:
outputandrun_completevia injected append callbackshostMaincontinues to own:runbehaviorpty.write(...)ptyIngestionQueueRunResultconstructionDependency shape
Inject narrow append callbacks rather than the full
EventLogor host state:hostMainshould call coordinator ingestion methods from inside the existingptyIngestionQueueso event ordering remains canonical.Public contract preservation
Do not change:
RunParamsSchemaRunResultSchemaInputRunEventPayloadSchemaRunCompleteEventPayloadSchemaIn particular, session exit for a waited run remains internally distinct but publicly implicit:
Do not add an
exitedpublic flag in this issue.Implementation phases
Phase 1 — Extract coordinator module
src/host/runCompletionCoordinator.ts.hostMaininto the new module:shellOctalEscapedBytes(...)buildRunCompleteSignalSentinel(...)buildRunCompletePostamble(...)RunCompletionSentinelScannerandRunCompletionPostambleEchoSanitizerinsrc/host/runCompletionSentinel.ts.RUN_MARKER_PATTERNinputRunSeqis a non-negative integerrun_completemaps to an active completionappendRunComplete(...)returns a non-negative integer sequenceProposed public/internal coordinator API:
Notes:
prepareWaitedRun()should be pure marker preparation. It must not mutate scanner/sanitizer/active-completion state.registerWaitedRun(...)should happen only afterinput_runappends successfully, and should finalize a unique short sentinel plus postamble before the PTY write.input_runis committed and then registration throws because a prepared sentinel collided with another active run. Generate or regenerate the sentinel/postamble during registration so sentinel uniqueness is guaranteed before returningpostamble; only impossible/programmer-error invariants may throw. Marker collision remains the same effectively-impossible UUID risk as today.prepareWaitedRun()should not receivecommand; command validation and PTY writing stay inhostMain.wait(timeoutMs)must clear timeout timers on completed/exited/rejected outcomes, and the timeout callback must no-op after the wait has resolved.__AT_MARKER_${crypto.randomUUID().replace(/-/g, '')}__.Phase 1 quality gate
Phase 2 — Wire
hostMainto coordinatorhostMainwith oneRunCompletionCoordinatorinstance.eventLog.append(...)calls:appendOutput(data)→eventLog.append('output', { data })appendRunComplete(payload)→eventLog.append('run_complete', payload)runbranch inhostMainunchanged except for removed local helper references.hostMainconst prepared = runCompletion.prepareWaitedRun()input_runwithcommand,prepared.marker, andnoWaitconst completion = runCompletion.registerWaitedRun({ marker: prepared.marker, inputRunSeq: seq }), finalizing a unique sentinel/postamble before PTY write${command}\n${completion.postamble}to PTYcompletion.wait(effectiveTimeoutMs)replayRendererThroughSeq(seq)inhostMainRunResultshape as todaydurationMsand the default30_000timeout inhostMain.runCompletion.ingestPtyData(data)from the existingpty.onDataqueue callbackrunCompletion.flushPtyDataOnExit()from the existingpty.onExitqueue callback beforehandlePtyExit(...)exitrunCompletion.resolvePendingWaitersForExit()in the samefinallyposition where waiters are resolved today, so pending waiters resolve after the attemptedexitappend even if that append throwsresolvePendingWaitersForExit()should be idempotent and clear remaining active completion bookkeeping after final flush.clear()APIs torunCompletionSentinel.ts, unless implementation proves awkward.Phase 2 quality gate
hostMainstill owns PTY/RPC/session/rendering orchestration and only delegates run-completion-specific bookkeeping.Phase 3 — Add focused coordinator tests
Add
test/unit/host/runCompletionCoordinator.test.tsusing fake appenders rather than realEventLogfiles.Cover at least:
before + sentinel + afteroutput(before), thenrun_complete, thenoutput(after)run_completecarries the correctmarkerandinputRunSeq{ kind: 'completed', seq }run_complete.inputRunSeqpoints to the rightinput_runsequence{ kind: 'timeout' }run_completestill appendsappendRunComplete(...)throw when the later sentinel arrivesresolvePendingWaitersForExit(){ kind: 'exited' }run_completeis appended by exit alone{ kind: 'exited' }appendRunComplete(...)throw for an active, non-timed-out waiteroutput.Keep
test/unit/host/runCompletionSentinel.test.tsfocused on scanner/sanitizer algorithms; do not rewrite those algorithms.Phase 3 quality gate
Phase 4 — Focused validation
Run the narrowest useful validation first:
If the repo/environment has
miseavailable and this is release-sensitive, also run:If any validation cannot run, report the exact command, failure/blocker, and next-best check.
Phase 5 — CLI dogfooding and proof bundle
Use an isolated absolute home; never mutate the real
~/.agent-tty:Then, using the returned session id:
If WebM export is available and inexpensive in the local environment, also capture a video proof:
Verify and document:
completed: true,timedOut: falsecompleted: false,timedOut: trueevents.jsonlcontains matchinginput_runand laterrun_completerecords withrun_complete.inputRunSeqpointing to theinput_runsequenceBecause this is a backend refactor, automated tests and event-log/artifact assertions are the release gate. Screenshot/video proof should still be captured for reviewer confidence when the renderer/export environment supports it; if renderer proof cannot run, report the blocker and rely on the targeted integration tests plus snapshot/asciicast checks.
Best-effort cleanup after dogfooding:
If cleanup commands differ in the current CLI, use the documented session stop/destroy command and remove only the isolated temp home created for this dogfood run.
Acceptance criteria
hostMaindelegates waited-run completion coordination toRunCompletionCoordinatorwhile retaining PTY lifecycle, RPC wiring, command dispatch, session state, and renderer catch-up ownership.runbehavior remains unchanged.runresult fields and semantics remain unchanged for completed, timed-out, and exited sessions.run_completecan still append.run_completeby itself, and clears no-longer-possible active completions after final flush.run_completeevents are appended in the same event-log position as today when an active completion sentinel is observed, with the correct marker andinputRunSeqrelationship, including output-before-sentinel and output-after-sentinel ordering.AGENT_TTY_HOMEand captures reviewer-facing artifact proof when supported.Generated with
mux• Model:openai:gpt-5.5• Thinking:xhigh