Fix: endSub has no timeout on readDone, unlike closeSub (#17) - #18
Merged
thegoodengineer merged 2 commits intoAug 14, 2026
Merged
Conversation
This was referenced Aug 14, 2026
…onflict PR thegoodengineer#16 (appendNote) and PR thegoodengineer#18 (waitAtMost) both inserted new code at the same point in recorder.ts. Resolved by keeping both functions: appendNote still backs the two diagnostic-note call sites, waitAtMost still bounds endSub's wait on entry.readDone. Also fixes a latent race in checklist.test.ts exposed by this merge: its helper resolved as soon as the raw onDidEndTerminalShellExecution event fired, not once TruthLog's store actually recorded the entry - those were never the same moment, recorder.ts's END handler still has to finish its (now bounded) wait on readDone first. Rewrote it to wait on store.onDidChange like recording.test.ts already correctly does. Full suite: 47 passing, 0 failing, verified from raw mocha output, not the task-runner's exit code.
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.
Fixes #17
Bug
endSubinrecorder.tsawaitedentry.readDonewith no timeout before finalizing a command's record. Its sibling handler,closeSub, already bounds the same wait to 1 second, with a comment acknowledging thatread()"may never finish." That protection was never applied toendSub— the normal, non-killed-terminal completion path. If a command's output stream doesn't resolve promptly after the shell reports it finished (e.g. a backgrounded/detached child process keeping the terminal's stream open),drain()never resolves,entry.readDonenever resolves, andendSub's handler would await forever — so the command never gets a row, even though the shell already reported a definite exit code.Fix
Extracted the bounded-wait pattern into a small exported helper,
waitAtMost(promise, timeoutMs), which races a promise against a timeout and resolves either way (swallowing rejection, since callers only care that the wait ended).endSubnow doesawait waitAtMost(entry.readDone, 1000)instead of an unbounded await.Timeout chosen: 1000ms, matching
closeSub's existing bound exactly. Both handlers are protecting against the same underlying risk (read()not resolving), and there's no reason the normal-completion path should tolerate a longer or shorter stall than the terminal-closed path already treats as reasonable.closeSubitself is untouched — it still uses its own inlinePromise.race([...])exactly as before, per the issue's suggested fix. (Its neighboring comment "Bounded, unlike the end handler's wait" is now slightly stale sinceendSubis bounded too, but I leftcloseSub's code as-is rather than touch a working, unrelated path — happy to update that comment in a follow-up if preferred.)Testing
npm run compilepasses cleanly with no errors.src/test/suite/endSubTimeout.test.ts, a new regression-test file that importswaitAtMostdirectly (same patternoutputTruncation.test.tsalready uses forsliceUtf8) and exercises it against:read()drain — asserting the wait still returns within the 1000ms bound instead of hanging forever, and doesn't return before the timeout either.waitAtMostnever rejects.TerminalShellExecution.read()through a real terminal/command. This test harness has no mocking library, andvscode.window's terminal-shell-execution events aren't something I can reliably fake or force to hang from outside VS Code's own shell integration implementation (the same class of limitation noted for thedrain()catch-block path in Fix: diagnostic notes silently dropped once output is already truncated (#15) #16). Instead, this test proves the exact bounded-wait mechanismendSubnow relies on doesn't hang on a promise that never resolves — which is precisely the failure mode described in endSub has no timeout on entry.readDone, unlike closeSub — a stuck output stream silently drops the row #17 — deterministically and without needing a real terminal at all.npm test(the Electron-hosted VS Code integration suite) in this environment. It's a Windows sandbox with no GUI/xvfb, andCode.exedoesn't launch as a real GUI app here regardless of shell — this matches this repo's own CI design, where the Windows/macOS jobs are compile-only and onlyubuntu-latestunderxvfbruns the integration suite. I did separately sanity-check the exactwaitAtMostalgorithm in a standalone plain-Node script (bypassing the blocked Electron host) and confirmed its timing behavior matches what the new test asserts. This PR's CI run will be the first real execution of the new test file inside the actual suite.