Skip to content

feat(doctor): add a console ctrl+c check - #250

Merged
quickbeard merged 1 commit into
mainfrom
doctor-console-check
Aug 6, 2026
Merged

feat(doctor): add a console ctrl+c check#250
quickbeard merged 1 commit into
mainfrom
doctor-console-check

Conversation

@quickbeard

Copy link
Copy Markdown
Owner

Why

Quitting CoDev Code hangs for some Windows users and not others (#249). The variable we could not observe is how ctrl+c is delivered.

CoDev Code clears ENABLE_PROCESSED_INPUT on the shared console while it runs, so ctrl+c arrives as a keypress its own double-ctrl+c exit can handle. When that guard doesn't take, ctrl+c stays a console break — delivered to every process attached to the console, including the four cmd.exe batch shims in a hub launch chain, which stop at Terminate batch job (Y/N)? instead of exiting.

Until now the only way to tell an affected machine from a working one was to ask the user for a screenshot and guess.

What

A console-ctrl-c environment check:

✓ Console ctrl+c handling   ctrl+c reaches the agent as a keypress (Windows Terminal).
✗ Console ctrl+c handling   ctrl+c will reach this console as a break event (conhost).
    what:  ctrl+c is not being delivered to CoDev Code as a keypress.
    cause: CoDev Code could not read the console mode at all (kernel32 dlopen or GetConsoleMode failed).

The agent reports its own verdict via codev debug console --json (codev-code PR #163) rather than the hub reading the console itself, for two reasons: the agent runs under Bun and owns the FFI that reads the mode, and it can install the real guard and re-read to prove the bit actually clears. A mode read from the hub would only describe the shell's console at rest, which is not the question.

The three failure modes are graded apart — FFI unavailable, guard didn't install, guard installed but the bit stayed set — so the output names its own cause.

Skips (not fails) when: not Windows, stdin isn't a console, CoDev Code isn't installed, or the installed build predates codev debug console. That last one means the two PRs can land in either order.

Supporting changes

  • run.tsresolveAgentPath split out of agentOnPath, which becomes a one-line wrapper. The check probes the resolved path, never the bare name: the bare name would re-enter our own PATH shim and relaunch the agent through the hub, dragging in the upload daemon and gateway-key refresh — side effects a diagnostic has no business causing.
  • npm.tsexecAsync gains { inheritStdin }. The probe reads the console input mode through its stdin handle, and a piped stdin is not a console. execFile has no stdio option (neither its typings nor its docs), so that one path goes through spawn and collects the streams itself. Kept inside execAsync rather than spawned from doctor.ts so the child stays covered by the diagnostic log and commandLog.
    Its shell branch now also quotes a file containing spaces — callers can pass a resolved absolute path like C:\Program Files\...\codev.cmd, which cmd.exe would otherwise split. Args stay unquoted, as before.

Tests

Six new tests in tests/lib/doctor.test.ts, using the existing stdinApi / platform-swap seams:

  • skipped off Windows
  • passes when the guard clears processed input
  • fails and names the cause when the FFI is unavailable
  • distinguishes "installed but did not stick" from "never installed"
  • skips on an agent without the subcommand, pointing at codevhub update
  • probes the resolved path with inheritStdin, never the bare name

Full suite green via the pre-commit hook: biome check, tsc --noEmit, 1390 passed / 2 skipped, pnpm build.

Relationship to #249

Independent, and branched off main so they can merge in any order. #249 makes a wedged launch survivable; this one tells you whether a given machine is at risk in the first place.

Neither is verified on Windows — that is what this check exists to change.

🤖 Generated with Claude Code

Quitting CoDev Code hangs for some Windows users and not others, and the
variable we could not observe is how ctrl+c is delivered. CoDev Code
clears ENABLE_PROCESSED_INPUT on the shared console while it runs so
ctrl+c arrives as a keypress; when that guard does not take, ctrl+c
stays a console break delivered to every process attached to the
console, including the cmd.exe shims in our launch chain, which stop at
"Terminate batch job (Y/N)?" instead of exiting.

The agent reports its own verdict (`codev debug console --json`): it
runs under Bun and owns the FFI that reads the console mode, and it
installs the real guard to check that clearing the bit sticks. This
check relays that and names which precondition failed.

Two supporting changes:

- run.ts splits resolveAgentPath out of agentOnPath. The check probes
  the resolved path, never the bare name — the bare name would re-enter
  our own PATH shim and relaunch the agent through the hub, with the
  upload daemon and gateway key refresh in tow.
- npm.ts gains execAsync({ inheritStdin }). The probe reads the console
  input mode through its stdin handle, and a piped stdin is not a
  console. execFile has no stdio option, so that path uses spawn and
  collects the streams itself. Its shell branch now also quotes a file
  carrying spaces, since callers can pass a resolved absolute path.

Skips rather than fails on a build predating `codev debug console`, so
the two repos can land in either order.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@quickbeard
quickbeard merged commit 6e81dd7 into main Aug 6, 2026
2 of 4 checks passed
@quickbeard
quickbeard deleted the doctor-console-check branch August 6, 2026 04:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant