Skip to content

fix(coding-agent): isolate Inspector import crashes#321

Closed
minpeter wants to merge 1 commit into
code-yeongyu:mainfrom
minpeter:fix/inspector-vm-import-crash
Closed

fix(coding-agent): isolate Inspector import crashes#321
minpeter wants to merge 1 commit into
code-yeongyu:mainfrom
minpeter:fix/inspector-vm-import-crash

Conversation

@minpeter

@minpeter minpeter commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

  • hand an inherited fixed Node Inspector endpoint from the launcher to cli-main
  • keep Inspector VM dynamic-import recovery fail-closed by default
  • allow an explicit startup opt-in for the exact reported evaluator rejection
  • preserve the existing fatal path for application VM, late environment mutation, direct uncaught exceptions, and
    unrelated failures

This is an independent one-commit PR based directly on current main; it does not depend on compaction PR #310 or
the fork's compaction-TUI/codemode PRs.

Root cause

node inspect evaluates expressions in a VM context without an importModuleDynamically callback. A delayed import:

exec setTimeout(()=>import('node:fs'),0)

therefore rejects with:

ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING

The CLI also has a launcher/child topology. With a fixed NODE_OPTIONS=--inspect=..., the launcher previously claimed
the endpoint and cli-main inherited the same option, producing address already in use. The debugger attached to
the wrapper rather than the TUI child.

Behavior

Before spawning cli-main, the launcher now releases an active Inspector endpoint that came from inherited Node
startup options. The child can bind the same endpoint, so an existing fixed-port debugging command targets the
interactive process.

Recovery remains disabled unless the process starts with:

SENPI_RECOVER_INSPECTOR_VM_IMPORT=1 \
NODE_OPTIONS=--inspect=127.0.0.1:9229 \
senpi

The recovery predicate then requires:

  • active Inspector endpoint
  • unhandledRejection origin
  • ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING
  • Node's dynamic-import callback frame
  • Inspector <anonymous> timer provenance

The opt-in is captured when the policy module loads, before runtime/extension loading. Later process.env mutation
cannot enable recovery. This explicit gate is necessary because Node does not expose a non-spoofable Inspector origin
on the global rejection.

RED -> GREEN

  • RED: fixed-port child emitted address already in use
  • RED: exact reported Inspector rejection exited through uncaughtException
  • GREEN: deterministic parent -> child rebind on the same OS-assigned fixed port
  • GREEN: explicit opt-in keeps the TUI alive with guidance
  • GREEN: no opt-in, late env mutation, application VM, direct uncaughtException, and unrelated errors remain fatal

Verification

  • focused regression: 5/5 passed
  • coding-agent build: passed
  • root npm run build: passed
  • root npm run check: passed
  • changed-file Biome: passed
  • git diff --check: passed

The parallel coding-agent suite completed 4,509 passing tests and 32 skips, but three unrelated temporary-file writes
returned Linux errno -122 under concurrent load. Each affected test file passed directly afterward. A serial full
package run exceeded the 600-second command ceiling before summary.

Real-surface QA

  • fixed-port fixpi --help: two sequential Inspector endpoints, no port conflict, exit 0
  • real TUI + real node inspect, explicit opt-in: warning visible, process remained alive
  • same real TUI without opt-in: exact reported error remained fatal, exit 1
  • unrelated TypeError: remained fatal, exit 1
  • browser-rendered xterm.js screenshot/transcript inspected
  • isolated config/session/HOME, offline mode, no real provider or credential use
  • all Inspector clients, PTYs, browsers, ports, and sandboxes cleaned

Review

Independent goal, code-quality, security, hands-on QA, and upstream-context reviewers returned PASS with no remaining
blockers.


Summary by cubic

Stops Node Inspector port conflicts and isolates the Inspector VM dynamic-import crash so sessions don’t die unexpectedly. The debugger now attaches to the TUI, and with an explicit opt-in the exact Inspector import rejection no longer kills the process.

  • Bug Fixes

    • Launcher releases an inherited --inspect endpoint before spawning cli-main, letting the child rebind the same fixed port and avoiding “address already in use.”
    • Adds inspector-policy with an opt-in that keeps the TUI running only for ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING from Inspector eval (checked via origin and stack); shows a warning. All other errors remain fatal, and late env changes cannot enable recovery.
  • Migration

    • To enable recovery while debugging: start with SENPI_RECOVER_INSPECTOR_VM_IMPORT=1 and NODE_OPTIONS=--inspect=127.0.0.1:<port>. Defaults remain unchanged.

Written for commit 06fad3d. Summary will update on new commits.

Review in cubic

@minpeter

Copy link
Copy Markdown
Contributor Author

Superseded by #336, the verified patch-equivalent replacement based directly on current main. The replacement is an open draft, mergeable, one commit ahead/zero behind, with fresh focused test, build/check, and enabled/default real Inspector CLI QA evidence.

@minpeter minpeter closed this Jul 24, 2026
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