Skip to content

cc's MCP debug logger writes nothing under perry — even when its catch handler demonstrably runs; and exec/execFile callback order is inverted #9500

Description

@proggeramlug

Two secondary findings from the #9485 investigation (PR #9498).

1. The MCP debug log tree is never created

Node writes 6 lines to ~/.cache/claude-cli-nodejs/<key>/mcp-logs-*/…jsonl on a failed connect. Perry writes nothing — no .cache tree at all — even though the bundle's OL catch demonstrably runs (i8(...)/yz(...) execute; the connect returns type:"failed", not the outer BX5 catch).

#9485 assumed the missing logs were purely downstream of never connecting. That is now disproven: the connect fails, the error path runs, and the logger is still silent. Most likely an async-write/exit-flush gap adjacent to the #9421 family (merged PR #9491 fixed appendFile error-swallowing; this logger may use a different write shape — createWriteStream is still synchronous-under-the-hood per #9493, and a failed dir-creation on this path would now throw post-#9491 rather than vanish — re-test on a post-#9491 build first, the behaviour may have changed).

Why it matters: this is why #9485 had zero diagnostics to start from. Debug logging that dies silently multiplies the cost of every other bug.

2. cp.exec / cp.execFile callbacks fire in the opposite order vs node

perry: exec→execFile; node: execFile→exec (same script, same commands). Ordering-sensitive test suites and promisified-parallel patterns can see it. Small, but it is a scheduling-semantics divergence in freshly-unlocked territory (PR #9498 made these callable as cp.exec(...) at all), so it was previously unreachable.

3. Drift-capable duplicate knowledge (maintenance, not behaviour)

is_cjs_style_native_default_import, cjs_default_namespace_name, cjs_default_base_module, and the dispatch router's table were four hand-maintained copies of the same module-name knowledge; #9498 collapsed the router's copy onto the canonical one with ratchet tests. The other three remain and can drift exactly the way the router did. A follow-up that derives them from one source kills the recurring-bug factory.

Activity

  1. proggeramlug commented on Sep 2, 2026

    @proggeramlug
    ContributorAuthor

    Significance upgrade from the clean 15-case re-run (layer-1-only cc binary, PR #9498 in): the missing MCP log tree is not a diagnostics nicety — it is the only remaining divergence for the whole mcp add family.

    B11_mcp_list_cfg's detail shows stdout already byte-identical to node after the spawn fix, with HOME-PATHS-ONLY-IN-NODE: ['.cache/claude-cli-nodejs/<CWDKEY>/mcp-logs-*'] as the sole flag. The same pattern holds across the add-family cases (~12 of the 15). The connect-path residual (#9499) bites only the self-server cases (E20/E21/E22), which still carry a stdout diff.

    So the expected suite arithmetic: #9499 + this issue together clear all 15; this issue alone is worth ~12 cases. Whoever picks it up should also note the #9485 thread's finding that the logger stays silent even when the error path demonstrably runs — and re-test on a post-#9491 build first, since appendFile error-swallowing (a prime suspect for a silently-failing logger) was fixed there.

  2. proggeramlug commented on Sep 2, 2026

    @proggeramlug
    ContributorAuthor

    PR #9531 covers all three findings; the short version of what the measurements said:

    1. The silent MCP debug logger was cc session transcripts are written incompletely: 1 line vs node's 5 (async queue-and-flush path, NOT the exit hooks) #9421, fixed by fix(fs): appendFile must report failure, not resolve; writeFileSync must throw; mode must reach open(2) (#9421) #9491. strace on the pre-fix(fs): appendFile must report failure, not resolve; writeFileSync must throw; mode must reach open(2) (#9421) #9491 cc binary shows both engines reaching the same openat(…/mcp-logs-alpha/….jsonl, O_APPEND) = ENOENT; node follows with the recursive mkdir chain and a successful retry, perry with nothing — appendFileSync returned success, so the catch { mkdirSync; appendFileSync } arm that is the only code creating the tree never ran. The flush itself always happened (the 1 s timer). cc compiled from post-fix(fs): appendFile must report failure, not resolve; writeFileSync must throw; mode must reach open(2) (#9421) #9491 main writes the logs in all 15 harness cases; the shape is pinned by test_gap_9500_mcp_debug_logger_shape.ts, which fails on a pre-fix(fs): appendFile must report failure, not resolve; writeFileSync must throw; mode must reach open(2) (#9421) #9491 tree.
    2. exec/execFile order is a same-turn delivery artefact, not a rule. Node fires whichever of two instant children was submitted second first; for three identical batches in one process it gives C B A then B A C. The only order node guarantees — completion order — perry matches, and that is what test_gap_9500_exec_callback_completion_order.ts pins. The reactor is unchanged.
    3. The module-name knowledge was five copies, not four (the HIR had two that already disagreed). They are now one table in perry-dispatch, with every consumer derived from it.

    With the logs readable, three further divergences fell out and are filed with fixtures: #9535 (child lifecycle delivered before the continuation that awaited 'spawn' → the SDK's Not connected), #9536 (fetch(URL instance) → Invalid URL), #9537 (null-byte validation missing on the require() path). Those plus #9499 are what remains of the 15.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions