@with-logic/elwood 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +565 -1
- package/CONTRIBUTING.md +3 -0
- package/README.md +4 -2
- package/dist/bridge/socket-release.d.ts +15 -0
- package/dist/bridge/socket-release.d.ts.map +1 -0
- package/dist/bridge/socket-release.js +22 -0
- package/dist/bridge/socket-release.js.map +1 -0
- package/dist/claude/composer/border.d.ts +12 -0
- package/dist/claude/composer/border.d.ts.map +1 -0
- package/dist/claude/composer/border.js +12 -0
- package/dist/claude/composer/border.js.map +1 -0
- package/dist/claude/composer/empty-input.d.ts +15 -0
- package/dist/claude/composer/empty-input.d.ts.map +1 -1
- package/dist/claude/composer/empty-input.js +30 -11
- package/dist/claude/composer/empty-input.js.map +1 -1
- package/dist/claude/composer/footer.d.ts +26 -2
- package/dist/claude/composer/footer.d.ts.map +1 -1
- package/dist/claude/composer/footer.js +29 -2
- package/dist/claude/composer/footer.js.map +1 -1
- package/dist/claude/composer/idle.d.ts +14 -0
- package/dist/claude/composer/idle.d.ts.map +1 -0
- package/dist/claude/composer/idle.js +17 -0
- package/dist/claude/composer/idle.js.map +1 -0
- package/dist/claude/composer/last.d.ts +2 -0
- package/dist/claude/composer/last.d.ts.map +1 -0
- package/dist/claude/composer/last.js +15 -0
- package/dist/claude/composer/last.js.map +1 -0
- package/dist/claude/composer/staged-input.d.ts.map +1 -1
- package/dist/claude/composer/staged-input.js +2 -1
- package/dist/claude/composer/staged-input.js.map +1 -1
- package/dist/claude/hooks/stop-failure.d.ts +15 -2
- package/dist/claude/hooks/stop-failure.d.ts.map +1 -1
- package/dist/claude/hooks/stop-failure.js +32 -9
- package/dist/claude/hooks/stop-failure.js.map +1 -1
- package/dist/claude/login/stages.d.ts.map +1 -1
- package/dist/claude/login/stages.js +2 -2
- package/dist/claude/login/stages.js.map +1 -1
- package/dist/claude/model-picker.d.ts +1 -1
- package/dist/claude/model-picker.d.ts.map +1 -1
- package/dist/claude/model-picker.js +27 -10
- package/dist/claude/model-picker.js.map +1 -1
- package/dist/claude/preflight.d.ts +1 -1
- package/dist/claude/preflight.d.ts.map +1 -1
- package/dist/claude/preflight.js +18 -12
- package/dist/claude/preflight.js.map +1 -1
- package/dist/claude/questions/navigation.d.ts.map +1 -1
- package/dist/claude/questions/navigation.js +1 -2
- package/dist/claude/questions/navigation.js.map +1 -1
- package/dist/claude/questions/screen.d.ts.map +1 -1
- package/dist/claude/questions/screen.js +10 -2
- package/dist/claude/questions/screen.js.map +1 -1
- package/dist/claude/screen-table.d.ts +8 -5
- package/dist/claude/screen-table.d.ts.map +1 -1
- package/dist/claude/screen-table.js +20 -12
- package/dist/claude/screen-table.js.map +1 -1
- package/dist/claude/session/bridge.d.ts +2 -1
- package/dist/claude/session/bridge.d.ts.map +1 -1
- package/dist/claude/session/bridge.js +3 -1
- package/dist/claude/session/bridge.js.map +1 -1
- package/dist/claude/session/build.d.ts.map +1 -1
- package/dist/claude/session/build.js +13 -11
- package/dist/claude/session/build.js.map +1 -1
- package/dist/claude/session/cleanup.d.ts +18 -0
- package/dist/claude/session/cleanup.d.ts.map +1 -0
- package/dist/claude/session/cleanup.js +10 -0
- package/dist/claude/session/cleanup.js.map +1 -0
- package/dist/claude/session/index.d.ts.map +1 -1
- package/dist/claude/session/index.js +27 -13
- package/dist/claude/session/index.js.map +1 -1
- package/dist/claude/session/instance.d.ts +7 -8
- package/dist/claude/session/instance.d.ts.map +1 -1
- package/dist/claude/session/instance.js +9 -13
- package/dist/claude/session/instance.js.map +1 -1
- package/dist/claude/session/resume.d.ts.map +1 -1
- package/dist/claude/session/resume.js +14 -5
- package/dist/claude/session/resume.js.map +1 -1
- package/dist/claude/session/transcript.js +2 -2
- package/dist/claude/session/transcript.js.map +1 -1
- package/dist/claude/simple.d.ts +1 -1
- package/dist/claude/simple.d.ts.map +1 -1
- package/dist/claude/simple.js +3 -3
- package/dist/claude/simple.js.map +1 -1
- package/dist/claude/startup-prompts.d.ts +2 -0
- package/dist/claude/startup-prompts.d.ts.map +1 -1
- package/dist/claude/startup-prompts.js +5 -1
- package/dist/claude/startup-prompts.js.map +1 -1
- package/dist/claude/transcript/cursor-io.d.ts +1 -1
- package/dist/claude/transcript/cursor-io.d.ts.map +1 -1
- package/dist/claude/transcript/cursor-io.js +1 -1
- package/dist/claude/transcript/cursor-io.js.map +1 -1
- package/dist/claude/transcript/cursor.d.ts +5 -3
- package/dist/claude/transcript/cursor.d.ts.map +1 -1
- package/dist/claude/transcript/cursor.js +18 -8
- package/dist/claude/transcript/cursor.js.map +1 -1
- package/dist/claude/transcript/drain-context.d.ts +4 -3
- package/dist/claude/transcript/drain-context.d.ts.map +1 -1
- package/dist/claude/transcript/drain-context.js.map +1 -1
- package/dist/claude/transcript/emit.d.ts +2 -1
- package/dist/claude/transcript/emit.d.ts.map +1 -1
- package/dist/claude/transcript/emit.js +1 -0
- package/dist/claude/transcript/emit.js.map +1 -1
- package/dist/claude/transcript/index.d.ts.map +1 -1
- package/dist/claude/transcript/index.js +2 -2
- package/dist/claude/transcript/index.js.map +1 -1
- package/dist/claude/transcript/warnings.d.ts +2 -1
- package/dist/claude/transcript/warnings.d.ts.map +1 -1
- package/dist/claude/transcript/warnings.js +3 -2
- package/dist/claude/transcript/warnings.js.map +1 -1
- package/dist/cli/config/effective.d.ts +2 -2
- package/dist/cli/config/effective.d.ts.map +1 -1
- package/dist/cli/config/effective.js +0 -2
- package/dist/cli/config/effective.js.map +1 -1
- package/dist/cli/interactive/spawn.d.ts +0 -2
- package/dist/cli/interactive/spawn.d.ts.map +1 -1
- package/dist/cli/interactive/spawn.js +27 -24
- package/dist/cli/interactive/spawn.js.map +1 -1
- package/dist/cli/lifecycle/index.d.ts.map +1 -1
- package/dist/cli/lifecycle/index.js +5 -3
- package/dist/cli/lifecycle/index.js.map +1 -1
- package/dist/cli/main-failure.d.ts.map +1 -1
- package/dist/cli/main-failure.js +5 -2
- package/dist/cli/main-failure.js.map +1 -1
- package/dist/cli/models/combined.d.ts.map +1 -1
- package/dist/cli/models/combined.js +3 -2
- package/dist/cli/models/combined.js.map +1 -1
- package/dist/cli/models/render.d.ts.map +1 -1
- package/dist/cli/models/render.js +2 -1
- package/dist/cli/models/render.js.map +1 -1
- package/dist/cli/output/cleanup-error.d.ts +22 -0
- package/dist/cli/output/cleanup-error.d.ts.map +1 -0
- package/dist/cli/output/cleanup-error.js +67 -0
- package/dist/cli/output/cleanup-error.js.map +1 -0
- package/dist/cli/output/types.d.ts +6 -2
- package/dist/cli/output/types.d.ts.map +1 -1
- package/dist/cli/request/agent-detect.d.ts +6 -2
- package/dist/cli/request/agent-detect.d.ts.map +1 -1
- package/dist/cli/request/agent-detect.js +14 -6
- package/dist/cli/request/agent-detect.js.map +1 -1
- package/dist/cli/request/finalize.d.ts +1 -1
- package/dist/cli/request/finalize.d.ts.map +1 -1
- package/dist/cli/request/finalize.js +4 -25
- package/dist/cli/request/finalize.js.map +1 -1
- package/dist/cli/request/index.d.ts.map +1 -1
- package/dist/cli/request/index.js +11 -5
- package/dist/cli/request/index.js.map +1 -1
- package/dist/cli/request/path-replacement.d.ts +27 -0
- package/dist/cli/request/path-replacement.d.ts.map +1 -0
- package/dist/cli/request/path-replacement.js +79 -0
- package/dist/cli/request/path-replacement.js.map +1 -0
- package/dist/cli/request/workspace.d.ts +8 -0
- package/dist/cli/request/workspace.d.ts.map +1 -0
- package/dist/cli/request/workspace.js +50 -0
- package/dist/cli/request/workspace.js.map +1 -0
- package/dist/cli/run/output.d.ts.map +1 -1
- package/dist/cli/run/output.js +2 -1
- package/dist/cli/run/output.js.map +1 -1
- package/dist/cli/run/subscribe.d.ts.map +1 -1
- package/dist/cli/run/subscribe.js +1 -1
- package/dist/cli/run/subscribe.js.map +1 -1
- package/dist/cli/session/index.d.ts +4 -4
- package/dist/cli/session/index.d.ts.map +1 -1
- package/dist/cli/session/index.js +5 -5
- package/dist/cli/session/index.js.map +1 -1
- package/dist/cli/session/launch.d.ts +2 -2
- package/dist/cli/session/launch.d.ts.map +1 -1
- package/dist/cli/session/launch.js +12 -3
- package/dist/cli/session/launch.js.map +1 -1
- package/dist/cli/session/turn-readers.d.ts +14 -0
- package/dist/cli/session/turn-readers.d.ts.map +1 -0
- package/dist/cli/session/turn-readers.js +13 -0
- package/dist/cli/session/turn-readers.js.map +1 -0
- package/dist/codex/config/default-model.d.ts +24 -0
- package/dist/codex/config/default-model.d.ts.map +1 -0
- package/dist/codex/config/default-model.js +133 -0
- package/dist/codex/config/default-model.js.map +1 -0
- package/dist/codex/config/home.d.ts +35 -0
- package/dist/codex/config/home.d.ts.map +1 -0
- package/dist/codex/config/home.js +122 -0
- package/dist/codex/config/home.js.map +1 -0
- package/dist/codex/config/lock.d.ts +17 -11
- package/dist/codex/config/lock.d.ts.map +1 -1
- package/dist/codex/config/lock.js +102 -11
- package/dist/codex/config/lock.js.map +1 -1
- package/dist/codex/config/read.d.ts +16 -0
- package/dist/codex/config/read.d.ts.map +1 -0
- package/dist/codex/config/read.js +75 -0
- package/dist/codex/config/read.js.map +1 -0
- package/dist/codex/config/restore.d.ts +17 -7
- package/dist/codex/config/restore.d.ts.map +1 -1
- package/dist/codex/config/restore.js +33 -20
- package/dist/codex/config/restore.js.map +1 -1
- package/dist/codex/config/transaction.d.ts +6 -4
- package/dist/codex/config/transaction.d.ts.map +1 -1
- package/dist/codex/config/transaction.js +4 -3
- package/dist/codex/config/transaction.js.map +1 -1
- package/dist/codex/images/clipboard-copies.d.ts +39 -0
- package/dist/codex/images/clipboard-copies.d.ts.map +1 -0
- package/dist/codex/images/clipboard-copies.js +91 -0
- package/dist/codex/images/clipboard-copies.js.map +1 -0
- package/dist/codex/model-picker.d.ts +1 -1
- package/dist/codex/model-picker.d.ts.map +1 -1
- package/dist/codex/model-picker.js +16 -6
- package/dist/codex/model-picker.js.map +1 -1
- package/dist/codex/preflight.d.ts +2 -2
- package/dist/codex/preflight.d.ts.map +1 -1
- package/dist/codex/preflight.js +28 -21
- package/dist/codex/preflight.js.map +1 -1
- package/dist/codex/pty.d.ts +25 -3
- package/dist/codex/pty.d.ts.map +1 -1
- package/dist/codex/pty.js +53 -7
- package/dist/codex/pty.js.map +1 -1
- package/dist/codex/questions/candidates.d.ts.map +1 -1
- package/dist/codex/questions/candidates.js +4 -5
- package/dist/codex/questions/candidates.js.map +1 -1
- package/dist/codex/questions/driver.js +2 -2
- package/dist/codex/questions/driver.js.map +1 -1
- package/dist/codex/questions/records.d.ts +1 -1
- package/dist/codex/questions/records.d.ts.map +1 -1
- package/dist/codex/questions/records.js +1 -3
- package/dist/codex/questions/records.js.map +1 -1
- package/dist/codex/screen/clearance.d.ts +6 -0
- package/dist/codex/screen/clearance.d.ts.map +1 -1
- package/dist/codex/screen/clearance.js +10 -0
- package/dist/codex/screen/clearance.js.map +1 -1
- package/dist/codex/screen-table.d.ts.map +1 -1
- package/dist/codex/screen-table.js +6 -2
- package/dist/codex/screen-table.js.map +1 -1
- package/dist/codex/session/bridge.d.ts +1 -0
- package/dist/codex/session/bridge.d.ts.map +1 -1
- package/dist/codex/session/bridge.js +3 -1
- package/dist/codex/session/bridge.js.map +1 -1
- package/dist/codex/session/build.d.ts.map +1 -1
- package/dist/codex/session/build.js +14 -12
- package/dist/codex/session/build.js.map +1 -1
- package/dist/codex/session/cleanup.d.ts +2 -2
- package/dist/codex/session/cleanup.d.ts.map +1 -1
- package/dist/codex/session/cleanup.js +8 -11
- package/dist/codex/session/cleanup.js.map +1 -1
- package/dist/codex/session/config-switch.d.ts +20 -0
- package/dist/codex/session/config-switch.d.ts.map +1 -0
- package/dist/codex/session/config-switch.js +42 -0
- package/dist/codex/session/config-switch.js.map +1 -0
- package/dist/codex/session/hooks.d.ts.map +1 -1
- package/dist/codex/session/hooks.js +7 -0
- package/dist/codex/session/hooks.js.map +1 -1
- package/dist/codex/session/index.d.ts.map +1 -1
- package/dist/codex/session/index.js +34 -16
- package/dist/codex/session/index.js.map +1 -1
- package/dist/codex/session/instance.d.ts +10 -12
- package/dist/codex/session/instance.d.ts.map +1 -1
- package/dist/codex/session/instance.js +42 -37
- package/dist/codex/session/instance.js.map +1 -1
- package/dist/codex/session/readiness.d.ts +11 -0
- package/dist/codex/session/readiness.d.ts.map +1 -0
- package/dist/codex/session/readiness.js +22 -0
- package/dist/codex/session/readiness.js.map +1 -0
- package/dist/codex/session/resume.d.ts.map +1 -1
- package/dist/codex/session/resume.js +9 -4
- package/dist/codex/session/resume.js.map +1 -1
- package/dist/codex/session/transcript.d.ts.map +1 -1
- package/dist/codex/session/transcript.js +12 -1
- package/dist/codex/session/transcript.js.map +1 -1
- package/dist/codex/session/turn-commit.d.ts +58 -0
- package/dist/codex/session/turn-commit.d.ts.map +1 -0
- package/dist/codex/session/turn-commit.js +97 -0
- package/dist/codex/session/turn-commit.js.map +1 -0
- package/dist/codex/session/warnings.d.ts +13 -6
- package/dist/codex/session/warnings.d.ts.map +1 -1
- package/dist/codex/session/warnings.js +36 -9
- package/dist/codex/session/warnings.js.map +1 -1
- package/dist/codex/startup-prompts.d.ts +3 -0
- package/dist/codex/startup-prompts.d.ts.map +1 -1
- package/dist/codex/startup-prompts.js +20 -13
- package/dist/codex/startup-prompts.js.map +1 -1
- package/dist/codex/startup-warnings.d.ts +2 -0
- package/dist/codex/startup-warnings.d.ts.map +1 -1
- package/dist/codex/startup-warnings.js +6 -0
- package/dist/codex/startup-warnings.js.map +1 -1
- package/dist/codex/transcript/payload.d.ts +8 -0
- package/dist/codex/transcript/payload.d.ts.map +1 -0
- package/dist/codex/transcript/payload.js +11 -0
- package/dist/codex/transcript/payload.js.map +1 -0
- package/dist/codex/transcript/rejection.d.ts +15 -0
- package/dist/codex/transcript/rejection.d.ts.map +1 -0
- package/dist/codex/transcript/rejection.js +44 -0
- package/dist/codex/transcript/rejection.js.map +1 -0
- package/dist/codex/transcript/turn-end.d.ts +8 -0
- package/dist/codex/transcript/turn-end.d.ts.map +1 -0
- package/dist/codex/transcript/turn-end.js +17 -0
- package/dist/codex/transcript/turn-end.js.map +1 -0
- package/dist/codex/transcript/watcher.d.ts +5 -2
- package/dist/codex/transcript/watcher.d.ts.map +1 -1
- package/dist/codex/transcript/watcher.js +11 -6
- package/dist/codex/transcript/watcher.js.map +1 -1
- package/dist/codex/trust-retry.d.ts +12 -0
- package/dist/codex/trust-retry.d.ts.map +1 -0
- package/dist/codex/trust-retry.js +12 -0
- package/dist/codex/trust-retry.js.map +1 -0
- package/dist/codex/update/skip-attempt.d.ts +2 -0
- package/dist/codex/update/skip-attempt.d.ts.map +1 -1
- package/dist/codex/update/skip-attempt.js +4 -0
- package/dist/codex/update/skip-attempt.js.map +1 -1
- package/dist/codex/update/tracker.d.ts +2 -0
- package/dist/codex/update/tracker.d.ts.map +1 -1
- package/dist/codex/update/tracker.js +4 -0
- package/dist/codex/update/tracker.js.map +1 -1
- package/dist/core/delay.d.ts +3 -2
- package/dist/core/delay.d.ts.map +1 -1
- package/dist/core/delay.js +3 -2
- package/dist/core/delay.js.map +1 -1
- package/dist/core/errors.d.ts +33 -4
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +51 -7
- package/dist/core/errors.js.map +1 -1
- package/dist/core/images/materialize.d.ts +13 -4
- package/dist/core/images/materialize.d.ts.map +1 -1
- package/dist/core/images/materialize.js +48 -11
- package/dist/core/images/materialize.js.map +1 -1
- package/dist/core/input/abort.d.ts +15 -0
- package/dist/core/input/abort.d.ts.map +1 -1
- package/dist/core/input/abort.js +18 -2
- package/dist/core/input/abort.js.map +1 -1
- package/dist/core/input/constants.d.ts +4 -1
- package/dist/core/input/constants.d.ts.map +1 -1
- package/dist/core/input/constants.js +4 -1
- package/dist/core/input/constants.js.map +1 -1
- package/dist/core/input/index.d.ts +1 -1
- package/dist/core/input/index.d.ts.map +1 -1
- package/dist/core/input/index.js +1 -1
- package/dist/core/input/index.js.map +1 -1
- package/dist/core/models/clearance.d.ts +38 -0
- package/dist/core/models/clearance.d.ts.map +1 -0
- package/dist/core/models/clearance.js +38 -0
- package/dist/core/models/clearance.js.map +1 -0
- package/dist/core/models/close-wait.d.ts +17 -0
- package/dist/core/models/close-wait.d.ts.map +1 -0
- package/dist/core/models/close-wait.js +44 -0
- package/dist/core/models/close-wait.js.map +1 -0
- package/dist/core/models/dialog-region.d.ts +18 -3
- package/dist/core/models/dialog-region.d.ts.map +1 -1
- package/dist/core/models/dialog-region.js +29 -40
- package/dist/core/models/dialog-region.js.map +1 -1
- package/dist/core/models/input.d.ts +10 -1
- package/dist/core/models/input.d.ts.map +1 -1
- package/dist/core/models/input.js +15 -2
- package/dist/core/models/input.js.map +1 -1
- package/dist/core/models/intervention.d.ts +2 -2
- package/dist/core/models/intervention.d.ts.map +1 -1
- package/dist/core/models/intervention.js +6 -3
- package/dist/core/models/intervention.js.map +1 -1
- package/dist/core/models/own-picker.d.ts +21 -0
- package/dist/core/models/own-picker.d.ts.map +1 -0
- package/dist/core/models/own-picker.js +21 -0
- package/dist/core/models/own-picker.js.map +1 -0
- package/dist/core/models/picker.d.ts +34 -4
- package/dist/core/models/picker.d.ts.map +1 -1
- package/dist/core/models/picker.js +50 -44
- package/dist/core/models/picker.js.map +1 -1
- package/dist/core/models/row-block.d.ts +66 -0
- package/dist/core/models/row-block.d.ts.map +1 -0
- package/dist/core/models/row-block.js +87 -0
- package/dist/core/models/row-block.js.map +1 -0
- package/dist/core/models/rows.d.ts +32 -3
- package/dist/core/models/rows.d.ts.map +1 -1
- package/dist/core/models/rows.js +48 -20
- package/dist/core/models/rows.js.map +1 -1
- package/dist/core/models/tui-screen.d.ts +6 -1
- package/dist/core/models/tui-screen.d.ts.map +1 -1
- package/dist/core/models/tui-screen.js +25 -10
- package/dist/core/models/tui-screen.js.map +1 -1
- package/dist/core/models/window.d.ts +49 -0
- package/dist/core/models/window.d.ts.map +1 -0
- package/dist/core/models/window.js +105 -0
- package/dist/core/models/window.js.map +1 -0
- package/dist/core/path-trust.d.ts +11 -0
- package/dist/core/path-trust.d.ts.map +1 -0
- package/dist/core/path-trust.js +21 -0
- package/dist/core/path-trust.js.map +1 -0
- package/dist/core/screen-facts.d.ts +3 -1
- package/dist/core/screen-facts.d.ts.map +1 -1
- package/dist/core/screen-facts.js +1 -0
- package/dist/core/screen-facts.js.map +1 -1
- package/dist/core/simple/boundary-signal.d.ts +41 -6
- package/dist/core/simple/boundary-signal.d.ts.map +1 -1
- package/dist/core/simple/boundary-signal.js +43 -3
- package/dist/core/simple/boundary-signal.js.map +1 -1
- package/dist/core/simple/close.d.ts +2 -1
- package/dist/core/simple/close.d.ts.map +1 -1
- package/dist/core/simple/close.js +10 -2
- package/dist/core/simple/close.js.map +1 -1
- package/dist/core/simple/turn-boundary.d.ts +2 -0
- package/dist/core/simple/turn-boundary.d.ts.map +1 -1
- package/dist/core/simple/turn-boundary.js +1 -1
- package/dist/core/simple/turn-boundary.js.map +1 -1
- package/dist/core/simple/turn-types.d.ts +9 -3
- package/dist/core/simple/turn-types.d.ts.map +1 -1
- package/dist/core/simple/turn-types.js +10 -2
- package/dist/core/simple/turn-types.js.map +1 -1
- package/dist/core/simple/turn.d.ts +2 -0
- package/dist/core/simple/turn.d.ts.map +1 -1
- package/dist/core/simple/turn.js +11 -5
- package/dist/core/simple/turn.js.map +1 -1
- package/dist/core/terminal-options.d.ts +17 -1
- package/dist/core/terminal-options.d.ts.map +1 -1
- package/dist/core/terminal-options.js +20 -8
- package/dist/core/terminal-options.js.map +1 -1
- package/dist/core/transcript/cursor-io.d.ts +8 -0
- package/dist/core/transcript/cursor-io.d.ts.map +1 -1
- package/dist/core/transcript/cursor-io.js +11 -5
- package/dist/core/transcript/cursor-io.js.map +1 -1
- package/dist/core/transcript/cursor.d.ts +28 -4
- package/dist/core/transcript/cursor.d.ts.map +1 -1
- package/dist/core/transcript/cursor.js +53 -7
- package/dist/core/transcript/cursor.js.map +1 -1
- package/dist/core/transcript/drain.d.ts +43 -0
- package/dist/core/transcript/drain.d.ts.map +1 -0
- package/dist/core/transcript/drain.js +67 -0
- package/dist/core/transcript/drain.js.map +1 -0
- package/dist/core/trust/abortable.d.ts +12 -0
- package/dist/core/trust/abortable.d.ts.map +1 -0
- package/dist/core/trust/abortable.js +48 -0
- package/dist/core/trust/abortable.js.map +1 -0
- package/dist/core/trust/allowlist.d.ts +2 -0
- package/dist/core/trust/allowlist.d.ts.map +1 -1
- package/dist/core/trust/allowlist.js +4 -1
- package/dist/core/trust/allowlist.js.map +1 -1
- package/dist/core/trust/blocking.d.ts +11 -0
- package/dist/core/trust/blocking.d.ts.map +1 -1
- package/dist/core/trust/blocking.js +5 -4
- package/dist/core/trust/blocking.js.map +1 -1
- package/dist/core/trust/copy.d.ts.map +1 -1
- package/dist/core/trust/copy.js +7 -1
- package/dist/core/trust/copy.js.map +1 -1
- package/dist/core/trust/deadline.d.ts +24 -0
- package/dist/core/trust/deadline.d.ts.map +1 -0
- package/dist/core/trust/deadline.js +56 -0
- package/dist/core/trust/deadline.js.map +1 -0
- package/dist/core/trust/dialog.d.ts +11 -2
- package/dist/core/trust/dialog.d.ts.map +1 -1
- package/dist/core/trust/dialog.js +12 -3
- package/dist/core/trust/dialog.js.map +1 -1
- package/dist/core/trust/options.d.ts +5 -1
- package/dist/core/trust/options.d.ts.map +1 -1
- package/dist/core/trust/options.js +8 -2
- package/dist/core/trust/options.js.map +1 -1
- package/dist/core/trust/prompts.d.ts +3 -0
- package/dist/core/trust/prompts.d.ts.map +1 -1
- package/dist/core/trust/prompts.js +2 -1
- package/dist/core/trust/prompts.js.map +1 -1
- package/dist/core/trust/responder.d.ts +10 -5
- package/dist/core/trust/responder.d.ts.map +1 -1
- package/dist/core/trust/responder.js +37 -33
- package/dist/core/trust/responder.js.map +1 -1
- package/dist/core/trust/step.d.ts +67 -0
- package/dist/core/trust/step.d.ts.map +1 -0
- package/dist/core/trust/step.js +45 -0
- package/dist/core/trust/step.js.map +1 -0
- package/dist/core/trust/types.d.ts +21 -1
- package/dist/core/trust/types.d.ts.map +1 -1
- package/dist/core/trust/types.js +25 -1
- package/dist/core/trust/types.js.map +1 -1
- package/dist/core/trust/view.d.ts +2 -0
- package/dist/core/trust/view.d.ts.map +1 -1
- package/dist/core/trust/view.js +3 -0
- package/dist/core/trust/view.js.map +1 -1
- package/dist/core/trust/write.d.ts +31 -6
- package/dist/core/trust/write.d.ts.map +1 -1
- package/dist/core/trust/write.js +95 -80
- package/dist/core/trust/write.js.map +1 -1
- package/dist/core/turn-state.d.ts +18 -4
- package/dist/core/turn-state.d.ts.map +1 -1
- package/dist/core/turn-state.js +47 -25
- package/dist/core/turn-state.js.map +1 -1
- package/dist/core/warnings/images.d.ts +22 -0
- package/dist/core/warnings/images.d.ts.map +1 -0
- package/dist/core/warnings/images.js +3 -0
- package/dist/core/warnings/images.js.map +1 -0
- package/dist/core/warnings/index.d.ts +3 -2
- package/dist/core/warnings/index.d.ts.map +1 -1
- package/dist/core/warnings/lifecycle.d.ts +35 -4
- package/dist/core/warnings/lifecycle.d.ts.map +1 -1
- package/dist/core/warnings/lifecycle.js +1 -1
- package/dist/core/warnings/update.d.ts +8 -4
- package/dist/core/warnings/update.d.ts.map +1 -1
- package/dist/core/warnings/update.js +21 -6
- package/dist/core/warnings/update.js.map +1 -1
- package/dist/pty/exit-latch.d.ts +28 -0
- package/dist/pty/exit-latch.d.ts.map +1 -0
- package/dist/pty/exit-latch.js +50 -0
- package/dist/pty/exit-latch.js.map +1 -0
- package/dist/runtime/exit-status.d.ts +7 -0
- package/dist/runtime/exit-status.d.ts.map +1 -0
- package/dist/runtime/exit-status.js +13 -0
- package/dist/runtime/exit-status.js.map +1 -0
- package/dist/runtime/home-guard/index.d.ts +45 -0
- package/dist/runtime/home-guard/index.d.ts.map +1 -0
- package/dist/runtime/home-guard/index.js +69 -0
- package/dist/runtime/home-guard/index.js.map +1 -0
- package/dist/runtime/loop-restore.d.ts +9 -9
- package/dist/runtime/loop-restore.d.ts.map +1 -1
- package/dist/runtime/loop-restore.js +10 -10
- package/dist/runtime/loop-restore.js.map +1 -1
- package/dist/runtime/probe/capped-buffer.d.ts +16 -0
- package/dist/runtime/probe/capped-buffer.d.ts.map +1 -0
- package/dist/runtime/probe/capped-buffer.js +38 -0
- package/dist/runtime/probe/capped-buffer.js.map +1 -0
- package/dist/runtime/probe/capture.d.ts +55 -0
- package/dist/runtime/probe/capture.d.ts.map +1 -0
- package/dist/runtime/probe/capture.js +71 -0
- package/dist/runtime/probe/capture.js.map +1 -0
- package/dist/runtime/{probe-cleanup.d.ts → probe/cleanup.d.ts} +2 -2
- package/dist/runtime/probe/cleanup.d.ts.map +1 -0
- package/dist/runtime/{probe-cleanup.js → probe/cleanup.js} +3 -3
- package/dist/runtime/probe/cleanup.js.map +1 -0
- package/dist/runtime/probe/directory-key.d.ts +26 -0
- package/dist/runtime/probe/directory-key.d.ts.map +1 -0
- package/dist/runtime/probe/directory-key.js +46 -0
- package/dist/runtime/probe/directory-key.js.map +1 -0
- package/dist/runtime/probe/index.d.ts +25 -0
- package/dist/runtime/probe/index.d.ts.map +1 -0
- package/dist/runtime/probe/index.js +153 -0
- package/dist/runtime/probe/index.js.map +1 -0
- package/dist/runtime/probe/lookup.d.ts +12 -0
- package/dist/runtime/probe/lookup.d.ts.map +1 -0
- package/dist/runtime/probe/lookup.js +29 -0
- package/dist/runtime/probe/lookup.js.map +1 -0
- package/dist/runtime/probe/named-pipes.d.ts +35 -0
- package/dist/runtime/probe/named-pipes.d.ts.map +1 -0
- package/dist/runtime/probe/named-pipes.js +167 -0
- package/dist/runtime/probe/named-pipes.js.map +1 -0
- package/dist/runtime/probe/private-directory.d.ts +14 -0
- package/dist/runtime/probe/private-directory.d.ts.map +1 -0
- package/dist/runtime/probe/private-directory.js +50 -0
- package/dist/runtime/probe/private-directory.js.map +1 -0
- package/dist/runtime/probe/results.d.ts +14 -0
- package/dist/runtime/probe/results.d.ts.map +1 -0
- package/dist/runtime/probe/results.js +38 -0
- package/dist/runtime/probe/results.js.map +1 -0
- package/dist/runtime/probe/slots.d.ts +17 -0
- package/dist/runtime/probe/slots.d.ts.map +1 -0
- package/dist/runtime/probe/slots.js +35 -0
- package/dist/runtime/probe/slots.js.map +1 -0
- package/dist/runtime/probe/start.d.ts +64 -0
- package/dist/runtime/probe/start.d.ts.map +1 -0
- package/dist/runtime/probe/start.js +80 -0
- package/dist/runtime/probe/start.js.map +1 -0
- package/dist/runtime/probe-cache.d.ts +19 -0
- package/dist/runtime/probe-cache.d.ts.map +1 -0
- package/dist/runtime/probe-cache.js +56 -0
- package/dist/runtime/probe-cache.js.map +1 -0
- package/dist/runtime/readiness/initial-ready.d.ts +31 -5
- package/dist/runtime/readiness/initial-ready.d.ts.map +1 -1
- package/dist/runtime/readiness/initial-ready.js +64 -18
- package/dist/runtime/readiness/initial-ready.js.map +1 -1
- package/dist/runtime/seams.d.ts +14 -2
- package/dist/runtime/seams.d.ts.map +1 -1
- package/dist/runtime/seams.js +2 -2
- package/dist/runtime/seams.js.map +1 -1
- package/dist/runtime/session/base.d.ts.map +1 -1
- package/dist/runtime/session/base.js +6 -5
- package/dist/runtime/session/base.js.map +1 -1
- package/dist/runtime/session/commands.d.ts +5 -3
- package/dist/runtime/session/commands.d.ts.map +1 -1
- package/dist/runtime/session/commands.js +5 -2
- package/dist/runtime/session/commands.js.map +1 -1
- package/dist/runtime/session/frames.d.ts +12 -2
- package/dist/runtime/session/frames.d.ts.map +1 -1
- package/dist/runtime/session/frames.js +10 -3
- package/dist/runtime/session/frames.js.map +1 -1
- package/dist/runtime/session/image-attach.js +7 -2
- package/dist/runtime/session/image-attach.js.map +1 -1
- package/dist/runtime/session/launch-directory.d.ts +33 -0
- package/dist/runtime/session/launch-directory.d.ts.map +1 -0
- package/dist/runtime/session/launch-directory.js +84 -0
- package/dist/runtime/session/launch-directory.js.map +1 -0
- package/dist/runtime/session/lifecycle.d.ts +3 -3
- package/dist/runtime/session/lifecycle.d.ts.map +1 -1
- package/dist/runtime/session/lifecycle.js +1 -5
- package/dist/runtime/session/lifecycle.js.map +1 -1
- package/dist/runtime/session/loops.d.ts +1 -1
- package/dist/runtime/session/loops.d.ts.map +1 -1
- package/dist/runtime/session/loops.js +6 -3
- package/dist/runtime/session/loops.js.map +1 -1
- package/dist/runtime/session/picker-cleanup.d.ts +6 -21
- package/dist/runtime/session/picker-cleanup.d.ts.map +1 -1
- package/dist/runtime/session/picker-cleanup.js +19 -31
- package/dist/runtime/session/picker-cleanup.js.map +1 -1
- package/dist/runtime/session/picker-hold.d.ts +24 -0
- package/dist/runtime/session/picker-hold.d.ts.map +1 -0
- package/dist/runtime/session/picker-hold.js +42 -0
- package/dist/runtime/session/picker-hold.js.map +1 -0
- package/dist/runtime/session/picker.d.ts +13 -11
- package/dist/runtime/session/picker.d.ts.map +1 -1
- package/dist/runtime/session/picker.js +46 -24
- package/dist/runtime/session/picker.js.map +1 -1
- package/dist/runtime/session/prepare-state.d.ts +6 -0
- package/dist/runtime/session/prepare-state.d.ts.map +1 -0
- package/dist/runtime/session/prepare-state.js +14 -0
- package/dist/runtime/session/prepare-state.js.map +1 -0
- package/dist/runtime/session/questions.d.ts +2 -2
- package/dist/runtime/session/questions.d.ts.map +1 -1
- package/dist/runtime/session/questions.js.map +1 -1
- package/dist/runtime/session/readiness.d.ts +14 -5
- package/dist/runtime/session/readiness.d.ts.map +1 -1
- package/dist/runtime/session/readiness.js +22 -6
- package/dist/runtime/session/readiness.js.map +1 -1
- package/dist/runtime/session/reap.d.ts +11 -7
- package/dist/runtime/session/reap.d.ts.map +1 -1
- package/dist/runtime/session/reap.js +16 -10
- package/dist/runtime/session/reap.js.map +1 -1
- package/dist/runtime/session/runtime-cleanup.d.ts +18 -0
- package/dist/runtime/session/runtime-cleanup.d.ts.map +1 -0
- package/dist/runtime/session/runtime-cleanup.js +18 -0
- package/dist/runtime/session/runtime-cleanup.js.map +1 -0
- package/dist/runtime/session/session-input.d.ts +32 -0
- package/dist/runtime/session/session-input.d.ts.map +1 -0
- package/dist/runtime/session/{picker-input.js → session-input.js} +35 -7
- package/dist/runtime/session/session-input.js.map +1 -0
- package/dist/runtime/session/shutdown-binding.d.ts +12 -1
- package/dist/runtime/session/shutdown-binding.d.ts.map +1 -1
- package/dist/runtime/session/shutdown-binding.js +33 -1
- package/dist/runtime/session/shutdown-binding.js.map +1 -1
- package/dist/runtime/session/shutdown-cleanup.d.ts +17 -0
- package/dist/runtime/session/shutdown-cleanup.d.ts.map +1 -0
- package/dist/runtime/session/shutdown-cleanup.js +49 -0
- package/dist/runtime/session/shutdown-cleanup.js.map +1 -0
- package/dist/runtime/session/shutdown.d.ts +3 -2
- package/dist/runtime/session/shutdown.d.ts.map +1 -1
- package/dist/runtime/session/shutdown.js +13 -24
- package/dist/runtime/session/shutdown.js.map +1 -1
- package/dist/runtime/shell-quote.d.ts +8 -0
- package/dist/runtime/shell-quote.d.ts.map +1 -0
- package/dist/runtime/shell-quote.js +10 -0
- package/dist/runtime/shell-quote.js.map +1 -0
- package/dist/runtime/shell.d.ts +36 -12
- package/dist/runtime/shell.d.ts.map +1 -1
- package/dist/runtime/shell.js +90 -14
- package/dist/runtime/shell.js.map +1 -1
- package/dist/runtime/shutdown/coalesce.d.ts +15 -0
- package/dist/runtime/shutdown/coalesce.d.ts.map +1 -0
- package/dist/runtime/shutdown/coalesce.js +42 -0
- package/dist/runtime/shutdown/coalesce.js.map +1 -0
- package/dist/runtime/shutdown/detached-sweep/failures.d.ts +24 -0
- package/dist/runtime/shutdown/detached-sweep/failures.d.ts.map +1 -0
- package/dist/runtime/shutdown/detached-sweep/failures.js +32 -0
- package/dist/runtime/shutdown/detached-sweep/failures.js.map +1 -0
- package/dist/runtime/shutdown/detached-sweep/index.d.ts +55 -0
- package/dist/runtime/shutdown/detached-sweep/index.d.ts.map +1 -0
- package/dist/runtime/shutdown/detached-sweep/index.js +129 -0
- package/dist/runtime/shutdown/detached-sweep/index.js.map +1 -0
- package/dist/runtime/shutdown/detached-sweep/kill.d.ts +37 -0
- package/dist/runtime/shutdown/detached-sweep/kill.d.ts.map +1 -0
- package/dist/runtime/shutdown/detached-sweep/kill.js +87 -0
- package/dist/runtime/shutdown/detached-sweep/kill.js.map +1 -0
- package/dist/runtime/shutdown/detached-sweep/record.d.ts +67 -0
- package/dist/runtime/shutdown/detached-sweep/record.d.ts.map +1 -0
- package/dist/runtime/shutdown/detached-sweep/record.js +111 -0
- package/dist/runtime/shutdown/detached-sweep/record.js.map +1 -0
- package/dist/runtime/shutdown/detached-sweep/rounds.d.ts +78 -0
- package/dist/runtime/shutdown/detached-sweep/rounds.d.ts.map +1 -0
- package/dist/runtime/shutdown/detached-sweep/rounds.js +116 -0
- package/dist/runtime/shutdown/detached-sweep/rounds.js.map +1 -0
- package/dist/runtime/shutdown/detached-sweep/survivors.d.ts +40 -0
- package/dist/runtime/shutdown/detached-sweep/survivors.d.ts.map +1 -0
- package/dist/runtime/shutdown/detached-sweep/survivors.js +112 -0
- package/dist/runtime/shutdown/detached-sweep/survivors.js.map +1 -0
- package/dist/runtime/shutdown/leader-group.d.ts +29 -0
- package/dist/runtime/shutdown/leader-group.d.ts.map +1 -0
- package/dist/runtime/shutdown/leader-group.js +35 -0
- package/dist/runtime/shutdown/leader-group.js.map +1 -0
- package/dist/runtime/shutdown/native-read-admission.d.ts +13 -0
- package/dist/runtime/shutdown/native-read-admission.d.ts.map +1 -0
- package/dist/runtime/shutdown/native-read-admission.js +64 -0
- package/dist/runtime/shutdown/native-read-admission.js.map +1 -0
- package/dist/runtime/shutdown/process-table.d.ts +80 -0
- package/dist/runtime/shutdown/process-table.d.ts.map +1 -0
- package/dist/runtime/shutdown/process-table.js +147 -0
- package/dist/runtime/shutdown/process-table.js.map +1 -0
- package/dist/runtime/shutdown/process-tree-ops.d.ts +39 -0
- package/dist/runtime/shutdown/process-tree-ops.d.ts.map +1 -0
- package/dist/runtime/shutdown/process-tree-ops.js +73 -0
- package/dist/runtime/shutdown/process-tree-ops.js.map +1 -0
- package/dist/runtime/shutdown/read-timeout.d.ts +4 -0
- package/dist/runtime/shutdown/read-timeout.d.ts.map +1 -0
- package/dist/runtime/shutdown/read-timeout.js +6 -0
- package/dist/runtime/shutdown/read-timeout.js.map +1 -0
- package/dist/runtime/shutdown/reap-queue.d.ts +10 -0
- package/dist/runtime/shutdown/reap-queue.d.ts.map +1 -0
- package/dist/runtime/shutdown/reap-queue.js +29 -0
- package/dist/runtime/shutdown/reap-queue.js.map +1 -0
- package/dist/runtime/shutdown/reap-tree.d.ts +42 -32
- package/dist/runtime/shutdown/reap-tree.d.ts.map +1 -1
- package/dist/runtime/shutdown/reap-tree.js +78 -71
- package/dist/runtime/shutdown/reap-tree.js.map +1 -1
- package/dist/runtime/shutdown/shared-daemon.d.ts +59 -0
- package/dist/runtime/shutdown/shared-daemon.d.ts.map +1 -0
- package/dist/runtime/shutdown/shared-daemon.js +139 -0
- package/dist/runtime/shutdown/shared-daemon.js.map +1 -0
- package/dist/runtime/shutdown/teardown.d.ts +15 -0
- package/dist/runtime/shutdown/teardown.d.ts.map +1 -1
- package/dist/runtime/shutdown/teardown.js +19 -6
- package/dist/runtime/shutdown/teardown.js.map +1 -1
- package/dist/runtime/shutdown/terminate.d.ts +21 -1
- package/dist/runtime/shutdown/terminate.d.ts.map +1 -1
- package/dist/runtime/shutdown/terminate.js +83 -56
- package/dist/runtime/shutdown/terminate.js.map +1 -1
- package/dist/runtime/startup/buffer.d.ts +26 -7
- package/dist/runtime/startup/buffer.d.ts.map +1 -1
- package/dist/runtime/startup/buffer.js +42 -19
- package/dist/runtime/startup/buffer.js.map +1 -1
- package/dist/runtime/startup/cleanup.d.ts +37 -11
- package/dist/runtime/startup/cleanup.d.ts.map +1 -1
- package/dist/runtime/startup/cleanup.js +55 -25
- package/dist/runtime/startup/cleanup.js.map +1 -1
- package/dist/runtime/startup/index.d.ts +13 -1
- package/dist/runtime/startup/index.d.ts.map +1 -1
- package/dist/runtime/startup/index.js +38 -6
- package/dist/runtime/startup/index.js.map +1 -1
- package/dist/runtime/stream-marker.d.ts +17 -0
- package/dist/runtime/stream-marker.d.ts.map +1 -0
- package/dist/runtime/stream-marker.js +35 -0
- package/dist/runtime/stream-marker.js.map +1 -0
- package/dist/runtime/update/acquire.d.ts +16 -0
- package/dist/runtime/update/acquire.d.ts.map +1 -0
- package/dist/runtime/update/acquire.js +62 -0
- package/dist/runtime/update/acquire.js.map +1 -0
- package/dist/runtime/update/completion.d.ts +12 -0
- package/dist/runtime/update/completion.d.ts.map +1 -0
- package/dist/runtime/update/completion.js +50 -0
- package/dist/runtime/update/completion.js.map +1 -0
- package/dist/runtime/update/lock.d.ts +1 -1
- package/dist/runtime/update/lock.d.ts.map +1 -1
- package/dist/runtime/update/lock.js +21 -38
- package/dist/runtime/update/lock.js.map +1 -1
- package/dist/runtime/update/once.d.ts +16 -13
- package/dist/runtime/update/once.d.ts.map +1 -1
- package/dist/runtime/update/once.js +41 -31
- package/dist/runtime/update/once.js.map +1 -1
- package/dist/runtime/update/owner.js +1 -1
- package/dist/runtime/update/retained-lease.js +1 -1
- package/dist/runtime/update/waiting.d.ts +14 -1
- package/dist/runtime/update/waiting.d.ts.map +1 -1
- package/dist/runtime/update/waiting.js +20 -12
- package/dist/runtime/update/waiting.js.map +1 -1
- package/dist/state/canonical-path.d.ts +5 -0
- package/dist/state/canonical-path.d.ts.map +1 -1
- package/dist/state/canonical-path.js +11 -5
- package/dist/state/canonical-path.js.map +1 -1
- package/dist/state/directories.d.ts +31 -1
- package/dist/state/directories.d.ts.map +1 -1
- package/dist/state/directories.js +75 -13
- package/dist/state/directories.js.map +1 -1
- package/dist/state/files.d.ts +3 -3
- package/dist/state/files.d.ts.map +1 -1
- package/dist/state/files.js +4 -10
- package/dist/state/files.js.map +1 -1
- package/dist/state/launch-ownership.d.ts +4 -2
- package/dist/state/launch-ownership.d.ts.map +1 -1
- package/dist/state/launch-ownership.js +18 -10
- package/dist/state/launch-ownership.js.map +1 -1
- package/dist/state/launch-publication.d.ts +7 -1
- package/dist/state/launch-publication.d.ts.map +1 -1
- package/dist/state/launch-publication.js +14 -2
- package/dist/state/launch-publication.js.map +1 -1
- package/dist/state/loop-store.d.ts +15 -4
- package/dist/state/loop-store.d.ts.map +1 -1
- package/dist/state/loop-store.js +13 -7
- package/dist/state/loop-store.js.map +1 -1
- package/dist/state/preparation/created-directories.d.ts +13 -0
- package/dist/state/preparation/created-directories.d.ts.map +1 -0
- package/dist/state/preparation/created-directories.js +45 -0
- package/dist/state/preparation/created-directories.js.map +1 -0
- package/dist/state/preparation/ignore-sync.d.ts +9 -0
- package/dist/state/preparation/ignore-sync.d.ts.map +1 -0
- package/dist/state/preparation/ignore-sync.js +47 -0
- package/dist/state/preparation/ignore-sync.js.map +1 -0
- package/dist/state/preparation/index.d.ts +25 -0
- package/dist/state/preparation/index.d.ts.map +1 -0
- package/dist/state/preparation/index.js +31 -0
- package/dist/state/preparation/index.js.map +1 -0
- package/dist/state/preparation/install-ignore.d.ts +6 -0
- package/dist/state/preparation/install-ignore.d.ts.map +1 -0
- package/dist/state/preparation/install-ignore.js +87 -0
- package/dist/state/preparation/install-ignore.js.map +1 -0
- package/dist/state/preparation/rollback-parent.d.ts +3 -0
- package/dist/state/preparation/rollback-parent.d.ts.map +1 -0
- package/dist/state/preparation/rollback-parent.js +18 -0
- package/dist/state/preparation/rollback-parent.js.map +1 -0
- package/dist/state/preparation/root-trust.d.ts +3 -0
- package/dist/state/preparation/root-trust.d.ts.map +1 -0
- package/dist/state/preparation/root-trust.js +70 -0
- package/dist/state/preparation/root-trust.js.map +1 -0
- package/dist/state/private-session.d.ts +5 -2
- package/dist/state/private-session.d.ts.map +1 -1
- package/dist/state/private-session.js +21 -9
- package/dist/state/private-session.js.map +1 -1
- package/dist/state/root.d.ts +25 -0
- package/dist/state/root.d.ts.map +1 -0
- package/dist/state/root.js +84 -0
- package/dist/state/root.js.map +1 -0
- package/dist/state/runtime-paths.d.ts +5 -4
- package/dist/state/runtime-paths.d.ts.map +1 -1
- package/dist/state/runtime-paths.js +5 -4
- package/dist/state/runtime-paths.js.map +1 -1
- package/dist/state/setup-failure.d.ts +3 -0
- package/dist/state/setup-failure.d.ts.map +1 -0
- package/dist/state/setup-failure.js +13 -0
- package/dist/state/setup-failure.js.map +1 -0
- package/dist/state/socket-home.d.ts +17 -12
- package/dist/state/socket-home.d.ts.map +1 -1
- package/dist/state/socket-home.js +31 -14
- package/dist/state/socket-home.js.map +1 -1
- package/dist/state/store.d.ts +1 -3
- package/dist/state/store.d.ts.map +1 -1
- package/dist/state/store.js +3 -18
- package/dist/state/store.js.map +1 -1
- package/dist/terminal/headless.d.ts +1 -1
- package/dist/terminal/headless.d.ts.map +1 -1
- package/dist/terminal/headless.js +1 -3
- package/dist/terminal/headless.js.map +1 -1
- package/docs/cli-behavior.md +1218 -28
- package/docs/cli.md +2 -0
- package/docs/codex/cli.md +4 -2
- package/package.json +4 -4
- package/prd/04-architecture.md +32 -0
- package/prd/05-api/5.1-starting-claude.md +17 -1
- package/prd/05-api/5.2-resuming-claude.md +5 -0
- package/prd/05-api/5.3-claude-session-api.md +267 -40
- package/prd/05-api/5.4-events.md +77 -10
- package/prd/05-api/5.5-starting-codex.md +3 -1
- package/prd/05-api/5.6-resuming-codex.md +4 -1
- package/prd/05-api/5.7-codex-session-api.md +87 -5
- package/prd/05-api/5.8-session-api.md +32 -1
- package/prd/06-claude-hook-bridge.md +2 -0
- package/prd/07a-codex-hook-bridge.md +5 -0
- package/prd/08-state.md +63 -7
- package/prd/09-lifecycle.md +126 -35
- package/prd/10-errors.md +11 -4
- package/prd/12-e2e.md +81 -0
- package/prd/12a-cli.md +63 -9
- package/prd/14-conformance.md +52 -35
- package/prd/16-maintainer-review.md +11 -0
- package/dist/claude/model-composer.d.ts +0 -2
- package/dist/claude/model-composer.d.ts.map +0 -1
- package/dist/claude/model-composer.js +0 -12
- package/dist/claude/model-composer.js.map +0 -1
- package/dist/claude/transcript/drain.d.ts +0 -33
- package/dist/claude/transcript/drain.d.ts.map +0 -1
- package/dist/claude/transcript/drain.js +0 -56
- package/dist/claude/transcript/drain.js.map +0 -1
- package/dist/codex/session/cli-exit.d.ts +0 -24
- package/dist/codex/session/cli-exit.d.ts.map +0 -1
- package/dist/codex/session/cli-exit.js +0 -43
- package/dist/codex/session/cli-exit.js.map +0 -1
- package/dist/codex/transcript/drain.d.ts +0 -30
- package/dist/codex/transcript/drain.d.ts.map +0 -1
- package/dist/codex/transcript/drain.js +0 -48
- package/dist/codex/transcript/drain.js.map +0 -1
- package/dist/runtime/probe-cleanup.d.ts.map +0 -1
- package/dist/runtime/probe-cleanup.js.map +0 -1
- package/dist/runtime/probe.d.ts +0 -11
- package/dist/runtime/probe.d.ts.map +0 -1
- package/dist/runtime/probe.js +0 -113
- package/dist/runtime/probe.js.map +0 -1
- package/dist/runtime/session/foreign-picker.d.ts +0 -8
- package/dist/runtime/session/foreign-picker.d.ts.map +0 -1
- package/dist/runtime/session/foreign-picker.js +0 -18
- package/dist/runtime/session/foreign-picker.js.map +0 -1
- package/dist/runtime/session/picker-input.d.ts +0 -17
- package/dist/runtime/session/picker-input.d.ts.map +0 -1
- package/dist/runtime/session/picker-input.js.map +0 -1
package/docs/cli-behavior.md
CHANGED
|
@@ -55,8 +55,28 @@ invalid and will conclude the hook "never fires". (This once produced a false
|
|
|
55
55
|
sandboxed homes, and user `config.toml` hooks merge additively with `-c` hooks.)
|
|
56
56
|
Because `SessionStart` is Elwood's authoritative Codex readiness signal
|
|
57
57
|
(`markInitialReadyFromHook`, `src/codex/session/hooks.ts`), cold-start readiness
|
|
58
|
-
comes from that hook, bounded by a **
|
|
59
|
-
first render frame (`src/runtime/readiness/initial-ready.ts
|
|
58
|
+
comes from that hook, bounded by a **two-stage starvation deadline** armed on the
|
|
59
|
+
first render frame (`src/runtime/readiness/initial-ready.ts`): after 10 s
|
|
60
|
+
(`initialDeadlineMs`) Codex readiness waits for a configured composer, described
|
|
61
|
+
below, and after 60 s (`initialBackstopMs`) it is released without one. C-API-28.
|
|
62
|
+
|
|
63
|
+
**Codex 0.164 paints a boot composer before its session is configured
|
|
64
|
+
(2026-10-25).** About 0.6 s after its first output Codex draws `› Ask Codex to do
|
|
65
|
+
anything` and `? for shortcuts` with no model footer; the header may read
|
|
66
|
+
`loading` or already show the path. The model footer (`GPT-6.1-Sol default ·
|
|
67
|
+
<path>`) appears only once the session is configured, which for an untrusted
|
|
68
|
+
folder is after the directory-trust gate is answered. On a loaded machine (load
|
|
69
|
+
average 20 to 127, 8 concurrent launches with `autotrust`, fresh `git init`
|
|
70
|
+
folders) the gate painted 11.1 to 30.6 s after the first output in 24 of 32
|
|
71
|
+
launches, and every one of those 24 reported `ready` at 10.0 s, onto a screen
|
|
72
|
+
showing the boot composer without a footer. The other 8 painted their gate at
|
|
73
|
+
8.9 to 9.0 s and readied after it, on footed composers. Unloaded, the gate came
|
|
74
|
+
3.4 to 3.9 s after the first output. Codex's deadline therefore marks only onto a
|
|
75
|
+
composer with its model footer (`codexComposerConfigured`,
|
|
76
|
+
`src/codex/screen/clearance.ts`), and a 60 s backstop, twice the longest measured
|
|
77
|
+
gate delay, releases readiness if no footer is ever recognized; a gate painting
|
|
78
|
+
later than that would still follow `ready`. Fixtures:
|
|
79
|
+
`tests/fixtures/codex-0.164.0/boot/`. C-API-28.
|
|
60
80
|
|
|
61
81
|
**Codex 0.153.3 can still swallow the deadline-released first paste under load.**
|
|
62
82
|
Its cold composer may accept the paste visually, repaint another startup spinner,
|
|
@@ -119,6 +139,38 @@ misattributed to a CLI regression.)
|
|
|
119
139
|
quiet frame is not proof it finished. Evidence-driven turns (a caller
|
|
120
140
|
submission, a hook) bypass settling immediately. `src/core/turn-state.ts`,
|
|
121
141
|
C-TURN-03.
|
|
142
|
+
- **Codex 0.164.0 paints a `Resuming session…` placeholder before it loads the
|
|
143
|
+
conversation (2026-10-25).** Five captured resumes in private Codex homes, at load
|
|
144
|
+
averages of 20 to 60, all had the same shape. First comes the header, then
|
|
145
|
+
`Resuming session…`, then the live composer and `? for shortcuts`. That frame
|
|
146
|
+
repaints 8 to 11 times with no visible change. Elwood latches resume readiness on
|
|
147
|
+
its first composer frame. 0.5 to 1.6 s later, Codex sets a braille title spinner
|
|
148
|
+
and `OSC 7501;state=working:app=codex`. It replaces the placeholder with the
|
|
149
|
+
replayed transcript, either in the spinner's first frame or one frame later. The
|
|
150
|
+
spinner runs 0.7 to 1.5 s. Then the title goes idle with `state=idle`, at most one
|
|
151
|
+
cursor repaint follows, and Codex sends nothing more until input arrives. The quiet streak counted the
|
|
152
|
+
placeholder's repaints and released settling before the load. The spinner then
|
|
153
|
+
became a phantom `running → ready` on every capture. The real-CLI e2e reported it
|
|
154
|
+
as `saw: running,ready`. Placeholder frames are no longer quiet. Once the
|
|
155
|
+
placeholder has painted and the load's spinner has been seen, the first quiet
|
|
156
|
+
composer ends settling. A five-frame streak never arrives from an idle Codex.
|
|
157
|
+
Captures: `tests/fixtures/codex-0.164.0/resume/`. C-TURN-03.
|
|
158
|
+
- **Codex records a turn's completion only after its `Stop` hook returns
|
|
159
|
+
(2026-10-25).** Elwood reports `ready` from that hook, so a host that stops the
|
|
160
|
+
session at that `ready` signals Codex 0.164.0 before it writes the turn's
|
|
161
|
+
`task_complete`, and Codex exits without it. Stopping in the `ready` listener lost
|
|
162
|
+
the record in 3 of 3 probe runs and in 2 of 2 runs of
|
|
163
|
+
`tests/e2e/codex-stop-commit.e2e.ts` on `main`. Stopping 66 ms later lost it too;
|
|
164
|
+
at 156 ms and at 1 s it was written. The record carries the hook's `turn_id`, and
|
|
165
|
+
its own timestamp falls 3 to 94 ms after the hook reached Elwood. Resuming such a
|
|
166
|
+
conversation replays it ending in
|
|
167
|
+
`■ Conversation interrupted - use /feedback if something went wrong`. Before the
|
|
168
|
+
placeholder fix, that banner closed the spinner's phantom turn early and a second
|
|
169
|
+
phantom followed: `saw: running,ready,running,ready`. The `uncommitted-turn`
|
|
170
|
+
capture retains that replay. `stop()` and `teardown()` now give Codex up to 2 s to
|
|
171
|
+
write the record before signaling it. With the wait, a stop at `ready` took 70 to 126 ms
|
|
172
|
+
instead of 13 to 29 ms, and the record was always written.
|
|
173
|
+
`src/codex/session/turn-commit.ts`, C-TURN-03, C-LIFE-13.
|
|
122
174
|
- **A caller submission can precede the resumed composer's repaint.** The old
|
|
123
175
|
idle composer may remain visible briefly after `caller_submitted`; that frame
|
|
124
176
|
is not a turn end. Resume settling now synchronizes to current rendered work
|
|
@@ -143,6 +195,23 @@ misattributed to a CLI regression.)
|
|
|
143
195
|
observed `◐ ` prefix alongside the existing braille range; both screen facts
|
|
144
196
|
and live trust clearance share it. No other new spinner glyph is inferred.
|
|
145
197
|
C-TURN-05, C-TRUST-01.
|
|
198
|
+
- **Claude 2.1.310 alternates its working title between `◐` and `◑`, and its
|
|
199
|
+
spinner row has no `esc to interrupt`.** Sampling the title every 50 ms through
|
|
200
|
+
real turns (2026-10-25, Opus 5.5, 189 columns) gave `✳ ◐ ◑ ◐ ✳` and
|
|
201
|
+
`✳ ◐ ◑ ◐ ◑ ◐ ✳`, each half circle lasting about a second, with `✳` while idle.
|
|
202
|
+
The spinner row reads `✢ Nebulizing… (2s)`, or
|
|
203
|
+
`✶ Computing… (running Stop hook · 8s · ↓ 3 tokens)`, and no frame of those turns
|
|
204
|
+
contained `esc to interrupt`, so the title was the only working signal. With only
|
|
205
|
+
`◐` recognized, every `◑` frame read as idle: the session reported
|
|
206
|
+
`running → ready → running` about once a second for the whole turn, eight false
|
|
207
|
+
`ready`s during one 6 s `sleep` tool call. The ergonomic turn treats an accepted
|
|
208
|
+
`ready` as the start of its 2 s quiet window, so a turn with more than 2 s between
|
|
209
|
+
a `◑` frame and its first text settled early. Six concurrent `send("Reply with
|
|
210
|
+
exactly OK…")` calls returned `""` in 5 of 6 at load 117 and 6 of 6 at load 207,
|
|
211
|
+
with Claude's `Stop` carrying `OK` 2–4 s later. Both half circles now read as
|
|
212
|
+
working (`tests/fixtures/claude-2.1.310/working-half-right.txt`): the same six
|
|
213
|
+
sends returned `OK` 6 of 6 at load 120–126 with no `ready` before `Stop`.
|
|
214
|
+
C-TURN-05, C-API-48.
|
|
146
215
|
- Both CLIs render the literal `esc to interrupt` only while a turn is running
|
|
147
216
|
(Claude footer, Codex "Working" spinner). Elwood's `TurnStateWatcher` keys off
|
|
148
217
|
this, and **arms only after initial readiness** because the Codex MCP boot
|
|
@@ -159,6 +228,64 @@ misattributed to a CLI regression.)
|
|
|
159
228
|
after done — do not treat spinner presence as a reliable per-frame liveness bit
|
|
160
229
|
at tiny sizes.
|
|
161
230
|
|
|
231
|
+
## Rejected turns (2026-10-25)
|
|
232
|
+
|
|
233
|
+
**Claude 2.1.310 ends a rejected turn with `StopFailure`, never `Stop`.** A bogus
|
|
234
|
+
`--model elwood-bogus-model` is refused before any model quota is consumed. The
|
|
235
|
+
hooks were `InstructionsLoaded`, `SessionStart` (`model: "elwood-bogus-model"`),
|
|
236
|
+
`UserPromptSubmit`, then `StopFailure` with `error: "model_not_found"`, a
|
|
237
|
+
`prompt_id` matching the submit, and `last_assistant_message` set to Claude's own
|
|
238
|
+
"There's an issue with the selected model (elwood-bogus-model)…" text. No `Stop`
|
|
239
|
+
fired. The transcript writes that same text as an `assistant` entry carrying
|
|
240
|
+
`isApiErrorMessage: true`, `error: "model_not_found"` and `message.model:
|
|
241
|
+
"<synthetic>"`, so Elwood's transcript activity surfaces it as an ordinary
|
|
242
|
+
`assistant_message`. Startup also shows `⚠ Model "elwood-bogus-model" isn't
|
|
243
|
+
recognized`, so the session reaches ready normally. Before C-API-59 the
|
|
244
|
+
no-oracle quiet window settled that turn as a success: `ClaudeSession.send`
|
|
245
|
+
resolved with the error text and `elwood run` exited 0 with it as `response`.
|
|
246
|
+
Elwood now reads `StopFailure` as rejection evidence (`claudeBoundarySignal`) and
|
|
247
|
+
fails the turn with `turn_failed`, message `Claude rejected the turn:
|
|
248
|
+
model_not_found`. The assistant text is not the diagnostic (§6.1). C-API-59,
|
|
249
|
+
C-CLI-28, C-E2E-20.
|
|
250
|
+
|
|
251
|
+
**The hook can lag the first rendered `ready` under load.** On an idle machine
|
|
252
|
+
`StopFailure` arrived in the same 0.1 s as `ready` and 0.3 s before the transcript
|
|
253
|
+
entry. With four concurrent sessions at load ~55, it lagged the first `ready` by
|
|
254
|
+
0.8, 2.0, 2.5 and 1.8 s. One run rendered a transient `ready → running → ready`
|
|
255
|
+
before the hook. The turn fails only if the hook arrives before the turn settles,
|
|
256
|
+
and a no-oracle turn settles 2 s after its last content. That is the same lag
|
|
257
|
+
that can end a successful turn early when its `Stop` is late. A hook that late can
|
|
258
|
+
also land while the NEXT turn is running. Each hook is delivered by its own bridge
|
|
259
|
+
process, so the next turn's `UserPromptSubmit` can even arrive first. Elwood
|
|
260
|
+
therefore applies a `StopFailure` only after the turn's own `UserPromptSubmit`, and
|
|
261
|
+
only when its `prompt_id` matches that submit's.
|
|
262
|
+
|
|
263
|
+
**Codex 0.164.0 rejects the same bogus model without any boundary hook.** In a
|
|
264
|
+
sandboxed `CODEX_HOME`, only `SessionStart` and `UserPromptSubmit` fired. The
|
|
265
|
+
evidence is a transcript `event_msg` `task_complete` carrying `error`, with
|
|
266
|
+
`last_agent_message: null`, as first measured on 0.155.0. The record's `turn_id`
|
|
267
|
+
(and `root_turn_id`) equal the `UserPromptSubmit` `turn_id`. `error.message` is the
|
|
268
|
+
service's raw JSON body (`{"type":"error","status":400,"error":{...,"code":
|
|
269
|
+
"model_not_found"}}`, "…model is not supported when using Codex with a ChatGPT
|
|
270
|
+
account."), and `error.codex_error_info` is `"other"`. The binary's
|
|
271
|
+
`CodexErrorInfo` kinds are bare strings (`usage_limit_exceeded`, `bad_request`,
|
|
272
|
+
`other`, …) or one-key objects for kinds with fields (`http_connection_failed`,
|
|
273
|
+
`response_stream_connection_failed`, `response_stream_disconnected`, …). The
|
|
274
|
+
screen still reached `ready` 1.4 s after the submit hook and 0.15 s before the record.
|
|
275
|
+
|
|
276
|
+
On 0.164.0 the turn binds the submit's `turn_id`, and only a private `Stop` or a
|
|
277
|
+
classified rendered completion ends it. A rejected turn has no `Stop`, so its end
|
|
278
|
+
depended on the rendered path. In the A/B runs (load 60–100), `CodexSession.send`
|
|
279
|
+
resolved `""` about 20 s after submission, and `elwood run` exited 0 with
|
|
280
|
+
`"response": ""`, the same empty success 0.155.0 showed. In one earlier probe, run
|
|
281
|
+
beside a full test suite at load ~100, `send` stayed pending for over five minutes
|
|
282
|
+
and resolved `""` only when the CLI was killed. Elwood now
|
|
283
|
+
reads a failed `task_complete` that names the accepted `turn_id` as that turn's
|
|
284
|
+
native boundary (`confirmCodexRejection`) and fails it with `turn_failed`. The
|
|
285
|
+
message is `error.message` and `details.agentError` is the `codex_error_info`
|
|
286
|
+
kind. A second turn on the same session is rejected the same way, so the first
|
|
287
|
+
released the session at its real boundary. C-API-60, C-CLI-28, C-E2E-21.
|
|
288
|
+
|
|
162
289
|
## Codex transcript replies
|
|
163
290
|
|
|
164
291
|
**Codex 0.149.1 writes committed replies as phased `message` response items, not
|
|
@@ -181,6 +308,127 @@ only their raw observation. C-CODEX-16, C-CODEX-22.
|
|
|
181
308
|
visible catalog; every returned row correctly had `isCurrent: false`.
|
|
182
309
|
Real picker tests should let the installed CLI choose its launch default,
|
|
183
310
|
rather than pinning a model that can disappear from the catalog.
|
|
311
|
+
- **Codex 0.164.0 marks only the `(current)` row; nothing on screen names the
|
|
312
|
+
default.** Probed 2026-10-25 in a sandboxed `CODEX_HOME` whose config sets
|
|
313
|
+
`model = "gpt-6.1-sol"`, with the session launched on `--model gpt-6-luna`: the
|
|
314
|
+
picker drew `› 4. GPT-6-Luna (current)` and no marker on GPT-6.1-Sol. A session
|
|
315
|
+
switch moves `(current)` to the new row, again with nothing on the launch model.
|
|
316
|
+
Older Codex drew `1. gpt-5.5 (default)` beside `2. gpt-5.4 (current)`
|
|
317
|
+
(`codexPickerSplitMarkers`), so reading "a lone `(current)` row is also the
|
|
318
|
+
default" reported the session's own model as the user's default. Elwood now reads
|
|
319
|
+
the default from `config.toml`. The cached catalog (`models_cache.json`) carries
|
|
320
|
+
no default flag, so Codex's built-in default in an unconfigured home cannot be
|
|
321
|
+
identified at all.
|
|
322
|
+
- **Claude scrolls a list longer than ten rows inside the picker (2.1.310, 2026-10-24).**
|
|
323
|
+
An account with 13 models renders a ten-row window. At the top, rows 1–9 are plain,
|
|
324
|
+
row 10 is `↓ 10. Opus 4.8 …`, and `… +3 models` stands directly under it. Moving the
|
|
325
|
+
cursor past the last visible row scrolls the window by one: the first visible row then
|
|
326
|
+
carries `↑` (`↑ 2.`), and the last carries `↓` with `… +2 models` / `… +1 model` below it
|
|
327
|
+
while any rows stay hidden; at the end of the list that line is blank. The cursor glyph
|
|
328
|
+
REPLACES a marker on its row (`❯ 11. Opus 4.7` above `… +2 models`), and moving up keeps
|
|
329
|
+
one row visible above the cursor, so arrowing from row 5 to row 4 shifts the window from
|
|
330
|
+
`↑ 4.`–`13.` to `↑ 3.`–`↓ 12.`. The picker OPENS scrolled when the current model is below
|
|
331
|
+
the fold: started on `claude-opus-4-6`, it showed `↑ 3.` through `❯ 12. Opus 4.6 ✔` and
|
|
332
|
+
`… +1 model`, with the `Default (recommended)` row out of view. Two consequences: the
|
|
333
|
+
region grammar must accept a block that starts at its `↑` row and ends at its `↓` row or
|
|
334
|
+
overflow line (before this, it rejected every frame of such a list, so `setModel` failed
|
|
335
|
+
on its first arrow key with "The model picker was replaced"), and `setModel` must settle
|
|
336
|
+
on the cursor row's identity, not its index, because a target's index changes as the
|
|
337
|
+
window scrolls under the cursor. Fixtures:
|
|
338
|
+
`tests/fixtures/claude-2.1.310/model-picker/`. C-API-23, C-API-24.
|
|
339
|
+
- **Arrow keys walk Claude's picker window without applying anything (2.1.310, 2026-10-25).**
|
|
340
|
+
Row numbers are absolute positions, so a window starting past row 1 hides the rows
|
|
341
|
+
above it and `… +K models` counts the rows below; `listModels` reads the whole list by
|
|
342
|
+
walking the cursor one row at a time and merging the windows by number. Measured on a
|
|
343
|
+
13-model account: the window scrolls DOWN only once the cursor passes its last row, keeping
|
|
344
|
+
the cursor ON the last visible row (`↑ 2.` … `❯ 11.`), but scrolls UP while one row is
|
|
345
|
+
still above the cursor (`↑ 3.` `❯ 4.`), so the cursor never sits on the first row of a
|
|
346
|
+
scrolled window. Both ends WRAP: `↓` on row 13 jumps to `❯ 1.` with the top window, and
|
|
347
|
+
`↑` on row 1 jumps to `❯ 13.` with `↑ 4.`–`13.`; Elwood never presses past an end. Opened
|
|
348
|
+
at the top, 12 `↓` reveal rows 11–13; opened scrolled on row 12, one `↓` and 11 `↑`
|
|
349
|
+
reveal rows 13, 2, and 1. After 28 arrow presses, Escape printed `⎿ Kept model as Opus
|
|
350
|
+
5.5 (default)`, the header kept `with xhigh effort`, and the reopened picker showed the
|
|
351
|
+
same current row and effort, even though the effort row had followed the highlight
|
|
352
|
+
(`◐`, `●`, `○ Effort not supported`) on the way. A session started with `--name` adds a
|
|
353
|
+
rule carrying the name above the picker and walks identically. Fixtures:
|
|
354
|
+
`walk-down-from-top.txt`, `walk-opened-scrolled.txt` (frames separated by `--- ↓`/`--- ↑`,
|
|
355
|
+
each the screen after that key). A move that scrolls the window can be read before its
|
|
356
|
+
overflow line is repainted: a real listing that read on every render (2026-10-25) read
|
|
357
|
+
the move to `❯ 11.` with a `… +K models` count that was missing or did not add up to 13,
|
|
358
|
+
so the walk waits for a frame whose count adds up instead of failing on that one.
|
|
359
|
+
The raw stream shows why: the renderer writes only the cells that change, top to bottom,
|
|
360
|
+
and the PTY delivers the paint in reads of about 1 KB, so a frame can be read between
|
|
361
|
+
two reads. Scrolling to row 11 arrived as a 1,020-byte read carrying the new rows and a
|
|
362
|
+
71-byte read carrying the `3`→`2` of the count. The opening paint is ordered the same way:
|
|
363
|
+
the `… +K models` line is written before the effort row and the footer, and Elwood reads
|
|
364
|
+
the picker as complete only once its `Esc to cancel` footer is on screen, so a complete
|
|
365
|
+
frame always carries its count. Replaying every byte prefix of two real listings (opened
|
|
366
|
+
on row 2 and walked to 13; opened on row 10, where a count-less frame would read as a
|
|
367
|
+
complete ten-row list) found no complete frame stating fewer than 13 rows; the only
|
|
368
|
+
inconsistent ones were the stale-count frames above (`claude-picker-paint-order.test.ts`
|
|
369
|
+
pins the row-10 opening, `opening-on-row-10.json`, captured paint bytes over a placeholder
|
|
370
|
+
screen). If a later Claude painted the footer before the count, the walk could take a
|
|
371
|
+
full window with the cursor on its last row for the whole list: a listing would come back
|
|
372
|
+
short, and `setModel` would reject a hidden id as unknown. No key would go anywhere else,
|
|
373
|
+
since every key is still gated on the operation's own complete picker. C-API-23, C-API-24.
|
|
374
|
+
- **The picker's effort row follows the HIGHLIGHTED model, and `●` is one of its glyphs.**
|
|
375
|
+
On 2.1.310 the row under the list reads `○ Low effort`, `◐ Medium effort`, `● High
|
|
376
|
+
effort`, `◉ xHigh effort`, or `◈ Max effort` (each `←/→ to adjust`, with `(default)` when
|
|
377
|
+
it is that model's default), and `○ Effort not supported for Haiku 4.5` for a model
|
|
378
|
+
without effort. It changes as the cursor moves, so navigation crosses several glyphs.
|
|
379
|
+
`●` is also the older Claude reply bullet, which the region grammar treats as foreign
|
|
380
|
+
content, so the control row is recognized by its own copy before the reply check;
|
|
381
|
+
otherwise every frame with the cursor on a High-effort model reads as transcript. Captured
|
|
382
|
+
with `--effort low|medium|high|max` across all 13 rows. C-API-24.
|
|
383
|
+
- **Claude paints `/model` over its slash-command menu, so the header lands first.** A
|
|
384
|
+
5 ms capture on 2.1.310 (2026-10-24) showed the `Select model` header for 10–500 ms above
|
|
385
|
+
a partial dialog: rows 1–3 with row 3 spliced into a stale `/review-…` suggestion and
|
|
386
|
+
the rest of the menu below it, rows 1–7 the same way, or all ten rows and the overflow
|
|
387
|
+
line above leftover menu prose and no footer yet. Parsing the first frame that showed
|
|
388
|
+
the header returned one row, three rows, or none (`listClaudeModels` in `~/.coal-harbor`
|
|
389
|
+
gave 9, 1, error, 9, error over five runs), and Coal Harbor persisted a one-row
|
|
390
|
+
`default` catalog as a successful discovery. The picker is therefore parsed only once
|
|
391
|
+
the region grammar recognizes the complete dialog, footer included — the same check
|
|
392
|
+
every key write passes. A picker that never completes is never written to, because a
|
|
393
|
+
bare header is untrusted (the agent can print one): the operation fails, and failure
|
|
394
|
+
cleanup Escapes the dialog once it is recognizable. Cleanup's release is not proof the
|
|
395
|
+
picker is gone either: a blank or half-painted frame matches neither grammar, and the
|
|
396
|
+
picker can paint again after it. So once the operation or its cleanup has seen its
|
|
397
|
+
dialog, or while the picker header is up, a cleanup that ends on anything short of a
|
|
398
|
+
positively clear composer keeps queued input held until the composer is clear: Enter on
|
|
399
|
+
that picker would apply its highlighted row. Only a dialog the caller owns replacing it,
|
|
400
|
+
a complete `PreModelSwitch` hook confirmation, ends that hold early; the hook's prompt is
|
|
401
|
+
left for the caller. The header only ever holds; it never authorizes a key. Fixtures: `tests/fixtures/claude-2.1.310/model-picker/painting-*`.
|
|
402
|
+
C-API-23, C-API-24, C-API-55.
|
|
403
|
+
- **Claude can list a compiled five-row picker instead of the served catalog (2.1.310,
|
|
404
|
+
2026-10-25).** Observed: launched with `CLAUDE_CODE_MODEL_CATALOG=0`, the picker lists
|
|
405
|
+
`1. Default (recommended)`, `2. Opus`, `3. Fable`, `4. Sonnet`, and `5. Haiku`. Those are
|
|
406
|
+
bare family names with no version, and there is no overflow line. Every listing in that
|
|
407
|
+
process returns the same five rows, and Elwood reads them correctly. A listing e2e on a
|
|
408
|
+
loaded machine got the same five rows for its whole session, with no catalog variable in
|
|
409
|
+
its environment (the e2e harness strips `CLAUDE_CODE_*`).
|
|
410
|
+
|
|
411
|
+
From the 2.1.310 bundle, not observed: the choice is made once per process. The catalog
|
|
412
|
+
is off when `CLAUDE_CODE_MODEL_CATALOG` is falsy, under `--bare`, with nonessential
|
|
413
|
+
traffic disabled, with `ANTHROPIC_UNIX_SOCKET` set, with a provider that is not
|
|
414
|
+
first-party, with auth that has no catalog scope, when policy disallows
|
|
415
|
+
`allow_model_catalog`, or when the `tengu_delegated_quail` flag is off. With no catalog
|
|
416
|
+
cached in `~/.claude/cache/model-catalog/`, a first fetch slower than 1,750 ms (1,500
|
|
417
|
+
headless) also falls back.
|
|
418
|
+
|
|
419
|
+
The fallback has no hidden rows, so the listing e2e can't test the window walk against
|
|
420
|
+
it. Instead of retrying, the e2e fails with these causes
|
|
421
|
+
(`tests/e2e/claude-compiled-fallback.ts`), so a degraded environment stays visible.
|
|
422
|
+
C-API-23.
|
|
423
|
+
- **Codex 0.164.0 shortened its dialog hints.** The picker closes with `enter select ·
|
|
424
|
+
esc back` and the reasoning screen with `enter default · s session · esc back` (one
|
|
425
|
+
Escape still returns to the picker). Neither matched the old closing-hint phrases
|
|
426
|
+
(`esc to cancel`, `esc to go back`), so the grammar never recognized either dialog:
|
|
427
|
+
`setModel` failed on its first key with "The model picker was replaced", and only the
|
|
428
|
+
header-parsing `listModels` kept working. Each CLI now supplies its own hint phrase,
|
|
429
|
+
so Claude's `Esc to cancel` no longer closes a Codex dialog either. The new `s session`
|
|
430
|
+
key is a session-only apply. Fixtures: `tests/fixtures/codex-0.164.0/model-picker/`.
|
|
431
|
+
C-API-23, C-API-24.
|
|
184
432
|
- **A model dialog replaces the composer; nothing renders below it.** Full-viewport
|
|
185
433
|
captures from Claude 2.1.274 and Codex 0.154.0 (2026-09-17,
|
|
186
434
|
`tests/helpers/model-dialog-viewports.ts`) show the picker, Codex's reasoning
|
|
@@ -197,12 +445,14 @@ only their raw observation. C-CODEX-16, C-CODEX-22.
|
|
|
197
445
|
phrase on a reply or composer row itself (`❯ Select model` is staged input), and
|
|
198
446
|
a quoted picker above another dialog. Below its header a native dialog holds
|
|
199
447
|
exactly one contiguous block of picker rows (description column, numbered from 1,
|
|
200
|
-
at most one cursor);
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
448
|
+
at most one cursor); the one exception is a scrolled Claude window, which starts at
|
|
449
|
+
its `↑`-marked row (see the windowed list below). Any other caret or numbered row
|
|
450
|
+
(`❯ 1. Yes`, ` 1. Yes`, a staged `❯ 1. fix this then that`) restarts the
|
|
451
|
+
numbering, follows the blank or rule that separates the composer, or lacks the
|
|
452
|
+
column, and marks the region as someone else's. The block must also be COMPLETE
|
|
453
|
+
before Elwood treats it as live: every captured picker and reasoning screen carries
|
|
454
|
+
at least two numbered rows and a closing hint row (`Esc to cancel`, `esc to go
|
|
455
|
+
back`, `esc back`). A header with neither —
|
|
206
456
|
a transcript quoting `Select model` above a trust or hook prompt, or above a
|
|
207
457
|
reply's own numbered list — is a fragment, and Escaping it would dismiss the live
|
|
208
458
|
prompt underneath. The CURSOR is deliberately not required: a complete picker
|
|
@@ -281,6 +531,45 @@ only their raw observation. C-CODEX-16, C-CODEX-22.
|
|
|
281
531
|
the returned composer. C-API-24, C-ATTN-04. Verified from the installed 2.1.258
|
|
282
532
|
native binary and field-captured model/effort dialogs.
|
|
283
533
|
|
|
534
|
+
## Login-shell `CODEX_HOME` (2026-10-25)
|
|
535
|
+
|
|
536
|
+
Codex runs as `$SHELL -l -i -c 'exec codex …'` (§4.2). The login shell inherits
|
|
537
|
+
Elwood's environment and the user's startup files can change it, so what decides
|
|
538
|
+
which `config.toml` Codex reads and the picker writes is the environment after
|
|
539
|
+
those files run, not Elwood's own.
|
|
540
|
+
Codex takes a non-empty `CODEX_HOME`, canonicalized (a relative one from the
|
|
541
|
+
directory it runs in), else `$HOME/.codex`. Startup files can make that answer
|
|
542
|
+
depend on the launch itself:
|
|
543
|
+
|
|
544
|
+
- **`$PWD`.** The login shell starts in the session `cwd`, so
|
|
545
|
+
`export CODEX_HOME="$PWD/codex-home"` gives every project its own home. Real Codex
|
|
546
|
+
0.164.0 in two projects under that profile kept each project's default and restore
|
|
547
|
+
in that project's home.
|
|
548
|
+
- **The terminal and the session's environment.** Only the PTY launch has a TTY on
|
|
549
|
+
stdin and `ELWOOD_SESSION_ID`. A second shell started to ask (the first version of
|
|
550
|
+
this fix ran `printf` in a separate login shell) has neither, so a profile that
|
|
551
|
+
branches on `[[ -t 0 ]]` or on a session variable answers for a different home. The
|
|
552
|
+
real e2e's profile exports the session home only when both are present.
|
|
553
|
+
- **Exported values only.** Codex sees only exported variables. A profile that
|
|
554
|
+
assigns `CODEX_HOME=…` without `export` changes what Codex sees only if the
|
|
555
|
+
variable was already exported, for example inherited from Elwood's environment.
|
|
556
|
+
Otherwise Codex keeps the inherited value, or none. The report is written by a
|
|
557
|
+
child `/bin/sh`, which sees exactly what `codex` inherits.
|
|
558
|
+
- **A symlinked `cwd`.** The kernel follows a symlink before a later `..`, and so
|
|
559
|
+
does Codex. From `feature-link -> worktrees/feature`, real Codex 0.164.0 with
|
|
560
|
+
`CODEX_HOME=../codex-home` ran with `worktrees/codex-home`: its picker wrote that
|
|
561
|
+
file, not the `codex-home` beside the link. Resolving the value by name against
|
|
562
|
+
the session `cwd` read and restored the wrong file.
|
|
563
|
+
|
|
564
|
+
Elwood therefore has the launch script write the answer just before `exec codex`:
|
|
565
|
+
`/bin/sh -c 'printf "%s\0%s\0%s\0" "$CODEX_HOME" "$HOME" "$PWD"' > <report> || exit 73; exec codex …`,
|
|
566
|
+
with the report in the session's socket home, named after the launch's socket. The
|
|
567
|
+
session canonicalizes a non-empty home with `realpath` on the unnormalized
|
|
568
|
+
`$PWD/$CODEX_HOME`, which follows links the way the kernel does. Profile output
|
|
569
|
+
(banners, `nvm` notices) goes to the terminal, never to the report, so nothing has to
|
|
570
|
+
be parsed out of shell noise. If the report cannot be written, the launch shell exits
|
|
571
|
+
with 73 and never starts Codex. C-CODEX-24.
|
|
572
|
+
|
|
284
573
|
## Trust prompts
|
|
285
574
|
|
|
286
575
|
The real Claude folder-trust prompt renders as **one dialog** spanning header →
|
|
@@ -290,8 +579,10 @@ across physical rows** on a narrow terminal. Its option format is version-couple
|
|
|
290
579
|
- Claude 2.1.206 used numbered rows with the affirmative first
|
|
291
580
|
(`❯ 1. Yes, I trust this folder / 2. No, exit`).
|
|
292
581
|
- Claude 2.1.252 uses unnumbered cursor rows with the safe decline selected first
|
|
293
|
-
(`❯ No, exit / Yes, I trust this folder`). Accepting requires
|
|
294
|
-
sending the old numeric answer leaves the
|
|
582
|
+
(`❯ No, exit / Yes, I trust this folder`). Accepting requires moving the cursor
|
|
583
|
+
to the affirmative and pressing Enter; sending the old numeric answer leaves the
|
|
584
|
+
dialog waiting. From 2.1.310 evidence, never do that with a relative arrow and a
|
|
585
|
+
separate Enter (see "Claude trust selection reset" below).
|
|
295
586
|
|
|
296
587
|
The workspace gate is independent of tool permission policy: it still appears
|
|
297
588
|
under both `--permission-mode bypassPermissions` and the exact
|
|
@@ -304,7 +595,8 @@ under both `--permission-mode bypassPermissions` and the exact
|
|
|
304
595
|
"Yes" option) must never anchor a prompt (anti-spoofing).
|
|
305
596
|
- treat the whole numbered or cursor-selectable block as the option region,
|
|
306
597
|
including unselected rows above the cursor, then either send the old option
|
|
307
|
-
number or
|
|
598
|
+
number or select the affirmative by its absolute row and confirm in the same
|
|
599
|
+
write.
|
|
308
600
|
|
|
309
601
|
Under `autotrust`, detect-and-approve: if a trust prompt is detected, answer yes
|
|
310
602
|
rather than leave the agent hanging on the gate. Keep the cheap safety: never
|
|
@@ -326,6 +618,45 @@ cover both `autotrust` settings. A native rerun with this grammar answered the g
|
|
|
326
618
|
and attached the first image; its later cancelled-draft cleanup failed separately,
|
|
327
619
|
so this is trust/attachment evidence, not a passing successor-cleanup run. C-TRUST-01.
|
|
328
620
|
|
|
621
|
+
**Codex 0.164.0 asked for folder trust only in a Git repository, and saved the answer to
|
|
622
|
+
`CODEX_HOME`.** Real launches on 2026-10-25, each with a fresh `CODEX_HOME`: in a plain
|
|
623
|
+
scratch directory, Codex reached `ready` and wrote no trust entry, with and without
|
|
624
|
+
`--sandbox read-only --ask-for-approval never` and with and without a seeded
|
|
625
|
+
`config.toml`; no trust gate rendered in the launches whose screens were captured. After
|
|
626
|
+
`git init` in the same kind of directory it showed `Folder access` with `Note: You’re in
|
|
627
|
+
a subdirectory of a Git project. Trusting will apply to the repository root:` and the
|
|
628
|
+
resolved path (`/private/var/...` for a `/var/...` cwd). `Trust and continue` then saved
|
|
629
|
+
`[projects."<that path>"]` with `trust_level = "trusted"` to `$CODEX_HOME/config.toml`.
|
|
630
|
+
Every launch also wrote `[tui] screen_reader_detection_done = true` there, plus state
|
|
631
|
+
and log databases, a models cache, and plugin and skill caches. An e2e that leaves
|
|
632
|
+
`CODEX_HOME` unset therefore writes into the developer's own `~/.codex`, so the suite
|
|
633
|
+
gives every process a private one (`tests/e2e/codex-home.ts`, C-E2E-22).
|
|
634
|
+
|
|
635
|
+
**Codex 0.164.0 draws its trust gates differently when hook trust is not bypassed
|
|
636
|
+
(2026-10-25).** Native launches at 100 × 40 in a `git init` scratch directory with a
|
|
637
|
+
fresh `CODEX_HOME`:
|
|
638
|
+
|
|
639
|
+
- With `--dangerously-bypass-hook-trust` or `-c hookTrust="trust-all"` (Elwood passes
|
|
640
|
+
one of them on every launch, `src/codex/command.ts`), the `Folder access` gate is the
|
|
641
|
+
0.156.1 layout: `1. Trust and continue`, `2. Quit`, and `enter continue · esc quit`.
|
|
642
|
+
No hook gate follows.
|
|
643
|
+
- With neither, as C-E2E-09 launches Codex to see both gates, the same gate's decline
|
|
644
|
+
reads `2. Back to Agent Command Center` and its footer is `enter continue · esc back`.
|
|
645
|
+
After `Trust and continue`, `Hooks need review` follows with `1 hook is new or
|
|
646
|
+
changed.`, `Hooks can run outside the sandbox after you trust them.`, `1. Review
|
|
647
|
+
hooks`, `2. Trust all and continue`, `3. Continue without trusting (hooks won't
|
|
648
|
+
run)`, and `enter confirm · esc skip`. Alternate screen or `--no-alt-screen` drew
|
|
649
|
+
the same rows.
|
|
650
|
+
|
|
651
|
+
Without this grammar the unknown option and footers left both gates' option tails
|
|
652
|
+
invalid, so the responder held each gate and never answered it. The two footers are
|
|
653
|
+
bound to their gates (`TrustPromptSpec.footer`): each ends a dialog only under the
|
|
654
|
+
header, explanation, and options of the gate that draws it, and never counts as one of
|
|
655
|
+
the generic native footers that let an off-allowlist gate hold input. Under any other
|
|
656
|
+
dialog, or in conversation text, they make no frame a trust gate. The sanitized
|
|
657
|
+
captures, with the bypass and without, are `tests/fixtures/codex-0.164.0/trust/`.
|
|
658
|
+
C-CODEX-15.
|
|
659
|
+
|
|
329
660
|
### Where the clearance grammar lives
|
|
330
661
|
|
|
331
662
|
"This frame is the CLI's own idle composer, so the gate cleared" is a per-CLI layout
|
|
@@ -410,7 +741,9 @@ replacement prompts or future Claude renderers.
|
|
|
410
741
|
Claude 2.1.278 can paint the complete folder-trust dialog before accepting keyboard
|
|
411
742
|
navigation. In the real human-trust test, immediate Down/Enter writes left the
|
|
412
743
|
selection on "No, exit". Waiting for the selected "Yes, I trust this folder" repaint
|
|
413
|
-
before Enter cleared the dialog. The
|
|
744
|
+
before Enter cleared the dialog. The 2.1.310 investigation below found the cause, an
|
|
745
|
+
input guard that remounts the dialog, and showed that waiting for the repaint is not
|
|
746
|
+
enough on a loaded machine. The test records physical PTY writes and confirms
|
|
414
747
|
that queued caller input follows native cursor clearance; it does not require a
|
|
415
748
|
model response or available turn quota.
|
|
416
749
|
|
|
@@ -475,11 +808,12 @@ high-trust probe, a manual repeat at eight seconds cleared that same gate; the
|
|
|
475
808
|
first write had already produced premature startup activity at roughly one second.
|
|
476
809
|
Numbered automation therefore retries every 250 ms for at most five seconds,
|
|
477
810
|
revalidates the same header and exact option identity before each write, and
|
|
478
|
-
reports answered only after the gate clears.
|
|
811
|
+
reports answered only after the gate clears. Only the first write carries the
|
|
812
|
+
number; see "Codex numbered trust retries" below for what a retry may send. A native directory-to-hook transition
|
|
479
813
|
confirms directory clearance without sending an old answer to the hook gate.
|
|
480
814
|
An observed clear-and-reappear generation cancels the old attempt.
|
|
481
815
|
|
|
482
|
-
Cursor
|
|
816
|
+
Cursor answers reread the frame before every selection and confirmation write. Both navigation
|
|
483
817
|
styles now belong to one session-owned coordinator: each observed dialog
|
|
484
818
|
generation owns its reservation, deadline, exact affirmative, and completion.
|
|
485
819
|
An old cancelled attempt cannot release or settle a reappeared gate. The five-
|
|
@@ -503,6 +837,68 @@ PTY exit cancel pending automation and timers before disposal. Only a rejected
|
|
|
503
837
|
PTY write belonging to a still-live matching attempt emits
|
|
504
838
|
`startup_prompt_write_failed`; cancellation and late settlement stay quiet.
|
|
505
839
|
|
|
840
|
+
**The five seconds count on-screen time only (Claude 2.1.310, 2026-10-25).** After
|
|
841
|
+
an accepted folder-trust answer, Claude erases the dialog at once but takes
|
|
842
|
+
seconds to paint its composer. In 30 fresh-folder `autotrust` launches, 10 at a
|
|
843
|
+
time (load average 30–70), the dialog was erased 0.17–0.98 s after it first
|
|
844
|
+
painted. The screen then stayed blank, then showed only the welcome banner
|
|
845
|
+
(logo, version, model, path, one rule). The composer painted 4.0–8.9 s after
|
|
846
|
+
the dialog first appeared. That deadline used to run from the first candidate to
|
|
847
|
+
verified composer clearance, so it timed Claude's boot rather than the gate.
|
|
848
|
+
Every launch whose composer came after 5 s went `blocked` with
|
|
849
|
+
`claude-workspace_trust-prompt` attention at +5.0 s, then `ready` when the
|
|
850
|
+
composer painted: 17 of the 27 launches that reached `ready`. At each of those
|
|
851
|
+
blocks the rendered frame held no trust dialog. Another 100 launches the same
|
|
852
|
+
day show the same split: 51 of 94 `ready` launches blocked; the dialog was
|
|
853
|
+
erased 0.05–1.06 s after first paint; erase to composer took 0.72–8.23 s, median
|
|
854
|
+
4.6 s. Headless `elwood run` treats that rule id as a human prompt and kills the
|
|
855
|
+
session: 5 of 7 runs, 14 at once in fresh `$TMPDIR` folders, failed with
|
|
856
|
+
`blocked_prompt`, against none with this change.
|
|
857
|
+
|
|
858
|
+
The budget therefore elapses only on frames that show the gate: it is five
|
|
859
|
+
seconds of cumulative visible time. A frame without it pauses the budget, and
|
|
860
|
+
the same gate's return resumes the remainder rather than a fresh five seconds,
|
|
861
|
+
so a gate that flickers still exhausts one budget. Only a gate that stays absent
|
|
862
|
+
from its departure through verified clearance is guaranteed never to block. The
|
|
863
|
+
input hold does not change: it lasts until verified composer or successor
|
|
864
|
+
clearance. No separate post-answer
|
|
865
|
+
bound replaces the old one. Boot time grows with load, so any fixed bound only
|
|
866
|
+
moves the false attention later. A CLI that never paints after an answered gate
|
|
867
|
+
leaves the session held, as before, but no longer reported as a trust prompt.
|
|
868
|
+
Interleaved under the same load, 16 at once, the old rule blocked 10 of 16
|
|
869
|
+
launches and this one 0 of 16, with 10 boots over 5 s in each arm. Sixteen
|
|
870
|
+
launches matter: eight on a quiet machine all booted in under 4 s and never
|
|
871
|
+
reached the condition. The sanitized PTY bytes of the slowest launch are in
|
|
872
|
+
`tests/fixtures/claude-2.1.310/trust-slow-boot/`;
|
|
873
|
+
`tests/claude/session-trust-slow-boot.test.ts` replays them at their recorded
|
|
874
|
+
times. `tests/e2e/trust-slow-boot-claude.e2e.ts` launches 16 fresh folders at
|
|
875
|
+
once against the real CLI and logs how many boots exceeded 5 s. C-ATTN-03,
|
|
876
|
+
C-TRUST-01.
|
|
877
|
+
|
|
878
|
+
**An answer is reported when the composer verifies it, not when the dialog
|
|
879
|
+
leaves (Claude 2.1.310, 2026-10-25).** The blank and banner-only frames between
|
|
880
|
+
the erased folder-trust dialog and the composer show neither the gate nor a
|
|
881
|
+
composer. The attempt read them as its choice vanishing and cancelled itself
|
|
882
|
+
quietly, which is right for a choice lost before its Enter. By then, though, the
|
|
883
|
+
confirming write had fulfilled, so the cancellation discarded a real answer.
|
|
884
|
+
None of the 30 fresh-folder `autotrust` launches above emitted `startup_prompt`,
|
|
885
|
+
although the 27 that reached `ready` had answered their gate. A confirmed
|
|
886
|
+
attempt now waits through dialog-free frames and writes nothing; it re-reads the
|
|
887
|
+
screen only when a new frame renders, so the wait holds no timer. Verified
|
|
888
|
+
composer or successor clearance reports it once. If the same gate returns, the
|
|
889
|
+
attempt answers again within the remaining on-screen budget. Expiry, a changed
|
|
890
|
+
gate, a complete off-allowlist gate, or closing cancels it without a report. With this change, all 21 launches of the two Claude trust e2e
|
|
891
|
+
files (5 at once, then 16 at once with every composer more than 7.5 s after the
|
|
892
|
+
prompt) reported exactly one `startup_prompt`, `sent home+down+enter`. Swapping
|
|
893
|
+
in the previous trust code failed both files: no launch reported its answer.
|
|
894
|
+
Codex 0.164 shows its directory-trust prompt in a fresh git folder, and its
|
|
895
|
+
answers were lost the same way: in four rounds of 4 such launches at once, the
|
|
896
|
+
previous code reported 0 of 16 answers and this change 16 of 16, each `sent 1`.
|
|
897
|
+
`tests/claude/session-trust-slow-boot.test.ts` replays the recorded launch and
|
|
898
|
+
requires exactly one report, and both Claude trust e2e files require one
|
|
899
|
+
`workspace_trust` `startup_prompt` per launch that names a write the launch made. C-CLAUDE-10,
|
|
900
|
+
C-TRUST-01.
|
|
901
|
+
|
|
506
902
|
Both CLIs reword gates often, so an off-allowlist gate behind a recognized header
|
|
507
903
|
prefix fails closed: a header-shaped region (`headerStart` in
|
|
508
904
|
`src/core/trust/dialog.ts`) with real header text, selectable options, only
|
|
@@ -523,6 +919,128 @@ A no-model Codex resize probe at 100×12, 100×8 and 100×6 confirms that the we
|
|
|
523
919
|
box can scroll away while its composer and model/reasoning/path footer remain.
|
|
524
920
|
Those native rows also prove clearance; welcome presence is not required.
|
|
525
921
|
|
|
922
|
+
### Codex numbered trust retries (0.164, 2026-10-25)
|
|
923
|
+
|
|
924
|
+
Under `autotrust`, Codex 0.164 sometimes received "1" as a user message right
|
|
925
|
+
after its directory-trust gate. Elwood wrote `1\r` when the `Folder access` gate
|
|
926
|
+
painted, then wrote `1\r` again 250 ms later because the frame still showed the
|
|
927
|
+
gate. Under load Codex had taken the first answer but painted its composer after
|
|
928
|
+
the retry, so the second `1\r` submitted "1" as a turn. In one capture the writes
|
|
929
|
+
landed at 21.26 s and 21.52 s, and the transcript then showed `› 1` with the turn
|
|
930
|
+
running at 24.7 s.
|
|
931
|
+
|
|
932
|
+
Codex also discards keys written as the gate paints. In 8 fresh git folders
|
|
933
|
+
launched at once (load average about 140), with `autotrust` off and a probe
|
|
934
|
+
typing `1\r` 8–33 ms after the gate's first frame, 7 of the 8 answers were
|
|
935
|
+
dropped. No output followed for 3 s. A bare `\r` then confirmed the
|
|
936
|
+
highlighted `› 1. Trust and continue` 24–156 ms later in all 7. The eighth
|
|
937
|
+
answer (written 13 ms after the gate's first frame) was taken. Once each
|
|
938
|
+
composer painted, a bare `\r` on the empty composer submitted nothing in 8 of
|
|
939
|
+
8: no hook fired, no `Working` row, and the status stayed `ready`. Typing `1`
|
|
940
|
+
alone selects option 1 without confirming it (the gate stayed for 40 s). So
|
|
941
|
+
neither a time limit nor new output can tell a dropped answer from one Codex
|
|
942
|
+
is still processing, and a dropped answer produces no output to wait for.
|
|
943
|
+
|
|
944
|
+
Elwood therefore types the number in its first write to a gate, and never
|
|
945
|
+
again on a stale frame alone. While the
|
|
946
|
+
rendered caret is on the affirmative, a Codex retry is Enter alone, every
|
|
947
|
+
250 ms within the gate's five on-screen seconds. On the gate it confirms the
|
|
948
|
+
highlighted affirmative; on the empty composer it does nothing. Claude never
|
|
949
|
+
gets a bare Enter (its list remounts with the caret on the decline; see the
|
|
950
|
+
next section), and Codex's hook gate opens with its caret on `Review hooks`.
|
|
951
|
+
Those numbered gates are answered again only after a frame rendered from
|
|
952
|
+
output that arrived after the last write still shows them. Once Elwood has
|
|
953
|
+
started writing the number to a gate, every later attempt at it retries this
|
|
954
|
+
way, even when that write or a retry was rejected, since a rejected write may
|
|
955
|
+
still have reached the CLI. Only another complete dialog replacing the gate
|
|
956
|
+
makes its return a fresh gate that gets the number.
|
|
957
|
+
`tests/codex/session-trust-slow-answer.test.ts` covers both cases.
|
|
958
|
+
|
|
959
|
+
Measured with 8 fresh git folders launched at once per round and two rounds per
|
|
960
|
+
arm, at load average 20–60 (leaked shell processes were also slowing the
|
|
961
|
+
machine): on the previous code, 9 of 16 launches typed `1` into the composer (2
|
|
962
|
+
to 11 `1\r` writes each), and only 5 of 16 reported their answer. With this
|
|
963
|
+
change, 0 of 16 did: each typed the number once, then 1 to 15 bare Enters, and
|
|
964
|
+
all 16 reported `sent 1`. `tests/e2e/trust-retry-codex.e2e.ts` makes the lag
|
|
965
|
+
certain by holding Codex's output from Elwood for 2 s after the answer while
|
|
966
|
+
Codex runs normally. On the previous code it failed both runs: seven more `1\r`
|
|
967
|
+
writes reached the composer, "11" was submitted, and the model answered "What
|
|
968
|
+
would you like me to do with 11?". With this change it passed 3 of 3. Freezing
|
|
969
|
+
Codex itself (SIGSTOP for 2 s right after the answer) does not work as a test:
|
|
970
|
+
Codex then exits with `timed out discarding buffered terminal input`, whatever
|
|
971
|
+
Elwood types. Codex 0.164 discards buffered input at screen changes like this
|
|
972
|
+
one, and a stalled discard is fatal.
|
|
973
|
+
|
|
974
|
+
### Claude trust selection reset (2.1.310, 2026-10-25)
|
|
975
|
+
|
|
976
|
+
Under `autotrust`, 7 of 60 fresh-folder launches of Claude Code 2.1.310 exited
|
|
977
|
+
with code 1 at the folder-trust gate on a loaded machine (load average 60–160 on
|
|
978
|
+
16 cores, three to six launches at once). Every failure had the same shape. Elwood
|
|
979
|
+
wrote `\x1b[B` (twice in two runs, the second a 250 ms retry), Claude painted
|
|
980
|
+
`❯ Yes, I trust this folder`, and Elwood wrote a bare `\r`. Claude then repainted
|
|
981
|
+
`❯ No, exit` without any key from Elwood and ran its exit sequence
|
|
982
|
+
(`\x1b[?1006l…\x1b[?25h`). In two runs the repaint and the exit sequence arrived in
|
|
983
|
+
one output chunk.
|
|
984
|
+
|
|
985
|
+
The 2.1.310 binary explains it. `TrustDialog` renders its confirm list with
|
|
986
|
+
`cancelFirst`, `focus: "cancel"` and a React key of `"held"` or `"settled"`. The key
|
|
987
|
+
flips to `"settled"` 150 ms after the dialog mounts. An Enter within 150 ms of mount,
|
|
988
|
+
or of an earlier refused Enter, is refused, and the refusal flips the key back to
|
|
989
|
+
`"held"` for another 150 ms. Each key change remounts the list with the cursor on
|
|
990
|
+
"No, exit". On a busy machine the settle timer is already overdue when Claude next
|
|
991
|
+
reads stdin, so it runs first. The Enter that Elwood decided from the `❯ Yes` frame
|
|
992
|
+
is then processed on the remounted list and confirms "No, exit". A probe that sent
|
|
993
|
+
only Down at first paint saw `❯ Yes` and then `❯ No, exit` 90 ms later, with no
|
|
994
|
+
Enter. A refused Enter only resets the cursor: that is the "first Enter is lost"
|
|
995
|
+
pattern in successful runs (Down, Enter, reset, Down, Enter). Separately, Down on
|
|
996
|
+
the last row wraps to the first. In one run a stalled first arrow and its 250 ms
|
|
997
|
+
retry both landed, painting `❯ Yes` and then `❯ No, exit`; an Enter decided between
|
|
998
|
+
them would also confirm "No, exit". Every installed binary from 2.1.268 to 2.1.310
|
|
999
|
+
carries the same remount key on this dialog (the 150 ms window was read from 2.1.305
|
|
1000
|
+
and 2.1.310), and the MCP-server dialog uses it too.
|
|
1001
|
+
|
|
1002
|
+
Claude's list binds `home` to `select:first` and `end` to `select:last` in every
|
|
1003
|
+
installed binary from 2.1.268 to 2.1.310. A live 2.1.310 probe confirmed that Home
|
|
1004
|
+
(`\x1b[H`) and End (`\x1b[F`, `\x1b[4~`, `\x1bOF`) move the trust cursor absolutely
|
|
1005
|
+
and repeat harmlessly. Written as one chunk, `\x1b[H\x1b[B\r` confirmed "Yes" in
|
|
1006
|
+
every probe and every fixed launch, including launches where the remount ran just
|
|
1007
|
+
before the chunk was read.
|
|
1008
|
+
The list's key handlers read focus through a synchronous getter, so one chunk's keys
|
|
1009
|
+
see each other's effect. Inside the 150 ms windows the chunk is refused, which is
|
|
1010
|
+
harmless, and the next retry succeeds. Elwood therefore writes Home plus one Down
|
|
1011
|
+
per row above the affirmative while the rendered cursor is elsewhere. Once the
|
|
1012
|
+
cursor is on the affirmative, it writes that selection and Enter as one write.
|
|
1013
|
+
Relative arrows and bare Enter are never sent. With that rule, interleaved with the
|
|
1014
|
+
failing runs under the same load, 0 of 51 launches exited. In 7 of them Claude
|
|
1015
|
+
repainted `❯ No, exit` after the combined write and still accepted it; with a bare
|
|
1016
|
+
Enter that interleaving exits. `tests/unit/trust-claude-selection-reset.test.ts`
|
|
1017
|
+
replays the real 100×30 frames (`tests/fixtures/claude-2.1.310/folder-trust-*.txt`)
|
|
1018
|
+
against a model of this dialog. `tests/e2e/trust-prompt-claude-concurrent.e2e.ts`
|
|
1019
|
+
launches five fresh folders at once and checks every trust write at the PTY
|
|
1020
|
+
boundary.
|
|
1021
|
+
|
|
1022
|
+
### Codex takes a trust answer before Elwood's frame shows it (0.164, 2026-10-25)
|
|
1023
|
+
|
|
1024
|
+
Elwood decides each startup retry from the frame it has rendered, and under load that
|
|
1025
|
+
frame trails Codex. Codex 0.164 can take the trust answer and draw its composer while
|
|
1026
|
+
Elwood's frame still shows the folder-access gate. A retry decided from that stale
|
|
1027
|
+
frame lands in the composer. Keys a caller types in that window also reach the
|
|
1028
|
+
composer, so a `1\r` retry written after them would add a digit to the caller's draft
|
|
1029
|
+
and submit it as a turn.
|
|
1030
|
+
|
|
1031
|
+
`tests/e2e/trust-caller-input-codex.e2e.ts` makes the window certain. It holds Codex's
|
|
1032
|
+
output from Elwood for 4 s after the first `1\r`, and types `hello` as soon as Codex's
|
|
1033
|
+
raw output draws its configured composer (the footer naming the session's model). On
|
|
1034
|
+
`main` at 6e396099, at load average 34, Elwood kept retrying the stale gate every
|
|
1035
|
+
250 ms: 14 more `1\r` writes followed `hello`. Raw caller input now hands the startup
|
|
1036
|
+
prompt on screen to the caller before its keys are written (C-TRUST-01). In two runs
|
|
1037
|
+
with that handoff, nothing followed `hello`.
|
|
1038
|
+
|
|
1039
|
+
The Codex update skip has the same ordering. A `2` decided from a stale update prompt
|
|
1040
|
+
would reach whatever Codex drew next; on the trust gate, `2` is Quit. Caller input
|
|
1041
|
+
ends the skip, and the update stays the caller's until a native composer clears it,
|
|
1042
|
+
because Elwood's frame can lose and regain a stale prompt in between (C-CODEX-12).
|
|
1043
|
+
|
|
526
1044
|
### Native Codex startup warnings
|
|
527
1045
|
|
|
528
1046
|
Codex 0.154.0 normally collapses MCP failures to
|
|
@@ -533,6 +1051,22 @@ does not send Ctrl+T automatically. Captures and the local failing-server/401
|
|
|
533
1051
|
real-CLI test verify the detailed rows. The current welcome chrome and native
|
|
534
1052
|
warning prefixes distinguish these banners from conversational quotations.
|
|
535
1053
|
|
|
1054
|
+
**Codex 0.164.0 shows only a badge (2026-10-25).** In C-E2E-09's native run (an MCP
|
|
1055
|
+
server answering 401, `startup_timeout_sec = 2`), the welcome is no longer boxed, and
|
|
1056
|
+
the only startup trace is `⚠ 1 warning · f2 to view` at the end of the composer's
|
|
1057
|
+
model footer, which the composer clearance already strips. Ctrl+T opened the
|
|
1058
|
+
`/ T R A N S C R I P T /` overlay. It holds both canonical rows, the `elwood_probe`
|
|
1059
|
+
not-logged-in row with its `codex mcp login elwood_probe` command and
|
|
1060
|
+
`⚠ MCP startup incomplete (failed: elwood_probe)`, under an unboxed
|
|
1061
|
+
`>_ OpenAI Codex (v0.164.0)` and above the overlay's own key hints. That overlay is
|
|
1062
|
+
Codex's transcript view, where the conversation also renders, so it is not a startup
|
|
1063
|
+
region; the parser finds no welcome box there and returns nothing. Elwood sends neither F2 nor Ctrl+T, so on 0.164 a
|
|
1064
|
+
session emits no MCP startup warning: the count names no server and no recovery
|
|
1065
|
+
command (PRD §5.7, C-CODEX-09). The captures are
|
|
1066
|
+
`tests/fixtures/codex-0.164.0/boot/warning-badge.txt` and
|
|
1067
|
+
`warning-transcript-overlay.txt`; C-E2E-09 asserts the badge, that its frame reads as
|
|
1068
|
+
the composer, and that no warning parses from it.
|
|
1069
|
+
|
|
536
1070
|
Native 0.154.0 startup renders no `•`/`●` row at any point: recorded frame by frame
|
|
537
1071
|
(2026-09-17) through the directory gate, hooks gate, welcome, and composer with
|
|
538
1072
|
both an MCP endpoint that fails at once and one that accepts the connection and
|
|
@@ -559,13 +1093,39 @@ The acceptance is persisted in Claude's global config as
|
|
|
559
1093
|
`bypassPermissionsModeAccepted` (the binary describes it as "Whether the user has
|
|
560
1094
|
accepted the bypass permissions mode dialog"), and `--bg` refuses bypass mode
|
|
561
1095
|
until it has been accepted once interactively, so the dialog is at most
|
|
562
|
-
once-per-machine.
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
1096
|
+
once-per-machine. On 2.1.268 (Claude Max account, macOS) the dialog did not render
|
|
1097
|
+
at all, through Elwood's `--permission-mode bypassPermissions` launch or a raw
|
|
1098
|
+
`--dangerously-skip-permissions` one, and no `bypassPermissionsModeAccepted` key
|
|
1099
|
+
existed anywhere under `~/.claude*`. The likely reason is a user setting, not the
|
|
1100
|
+
version or account: the developer's `~/.claude/settings.json` sets
|
|
1101
|
+
`"skipDangerousModePermissionPrompt": true`. On 2.1.310, in a config that has never
|
|
1102
|
+
accepted the dialog, that setting alone decides whether it renders: in an empty
|
|
1103
|
+
`CLAUDE_CONFIG_DIR` (no settings, never accepted) the dialog renders, and adding the
|
|
1104
|
+
setting there sends the session straight to the composer. The 2.1.310 copy
|
|
1105
|
+
is longer and opens with the cursor on the decline
|
|
1106
|
+
(`tests/fixtures/claude-2.1.310/bypass-permissions-{100,200}.txt`):
|
|
1107
|
+
|
|
1108
|
+
```
|
|
1109
|
+
WARNING: Claude Code running in Bypass Permissions mode
|
|
1110
|
+
|
|
1111
|
+
In Bypass Permissions mode, Claude Code will not ask for your approval before running potentially dangerous commands.
|
|
1112
|
+
This mode should only be used in a sandboxed container/VM that has restricted internet access and can easily be restored if damaged.
|
|
1113
|
+
|
|
1114
|
+
By proceeding, you accept all responsibility for actions taken while running in Bypass Permissions mode.
|
|
1115
|
+
|
|
1116
|
+
https://code.claude.com/docs/en/security
|
|
1117
|
+
|
|
1118
|
+
❯ No, exit
|
|
1119
|
+
Yes, I accept
|
|
1120
|
+
|
|
1121
|
+
Enter to confirm · Esc to cancel
|
|
1122
|
+
```
|
|
1123
|
+
|
|
1124
|
+
Until the native-copy pattern accepted the sandbox sentence and the link, Elwood
|
|
1125
|
+
held this dialog as unrecognized prose and the session stayed `blocked` under
|
|
1126
|
+
`autotrust`. The suite never saw it because the developer's setting hid it, so
|
|
1127
|
+
whether it appears depends on the user's settings and Elwood must be correct in
|
|
1128
|
+
BOTH states:
|
|
569
1129
|
|
|
570
1130
|
- The dialog is an allowlisted trust prompt (`bypass_permissions`), answered
|
|
571
1131
|
under `autotrust` by the same header-anchored, numbered-or-cursor machinery as
|
|
@@ -598,6 +1158,40 @@ prompt; `--no-trust` and generic permission dialogs remain human blocks. The sta
|
|
|
598
1158
|
deliver this attention after the responder has already continued, so treating
|
|
599
1159
|
every attention event as fatal produces a false `blocked_prompt`.
|
|
600
1160
|
|
|
1161
|
+
### Claude keeps the prompt's caret after a declined permission dialog (2.1.310, 2026-10-26)
|
|
1162
|
+
|
|
1163
|
+
A real Claude 2.1.310 session in `default` mode (189x48, Opus 5.5, a private
|
|
1164
|
+
`CLAUDE_CONFIG_DIR` with no user settings) asked to create a file rendered the
|
|
1165
|
+
`Do you want to proceed?` dialog with its cursor hidden. Escape declined it. The next
|
|
1166
|
+
frame showed `⎿ Interrupted · What should Claude do instead?`, the idle composer
|
|
1167
|
+
fenced by rules, the `⏸ manual mode on · ? for shortcuts` footer, a visible cursor at
|
|
1168
|
+
column 2, and the idle title `✳`. The transcript above it still held the submitted
|
|
1169
|
+
prompt, echoed as `❯ Create a file named …`. Claude echoes every submitted prompt that
|
|
1170
|
+
way, so the whole-frame clearance, which rejects any caret row that is not an empty
|
|
1171
|
+
composer, rejected the frame, and the session stayed `blocked` while Claude sat idle:
|
|
1172
|
+
15 s in the capture, and the whole wait of a C-ATTN-01 run in the same private config.
|
|
1173
|
+
Attention clearance now also accepts the last composer with its rules and native
|
|
1174
|
+
footer (`claudeLastComposerClearance`), the path the model picker already used for
|
|
1175
|
+
`❯ /model` (`tests/fixtures/claude-2.1.310/permission-declined/`). The real-CLI C-ATTN-01 runs
|
|
1176
|
+
Claude with the developer's own config dir, and had not caught this on the developer's
|
|
1177
|
+
machine: their own `PermissionRequest` hook allowed the command, so the dialog never
|
|
1178
|
+
rendered there. This capture is a manual reproduction in a private config dir with no
|
|
1179
|
+
user hooks. C-ATTN-02.
|
|
1180
|
+
|
|
1181
|
+
A resumed session repaints those carets too. In the same private config, a `default`-mode
|
|
1182
|
+
session ran one turn and was stopped, then resumed with `highTrust`. Bypass mode had
|
|
1183
|
+
never been accepted there, so the resume opened on the bypass-permissions dialog, and
|
|
1184
|
+
autotrust answered it. Claude then painted the resumed transcript, with
|
|
1185
|
+
`❯ Reply with exactly: hello. …`, above the idle bypass-mode composer, its footer, and a
|
|
1186
|
+
visible cursor at column 2. Startup trust clearance used the same whole-frame check,
|
|
1187
|
+
so the answer never settled. The session sat at `running` for the 60 s observed and
|
|
1188
|
+
never reached `ready`. Startup trust clearance now uses the same predicate as attention
|
|
1189
|
+
clearance (`tests/fixtures/claude-2.1.310/resume-bypass/`). Rerun with that change, the same
|
|
1190
|
+
resume answered the dialog, recorded a `bypass_permissions` startup prompt, and reached
|
|
1191
|
+
`ready` 4.6 s after launch. A folder-trust gate on
|
|
1192
|
+
resume goes through the same clearance, but that case was not captured. C-TRUST-01,
|
|
1193
|
+
C-CLAUDE-21.
|
|
1194
|
+
|
|
601
1195
|
## Codex in-TUI update prompt (and the restart loop)
|
|
602
1196
|
|
|
603
1197
|
Independent of the `autoupdate` preflight (`codex update` run before spawn), the
|
|
@@ -703,6 +1297,241 @@ other errors, pre-exit errors, and persistent `EPERM` still fail. Controlled
|
|
|
703
1297
|
shutdown also leaves the reap to its owning termination path instead of emitting
|
|
704
1298
|
a competing best-effort warning. Verified against Codex 0.153.3 on macOS.
|
|
705
1299
|
|
|
1300
|
+
## Hook and MCP process groups (2026-10-25)
|
|
1301
|
+
|
|
1302
|
+
Claude Code 2.1.310 and Codex 0.164.0 start every hook command with `setsid()`.
|
|
1303
|
+
Measured on macOS with `ps -o pid,ppid,pgid,command` and `os.getsid()` while a
|
|
1304
|
+
slow hook was in flight under a real Elwood session:
|
|
1305
|
+
|
|
1306
|
+
- Claude runs a hook as a direct child of the CLI (`ppid` = the PTY leader) whose
|
|
1307
|
+
`pgid` and `sid` both equal its own pid. A compound command runs as
|
|
1308
|
+
`/bin/sh -c '<command>'`; a plain one such as Elwood's
|
|
1309
|
+
`'<node>' '<bridge>.mjs'` is executed directly. An Electron host's
|
|
1310
|
+
`ELECTRON_RUN_AS_NODE=1 …` command needs the shell, consistent with the
|
|
1311
|
+
reported orphan `/bin/sh -c node hook-bridge.mjs`.
|
|
1312
|
+
- Codex runs a hook as `/bin/zsh -c '<command>'` (the user's shell), also with its
|
|
1313
|
+
own `pgid` and `sid`. Codex started an MCP server (`node ./scripts/start-mcp.mjs`,
|
|
1314
|
+
from a plugin it installed in the sandboxed `CODEX_HOME`) in its own process
|
|
1315
|
+
group but inside the leader's session.
|
|
1316
|
+
- The children the CLI starts without detaching, such as Claude's IDE probe
|
|
1317
|
+
(`ps aux | grep …`), stay in the leader's group.
|
|
1318
|
+
|
|
1319
|
+
So the leader-group SIGKILL (`kill(-leader)`) cannot reach a hook. Once the
|
|
1320
|
+
leader dies the hook is reparented to PID 1 and nothing ties it to the session;
|
|
1321
|
+
only the parent-pid chain links a hook to its leader, and only while the leader
|
|
1322
|
+
lives. With the group reap alone, `teardown()` during a `SessionStart` project hook
|
|
1323
|
+
running `sleep 45` resolved in 25 ms and left that `/bin/sh` and its `sleep` alive
|
|
1324
|
+
under PID 1. `stop()` while a `SessionEnd` hook ran `sleep 46` timed out after
|
|
1325
|
+
5 s; Claude waits for `SessionEnd` hooks on SIGTERM, so Elwood escalated to SIGKILL
|
|
1326
|
+
and left the hook running. On Codex, `teardown()` during a `UserPromptSubmit`
|
|
1327
|
+
config hook left the `zsh`/`sleep` group alive, and the MCP server group outlived
|
|
1328
|
+
the CLI briefly. Elwood's own bridge processes died only because teardown then
|
|
1329
|
+
closes the bridge socket; the reap itself never reached them.
|
|
1330
|
+
|
|
1331
|
+
When Claude exits on SIGTERM within the graceful window it kills its in-flight
|
|
1332
|
+
hooks itself: a `stop()` during the same `sleep 45` hook exited in about 1 s with
|
|
1333
|
+
no survivors. On macOS, `killpg` on a group whose members are all unreaped zombies
|
|
1334
|
+
returns `EPERM`, not `ESRCH`. `src/runtime/shutdown/process-table.ts` reads the
|
|
1335
|
+
table and finds these groups by walking parent pids from the leader.
|
|
1336
|
+
|
|
1337
|
+
Elwood therefore walks the tree only before a SIGKILL it sends to a live leader. It
|
|
1338
|
+
SIGSTOPs the leader's group and lists
|
|
1339
|
+
`/bin/ps -A -o pid=,ppid=,pgid=,stat=,lstart=,ucomm=,pid=` (the start time tells a reused
|
|
1340
|
+
pid from the process that had it; the short name and the pid again are for Codex's
|
|
1341
|
+
daemon, below). A
|
|
1342
|
+
group id from `ps` is only a snapshot, `kill()` returning means the SIGSTOP was sent,
|
|
1343
|
+
not delivered, and `ps` reads each process's state at its own moment (on macOS it
|
|
1344
|
+
takes the set of pids first and each row's state afterwards). So Elwood relies only
|
|
1345
|
+
on a process that read stopped (`stat` starting with `T`), inside a group it stopped,
|
|
1346
|
+
in two consecutive listings. A group that holds a live descendant is stopped once
|
|
1347
|
+
such a process is a parent of one of its members: that parent cannot reap the
|
|
1348
|
+
member, so the group keeps its id until the signal. The walk succeeds only on a
|
|
1349
|
+
listing that shows the leader live, leaves no live group unreached, and holds every
|
|
1350
|
+
live process in every stopped group that way, the leader's included, so none of them
|
|
1351
|
+
can have started a process the listing missed; a leader exit observed first fails it,
|
|
1352
|
+
because the leader's detached children are then out of reach. Then the stopped groups
|
|
1353
|
+
are SIGKILLed deepest first, except one that holds Codex's daemon, which is resumed
|
|
1354
|
+
(below) (`src/runtime/shutdown/detached-sweep/`). That is two listings with no hook in
|
|
1355
|
+
flight, and two more per level of nesting. A process whose own parent exited before
|
|
1356
|
+
the walk is already under PID 1 and out of reach. macOS refuses a stop or a kill of
|
|
1357
|
+
a group whose members are all unreaped zombies with `EPERM`, so either counts as a
|
|
1358
|
+
failure only if a later listing still shows a live member of that group.
|
|
1359
|
+
|
|
1360
|
+
`killpg` is not atomic against `fork` (measured 2026-10-25, Claude 2.1.310, macOS
|
|
1361
|
+
Darwin 25.5): a child forked while the group's SIGSTOP goes out can miss it and keep
|
|
1362
|
+
running. The PTY leader is the user's interactive login shell (§4.2), and in one
|
|
1363
|
+
Claude `teardown()` out of nine, with no hook in flight, `claude` had already exited
|
|
1364
|
+
and the shell was running a `$(… | tr …)` pipeline from the user's shell
|
|
1365
|
+
configuration. One subshell of it read `R` on every listing for the whole
|
|
1366
|
+
three-second deadline while the rest of the group read `T`, so the leader's group was
|
|
1367
|
+
never held and `teardown()` rejected. A zsh group forking `$(echo a | tr a b)` in a
|
|
1368
|
+
loop, SIGSTOPped once, left one member running in 1 of 150 stops, and a second
|
|
1369
|
+
SIGSTOP stopped it every time. So any listing that shows a live process in a stopped
|
|
1370
|
+
group not stopped sends that group SIGSTOP again: the leader's only while its exit
|
|
1371
|
+
has not been observed, a descendant group because its held parent keeps the id. A
|
|
1372
|
+
listing cannot tell a stop still in flight from a missed one, and a second SIGSTOP
|
|
1373
|
+
to a stopped process does nothing, so the walk does not try to. With the re-stop, 40
|
|
1374
|
+
of 40 real no-hook teardowns completed, 5 of them sending the leader's group a
|
|
1375
|
+
second SIGSTOP; without it, 1 of 9 had rejected.
|
|
1376
|
+
|
|
1377
|
+
The walk's cost, measured 2026-10-25 on Claude 2.1.310 on a shared machine: one `ps`
|
|
1378
|
+
listing of about 1,800 rows took 110–240 ms at load 33–35. At load 19–26, ten
|
|
1379
|
+
`teardown()`s with no hook in flight took 81–291 ms (median 227, two listings), and
|
|
1380
|
+
ten with a `SessionStart` hook in flight 175–567 ms (median 373, four), against 2–3
|
|
1381
|
+
ms and 7–18 ms with the walk disabled, where the in-flight hook survived every time. `tests/e2e/teardown-reap.e2e.ts` drives the
|
|
1382
|
+
real CLIs: Claude `teardown()` with a hook in its own group, a `stop()` whose
|
|
1383
|
+
`SessionEnd` hook outlasts the graceful window, and Codex `teardown()` with a hook.
|
|
1384
|
+
With the walk disabled all three fail with the hook still running.
|
|
1385
|
+
|
|
1386
|
+
On a loaded host the walk outgrew its first three-second deadline (measured
|
|
1387
|
+
2026-10-26, macOS Darwin 25.5, about 3,700 processes on the host). A `kill()` of a
|
|
1388
|
+
setsid hook with a setsid child of its own takes six reads. That is two to hold the
|
|
1389
|
+
leader's group, and two more for each level of nesting. At a 1-minute load of 70 to
|
|
1390
|
+
92, those reads took 78–644 ms each, and the walk took 1.1–2.6 s. Under the
|
|
1391
|
+
repository's own parallel test run on that host, reads passed half a second, and so
|
|
1392
|
+
six of them passed three seconds. `kill()` then rejected with `termination_failed`,
|
|
1393
|
+
though each read was making progress. The deadline is now ten seconds.
|
|
1394
|
+
`tests/unit/sweep-deadline-real.test.ts` slows each real read by 600 ms and checks
|
|
1395
|
+
that `kill()` still reaps such a tree.
|
|
1396
|
+
|
|
1397
|
+
Codex also starts a shell of its own, not a hook, that the walk has to reach
|
|
1398
|
+
(measured 2026-10-25, Codex 0.164, macOS Darwin 25.5). A few seconds after launch
|
|
1399
|
+
(2.1–2.6 s after `startCodex` was called, in these runs) it snapshots the user's
|
|
1400
|
+
shell environment with `/bin/zsh -lc '<script>'`. The script sources
|
|
1401
|
+
`$ZDOTDIR/.zshrc`, else `$HOME/.zshrc`, then prints the environment. The shell is a
|
|
1402
|
+
session leader of its own (`stat` `Ss`, `pgid` equal to its pid) whose parent is the
|
|
1403
|
+
`codex` leader. With a normal profile it lived about 0.8 s. A `.zshrc` that sleeps
|
|
1404
|
+
only when non-interactive (`[[ -o interactive ]] || sleep 120`) holds it open without
|
|
1405
|
+
slowing the PTY's own interactive login shell, and with that profile:
|
|
1406
|
+
|
|
1407
|
+
- `teardown()` with the group reap alone left the `zsh -lc` and its `sleep` running
|
|
1408
|
+
under PID 1. With the walk, `teardown()` and `kill()` reaped both.
|
|
1409
|
+
- `stop()`'s SIGTERM: Codex exited in about 190 ms. The leader exited within the
|
|
1410
|
+
graceful window, so there was no walk, and both the `zsh -lc` (now under PID 1)
|
|
1411
|
+
and its `sleep` survived.
|
|
1412
|
+
- A human at the terminal, `/quit` then Enter, or Ctrl-C twice: Codex exited about
|
|
1413
|
+
6 s after the keys. The `zsh -lc` was gone, but the profile's `sleep` survived
|
|
1414
|
+
under PID 1.
|
|
1415
|
+
|
|
1416
|
+
Claude 2.1.310 under the same profile started no shell that sources `.zshrc`
|
|
1417
|
+
non-interactively, and nothing survived. So the premise behind the graceful-stop
|
|
1418
|
+
exception, that the CLI's own SIGTERM shutdown ends its detached descendants, holds
|
|
1419
|
+
for Claude's hooks but not for this Codex shell: a `stop()` during the snapshot
|
|
1420
|
+
leaves it running. `tests/e2e/codex-snapshot-reap.e2e.ts` holds a snapshot open
|
|
1421
|
+
this way and checks that `teardown()` reaps it.
|
|
1422
|
+
|
|
1423
|
+
## Codex 0.164 starts an app-server daemon per `CODEX_HOME` (2026-10-25)
|
|
1424
|
+
|
|
1425
|
+
Codex 0.158.0 and later list the feature `daemon_auto_start` as stable and on (0.156.1
|
|
1426
|
+
lists it as experimental and off; 0.155.1 does not have it). With it, the Codex 0.164.0
|
|
1427
|
+
TUI can start a managed app-server daemon for its `CODEX_HOME`. Measured on macOS
|
|
1428
|
+
(Darwin 25.5) with fresh homes: 11 of 14 launches without the hook-trust bypass started
|
|
1429
|
+
one, the earliest beginning its install 1 s after launch, and none of 5 launches with
|
|
1430
|
+
the bypass did, one of them an Elwood session watched for 17 s. What decides it was not
|
|
1431
|
+
determined. The e2e suite's removed homes had left 29 of these daemons running by that
|
|
1432
|
+
evening.
|
|
1433
|
+
|
|
1434
|
+
- It installs a copy of the CLI, about 260 MB, at
|
|
1435
|
+
`$CODEX_HOME/packages/app-server-daemon/releases/<version>/bin/codex`. One such home
|
|
1436
|
+
measured 453 MB.
|
|
1437
|
+
- It runs `codex app-server --listen unix:// --analytics-default-enabled --managed-daemon`
|
|
1438
|
+
and `codex app-server daemon pid-update-loop`. Each is a session leader of its own and
|
|
1439
|
+
a child of the TUI, reparented to PID 1 when the TUI exits. The control socket is
|
|
1440
|
+
`$CODEX_HOME/app-server-control/app-server-control.sock`, pid files are under
|
|
1441
|
+
`$CODEX_HOME/app-server-daemon/`, and a socket directory and lock are under
|
|
1442
|
+
`/tmp/codex-daemon-<uid>/`. Plugin MCP servers (`node ./scripts/start-mcp.mjs`) run as
|
|
1443
|
+
its children.
|
|
1444
|
+
- The TUI then reads through it as client `codex-tui`. The daemon's rows in
|
|
1445
|
+
`logs_2.sqlite` show `account/read`, `config/read` (the project trust read),
|
|
1446
|
+
`hooks/list`, and `model/list`.
|
|
1447
|
+
- It outlives the TUI. Those 29 daemons had started 14 minutes to 1 h 49 min earlier,
|
|
1448
|
+
their homes removed seconds after, and were all still running, each holding its
|
|
1449
|
+
deleted binary. They refreshed `models_cache.json` and recreated `logs_2.sqlite`,
|
|
1450
|
+
`.sqlite-maintenance.lock`, and the plugin cache in the removed home. A removal that
|
|
1451
|
+
raced those writes failed with `ENOTEMPTY` (C-E2E-09, 1 run of 3).
|
|
1452
|
+
- `codex app-server daemon stop`, run with that `CODEX_HOME`, stopped the daemon and its
|
|
1453
|
+
MCP servers but left the updater loop running. SIGTERM ended both within 2 s, and the
|
|
1454
|
+
daemon stopped its MCP servers and removed its `/tmp/codex-daemon-<uid>/` entries.
|
|
1455
|
+
`codex app-server daemon start` started both on every run, in about 2.5 s.
|
|
1456
|
+
|
|
1457
|
+
Elwood's sessions use the user's own `CODEX_HOME` and always bypass hook trust, so a
|
|
1458
|
+
daemon there is the one the user's own Codex shares, as for a human at the terminal. The
|
|
1459
|
+
e2e suite stops whatever still runs from a sandbox or from its default home before
|
|
1460
|
+
removing it (`tests/e2e/scratch-processes.ts`). That is every process whose command line
|
|
1461
|
+
names a path inside the home, plus its descendants: SIGTERM first, then SIGKILL 3 s
|
|
1462
|
+
later. A sandbox where something will not stop is kept.
|
|
1463
|
+
`tests/e2e/scratch-processes.e2e.ts` starts a daemon with `daemon start` and checks that
|
|
1464
|
+
removing the sandbox, and exiting the run, stop it.
|
|
1465
|
+
|
|
1466
|
+
## Elwood's teardown never kills that daemon (2026-10-26)
|
|
1467
|
+
|
|
1468
|
+
While the TUI that started them runs, the daemon and its updater loop are setsid
|
|
1469
|
+
children of it, the shape of a hook in flight. So when a session's own TUI had started
|
|
1470
|
+
them, the detached walk that `kill()`, `teardown()`, and a `stop()` escalation run killed
|
|
1471
|
+
the daemon, its updater loop, and everything the daemon had started, though the daemon
|
|
1472
|
+
serves every Codex on the home, the user's own included. The walk now never descends into
|
|
1473
|
+
one (PRD §5.3, C-LIFE-10): a process whose `ucomm` (short name) is `codex`, that leads its
|
|
1474
|
+
own group, whose parent is named `codex` or is PID 1, and whose `comm` has
|
|
1475
|
+
`/packages/app-server-daemon/` in it. On macOS `comm` is `argv[0]`, which Codex sets to
|
|
1476
|
+
the full path; it reads that way even after the home is removed.
|
|
1477
|
+
|
|
1478
|
+
`comm` is read, one `ps -o comm= -p <pid>` per process and at most four at once, only for
|
|
1479
|
+
the processes that pass the other tests and that the walk can reach: the session's TUI and
|
|
1480
|
+
its descendants. None starts past the table read's deadline; the read then fails as a timed
|
|
1481
|
+
out one does. Every extra read costs a walk whose deadline, three seconds when these were
|
|
1482
|
+
measured, must hold on a loaded host:
|
|
1483
|
+
|
|
1484
|
+
- `comm` in every table read doubled its cost (94 to 200 ms at a load of about 220): 4 of
|
|
1485
|
+
9 real-process reap tests failed with `ETIMEDOUT`, none on `origin/main`.
|
|
1486
|
+
- Given several pids, macOS `ps` reads every process on the host (230 to 370 ms at a
|
|
1487
|
+
load of about 25): with one `ps -p` for all candidates, a real Codex `teardown()`
|
|
1488
|
+
failed in 5 of 7 runs, none of 2 on `origin/main`.
|
|
1489
|
+
- One `ps` per candidate anywhere on the host took 4 to 9 ms from a shell, but 120 to
|
|
1490
|
+
870 ms when spawned by the process tearing the session down. The user's own daemon was
|
|
1491
|
+
always a candidate, so every read paid that again: a real Codex `teardown()` failed in
|
|
1492
|
+
2 of 5 runs, none of 5 on `origin/main`.
|
|
1493
|
+
|
|
1494
|
+
A walk remembers each daemon it found, by pid, parent, and group, so when the session's
|
|
1495
|
+
TUI started one, the walk's first read pays its path reads, and no later read does unless
|
|
1496
|
+
its parent changed. A candidate no read has confirmed, such as the TUI's fork before its
|
|
1497
|
+
`exec`, is read again on every read until one does or it is gone. A walk with no
|
|
1498
|
+
candidate under its TUI runs the same single `ps` per read as before. In 3
|
|
1499
|
+
real Codex `teardown()`s, alternated with 3 on `origin/main`, it ran no path read. One run
|
|
1500
|
+
on each side failed at the deadline, when the host's 3,700 processes made a single table
|
|
1501
|
+
read take 1.6 to 2.1 s; the other reads took 350 to 540 ms.
|
|
1502
|
+
|
|
1503
|
+
No real-Codex e2e proves this: in 8 runs of Codex 0.164.0 under Elwood, from fresh
|
|
1504
|
+
homes, with and without the hook-trust bypass and the update check, with holds of up to
|
|
1505
|
+
40 s, none started the daemon. `tests/unit/reap-detached-real.test.ts` builds the
|
|
1506
|
+
measured shape from real processes instead, with its install in place and removed.
|
|
1507
|
+
|
|
1508
|
+
The TUI forks the daemon as a `codex` group leader of its own and then `exec`s it, so a
|
|
1509
|
+
path read can come between the two, and the walk can then stop the group after the
|
|
1510
|
+
`exec`. A stopped process cannot `exec`, so the kill pass goes by the latest read: a group
|
|
1511
|
+
it shows holding the daemon, or a process not stopped that could still become it, gets
|
|
1512
|
+
SIGCONT, not SIGKILL. A leader killed by something else while the walk holds it leaves
|
|
1513
|
+
that stopped daemon under PID 1, out of the leader's reach, so each group the walk stopped
|
|
1514
|
+
is a root of its own for the path reads. `tests/unit/shared-daemon-real.test.ts`
|
|
1515
|
+
reproduces both with a real process: Node cannot `exec`, but setting `process.title`
|
|
1516
|
+
changes what macOS `ps` reads as `comm` (`ucomm` stays the executable's name).
|
|
1517
|
+
|
|
1518
|
+
A descendant can set its `comm` to the daemon's path (Node does by setting
|
|
1519
|
+
`process.title`), and a binary named `codex` that leads its own group is then spared as
|
|
1520
|
+
the daemon would be. That gives it nothing it lacks: the walk follows parent pids from the
|
|
1521
|
+
leader, so a descendant whose parent exits first is out of its reach. Measured on
|
|
1522
|
+
`origin/main` (2026-10-26): a setsid hook that backgrounds `/bin/sleep 300` and exits
|
|
1523
|
+
leaves the sleep under PID 1 before `kill()`, which resolves with the sleep still running.
|
|
1524
|
+
|
|
1525
|
+
## `ps` prints a process's short name raw (2026-10-26)
|
|
1526
|
+
|
|
1527
|
+
To recognize Codex's daemon (above), each table read also lists every process's short
|
|
1528
|
+
name (`ucomm`), and `ps` prints it raw. A process whose name is `a`, a newline, and
|
|
1529
|
+
`1 5000 1 S ` reads as two rows, the second a forged process 1 whose parent is 5000
|
|
1530
|
+
(measured on macOS 26). `comm` and `ps -c` escape it as `\012`, but `comm` in every table
|
|
1531
|
+
read doubled its cost (94 to 200 ms at a load of about 220). So each row ends with its pid
|
|
1532
|
+
again (`-o pid=,ppid=,pgid=,stat=,lstart=,ucomm=,pid=`), and a read with any line that
|
|
1533
|
+
is not one whole row, or one pid twice, is unreadable: the walk fails rather than act on it.
|
|
1534
|
+
|
|
706
1535
|
## Claude "Not logged in" is a distinct re-auth banner from "Login expired"
|
|
707
1536
|
|
|
708
1537
|
Claude's mid-session re-auth banners are NOT one string. Besides the lapsed/revoked
|
|
@@ -747,6 +1576,13 @@ new CLI version or native capture was verified.
|
|
|
747
1576
|
means observed: each write first awaits `terminal.settled()`, because a dialog can
|
|
748
1577
|
be received before it is rendered. xterm parses the first write after user input
|
|
749
1578
|
synchronously and later ones on a timer, so that window is real and input-dependent.
|
|
1579
|
+
- Claude 2.1.310 shows a message submitted during a running turn as queued. It renders
|
|
1580
|
+
as `❯ <message>` above the composer, with `ctrl+x ctrl+s to send now` beneath it, and
|
|
1581
|
+
the composer reads `❯ Press up to edit queued messages`. In a turn told not to use
|
|
1582
|
+
tools it was still queued 10 s later, with the reply still streaming. A check that the
|
|
1583
|
+
message left the composer must read only the last `❯` line, because the queued row
|
|
1584
|
+
also starts with `❯` and holds the message. Observed with `sendGuidance` in a private
|
|
1585
|
+
config dir, 2026-10-26 (C-API-37).
|
|
750
1586
|
- **Writing a literal ESC byte in source is fragile** — it can be silently
|
|
751
1587
|
stripped by an editor/pipeline (this once neutered an interrupt e2e). Always
|
|
752
1588
|
write the escape sequence form (``), never a raw ESC character.
|
|
@@ -836,8 +1672,9 @@ Four real-PTY edges were invisible to the unit fixtures:
|
|
|
836
1672
|
- Codex 0.153.3 can paint the numbered menu before its input loop accepts the first
|
|
837
1673
|
hotkey. A successful PTY write therefore does not prove the dialog cleared. The
|
|
838
1674
|
responder runs one bounded retry operation while a safe numbered option remains
|
|
839
|
-
on the current rendered frame
|
|
840
|
-
|
|
1675
|
+
on the current rendered frame. A frame showing the option does not prove the key
|
|
1676
|
+
was ignored, so a retry can still reach the composer; it is Enter alone, or it
|
|
1677
|
+
waits for new output (see "Codex numbered trust retries"). Some PTY hosts close their input side before process completion and
|
|
841
1678
|
then return `EIO` from `setRawMode(false)`; contain only terminal-gone restoration
|
|
842
1679
|
errors because that host is already the sole remaining terminal-mode owner.
|
|
843
1680
|
- Interactive login-shell probes can give their external command control of the
|
|
@@ -893,11 +1730,30 @@ five rows in ~6s; real Codex six rows in ~5s. Two environment facts matter:
|
|
|
893
1730
|
- When Elwood itself runs nested inside a Claude Code session (`CLAUDECODE` and
|
|
894
1731
|
`CLAUDE_CODE_*` set), claude >= 2.1.201 skips transcript persistence for the
|
|
895
1732
|
nested instance; a headless run then fails `wait_timeout` ("transcript did not
|
|
896
|
-
catch up after ready")
|
|
897
|
-
|
|
898
|
-
-
|
|
899
|
-
|
|
900
|
-
|
|
1733
|
+
catch up after ready"). The e2e helper strips those variables; do the same
|
|
1734
|
+
for manual smoke runs.
|
|
1735
|
+
- **Claude 2.1.310 creates the transcript file only when the first turn is
|
|
1736
|
+
submitted (2026-10-25).** `SessionStart` already carries `transcript_path`, but
|
|
1737
|
+
the file was still absent 4 s after `ready` in every probe and appeared about
|
|
1738
|
+
1 s after the first prompt. Elwood used to report each 500 ms poll of that
|
|
1739
|
+
missing file as `transcript_read_error (ENOENT)`: 12–13 warnings per session
|
|
1740
|
+
with a 4 s idle, and a flood when nested persistence never creates the file.
|
|
1741
|
+
The Claude cursor now treats ENOENT as "not created yet" until a stat has
|
|
1742
|
+
seen the file. ENOENT after that, and any other read error, still warns.
|
|
1743
|
+
Codex never warned here: its shared cursor's `fileSize` already maps ENOENT
|
|
1744
|
+
to an empty file. C-CLAUDE-24.
|
|
1745
|
+
- **That same mapping hid a removed Codex transcript (2026-10-25).** On Codex
|
|
1746
|
+
0.164.0, `SessionStart` arrives only with the first submission, and its
|
|
1747
|
+
`transcript_path` is a realpath (`/private/var/…/sessions/YYYY/MM/DD/rollout-…jsonl`
|
|
1748
|
+
under `CODEX_HOME`); the file exists by the time that turn's `task_complete` is
|
|
1749
|
+
committed. Deleting it afterwards made the next poll read size 0, which the
|
|
1750
|
+
cursor took for a truncation, so nothing warned and the session's later
|
|
1751
|
+
transcript activity was silently gone: no `transcript_read_error` within 10 s
|
|
1752
|
+
on `main`. The Codex scan now reads through the shared cursor's existence
|
|
1753
|
+
policy, as Claude's does: until a stat has seen the file, ENOENT waits silently;
|
|
1754
|
+
after that, every poll, and the final flush, warns with `ENOENT` while the file is
|
|
1755
|
+
missing. A file that reappears is read with the
|
|
1756
|
+
existing truncation rule. C-CODEX-23.
|
|
901
1757
|
|
|
902
1758
|
`elwood sessions` reports `live` from the presence of a `.sock` file in the
|
|
903
1759
|
session's stable socket home. A connect probe was rejected on purpose: the
|
|
@@ -905,6 +1761,72 @@ bridge treats any connection that closes without a frame as a malformed request
|
|
|
905
1761
|
and emits `hookError`/`activity` on the live owner, so presence is the only
|
|
906
1762
|
side-effect-free signal. C-CLI-21 through C-CLI-24.
|
|
907
1763
|
|
|
1764
|
+
## Probe shell startup under concurrency (2026-10-25)
|
|
1765
|
+
|
|
1766
|
+
Preflight probes run through the user's interactive login shell (§4.2), so their wall
|
|
1767
|
+
time is the user's profile plus the CLI. On a 16-CPU host already at load 35–90 with a
|
|
1768
|
+
typical developer profile (oh-my-zsh with seven plugins, `nvm.sh`, `fnm env`,
|
|
1769
|
+
`brew shellenv`), measured with Claude Code 2.1.310:
|
|
1770
|
+
|
|
1771
|
+
- `claude --version` without a shell took 0.01–0.12 s at 1, 4, 8 and 16 concurrent
|
|
1772
|
+
runs. There is no `~/.claude.json` lock, autoupdate check or keychain wait on this
|
|
1773
|
+
path. An empty `CLAUDE_CONFIG_DIR` changed nothing, and `zsh -l -i -c true` costs
|
|
1774
|
+
the same as the real probe.
|
|
1775
|
+
- The interactive login shell alone took 2.3–7.6 s for one probe, about 2 s each at 4
|
|
1776
|
+
concurrent, about 5 s at 8, 7–19 s at 16 (p50 8–18 s), and 15.5–30.9 s at 32. A
|
|
1777
|
+
trace of one startup put about 2.5 s in `nvm.sh`'s command substitutions and 1.2 s
|
|
1778
|
+
in `compinit`/`compdef`. Fresh or shared working directories made no difference, so
|
|
1779
|
+
this is CPU saturation from fork-heavy startup files, not a shared lock.
|
|
1780
|
+
- With an empty private `ZDOTDIR`, the same probe took 0.03–0.23 s at 16.
|
|
1781
|
+
|
|
1782
|
+
The original 15 s bound covered both phases. 16 concurrent `elwood run`s therefore
|
|
1783
|
+
failed `claude_start_failed` ("`claude --version` failed", `ETIMEDOUT`) at 17.5–18.9 s:
|
|
1784
|
+
16 of 16 in one of three fleets at load 68, and 14 of 16 in the original report. The
|
|
1785
|
+
probe shell now writes `elwood-probe-command-start` on descriptor 3, a fourth pipe the
|
|
1786
|
+
runner opens, just before the command, then closes it so the command does not inherit it.
|
|
1787
|
+
Writing it to stderr was the first version; a review pointed out that a profile running
|
|
1788
|
+
`exec 2>&1` or `exec 2>/dev/null` would hide it. Real zsh with this host's profile, and
|
|
1789
|
+
zsh with either redirect in a private `ZDOTDIR`, delivered the line on descriptor 3. The
|
|
1790
|
+
bash check ran `bash --rcfile … -i`, a non-login shell, which keeps descriptor 3 too; macOS's
|
|
1791
|
+
bash as an interactive login shell does not (next section).
|
|
1792
|
+
The profile gets a 60 s launch bound and the CLI keeps 15 s from that line (C-PERF-05).
|
|
1793
|
+
With the stderr version, three 16-run fleets at load 48–74 passed preflight 48 of 48;
|
|
1794
|
+
the bounds are the same on descriptor 3. The real-shell e2e
|
|
1795
|
+
uses a private `ZDOTDIR` whose `.zshrc` sleeps 4 s with a 2 s command bound; `main`
|
|
1796
|
+
fails it with that same `ETIMEDOUT`.
|
|
1797
|
+
|
|
1798
|
+
## Probe start line under macOS bash 3.2 (2026-10-25)
|
|
1799
|
+
|
|
1800
|
+
macOS's `/bin/bash` 3.2.57, and `/bin/sh`, which is the same bash in sh mode, close every
|
|
1801
|
+
inherited descriptor above stderr when started with both `-l` and `-i`, even with
|
|
1802
|
+
`--noprofile --norc`. Pipes a Node parent opened on 3, 9, 10 and 19 were all closed before
|
|
1803
|
+
the `-c` script ran. `-l` alone or `-i` alone keeps them, and so does Linux's bash 5.2.15
|
|
1804
|
+
(Debian, in the `node:24` image) as an interactive login shell. zsh 5.9 and ksh 93u+ keep
|
|
1805
|
+
them; dash keeps 3–9 but cannot name a descriptor of 10 or more, so a higher number is no
|
|
1806
|
+
fix. With descriptor 3 as the only channel, every probe through those shells failed `login
|
|
1807
|
+
shell exited with status 0 before starting the probe command` although its command ran,
|
|
1808
|
+
and so did `elwood`'s agent detection. Any macOS user whose login shell is bash could not
|
|
1809
|
+
start a session. GitHub's `macos-latest` runner user's shell is bash.
|
|
1810
|
+
|
|
1811
|
+
The probe script now tests the write, then keeps descriptor 3 open through the e2e home
|
|
1812
|
+
guard (PRD §12), whose stop line takes the same path, and closes it only before the command:
|
|
1813
|
+
`{ echo L >&3; } 2>/dev/null || echo L; <guard>; exec 3>&-; <command>`. zsh, ksh, macOS
|
|
1814
|
+
and Debian dash, non-login bash 3.2 and bash 5.2 took descriptor 3 when it was open and
|
|
1815
|
+
stdout when it was closed, and none printed the failed redirection's error. The fallback
|
|
1816
|
+
is stdout, not stderr. Under real `bash -l -i` with a
|
|
1817
|
+
`~/.bash_profile` of `exec 2>/dev/null` or `exec 2>&1`, the line arrived on stdout and the
|
|
1818
|
+
probe passed. A stderr fallback would have been discarded by the first and moved into
|
|
1819
|
+
stdout by the second. A profile that hid stdout would hide the command's own output and
|
|
1820
|
+
fail the probe regardless. The runner removes the line from captured stdout. A profile
|
|
1821
|
+
that runs `exec 3>&-` itself now falls back too. Only a profile that reopens descriptor 3
|
|
1822
|
+
onto something else still loses the line. Because stdout is now watched for every probe,
|
|
1823
|
+
the line ends in a random per-process token: a profile that printed the bare
|
|
1824
|
+
`elwood-probe-command-start` would otherwise start the command's bound while the profile
|
|
1825
|
+
was still loading. `tests/unit/probe-closed-descriptor.test.ts` covers this with a
|
|
1826
|
+
`/bin/sh` stand-in that closes descriptor 3 on every host, and with real `/bin/bash -l -i`
|
|
1827
|
+
where `/bin/bash` is 3.x. The nine tests of its first version all fail on `main` at
|
|
1828
|
+
613b3ea5.
|
|
1829
|
+
|
|
908
1830
|
## Testing against the real CLIs
|
|
909
1831
|
|
|
910
1832
|
- `test:e2e` runs **serially** (`--test-concurrency=1`): Codex config-file
|
|
@@ -916,6 +1838,115 @@ side-effect-free signal. C-CLI-21 through C-CLI-24.
|
|
|
916
1838
|
CLI SHOULD have a real-CLI e2e. Every fact in this file is one a unit test could
|
|
917
1839
|
not have caught.
|
|
918
1840
|
|
|
1841
|
+
### Claude under a private `CLAUDE_CONFIG_DIR` (2.1.310, 2026-10-26)
|
|
1842
|
+
|
|
1843
|
+
With `CLAUDE_CONFIG_DIR` set, Claude keeps everything there: its global config is
|
|
1844
|
+
`$CLAUDE_CONFIG_DIR/.claude.json` instead of `~/.claude.json`, and settings,
|
|
1845
|
+
`projects/` transcripts, history and backups sit beside it. `claude auth status`
|
|
1846
|
+
reports both paths (`configDirectory`, `projectsDirectory`). Without the variable,
|
|
1847
|
+
every directory Claude runs in gets an entry in `~/.claude.json` and a transcript
|
|
1848
|
+
folder under `~/.claude/projects/`, so each real-Claude e2e scratch project used
|
|
1849
|
+
to leave both behind.
|
|
1850
|
+
|
|
1851
|
+
An empty config dir is logged out (`loggedIn: false`): Claude does not read the
|
|
1852
|
+
default `Claude Code-credentials` keychain item for it. It does read a
|
|
1853
|
+
`$CLAUDE_CONFIG_DIR/.credentials.json` holding the keychain item's JSON. A copy with
|
|
1854
|
+
`refreshToken` and `refreshTokenExpiresAt` removed logs in (`authMethod:
|
|
1855
|
+
"claude.ai"`), creates no new keychain item, and cannot refresh, so it can never
|
|
1856
|
+
rotate the user's own refresh token. It works until the access token's `expiresAt`,
|
|
1857
|
+
which is why `tests/e2e/claude-home.ts` copies it afresh in each process.
|
|
1858
|
+
|
|
1859
|
+
A set config dir has a keychain item of its own (claude 2.1.310, 2026-10-26):
|
|
1860
|
+
`Claude Code-credentials-` followed by the first eight hex digits of the SHA-256 of
|
|
1861
|
+
the `CLAUDE_CONFIG_DIR` value as written, NFC-normalized, even when it names
|
|
1862
|
+
`~/.claude`. An empty value counts as unset. With `CLAUDE_CONFIG_DIR` set to a fresh
|
|
1863
|
+
dir, `claude auth status` reported `loggedIn: true` exactly while an item of that
|
|
1864
|
+
name existed. The item wins over a `.credentials.json` in the same dir: with both
|
|
1865
|
+
holding different logins, Claude reported the item's. So a user with the variable
|
|
1866
|
+
set may have their login only in that item, and `tests/e2e/claude-home.ts` reads it
|
|
1867
|
+
from there first. The 2.1.310 binary also lets `CLAUDE_SECURESTORAGE_CONFIG_DIR`
|
|
1868
|
+
replace the setting the item is named for (read from the code, not exercised). When
|
|
1869
|
+
it is set, even to an empty value, it decides the name: empty means the default item,
|
|
1870
|
+
anything else that value's hash. Only when it is unset does `CLAUDE_CONFIG_DIR` decide.
|
|
1871
|
+
Claude also passes the variable on to the teammates it spawns. So a Claude started with
|
|
1872
|
+
a private `CLAUDE_CONFIG_DIR` would still use the user's item while the variable is
|
|
1873
|
+
inherited. `tests/e2e/claude-home.ts` reads the user's login from the item that
|
|
1874
|
+
variable names, then removes it from the suite's environment.
|
|
1875
|
+
|
|
1876
|
+
An `ANTHROPIC_API_KEY` in the environment logs any config dir in on its own
|
|
1877
|
+
(`authMethod: "api_key"`), so check a login with it unset. In the TUI, whether that
|
|
1878
|
+
key is used is the global config's `customApiKeyResponses`, which the e2e copy keeps.
|
|
1879
|
+
|
|
1880
|
+
`ready` does not show that Claude got neither credential variable (Claude 2.1.310,
|
|
1881
|
+
2026-10-26). With the copied login, an invalid `ANTHROPIC_API_KEY` from the launch
|
|
1882
|
+
shell's startup files keeps the TUI from `ready`, but an invalid `ANTHROPIC_AUTH_TOKEN`
|
|
1883
|
+
does not: Claude reaches `ready` with it set. Both variables can also come from the
|
|
1884
|
+
developer's own startup files, after Elwood's environment is handed over. So C-E2E-23
|
|
1885
|
+
unsets both in the launch shell, and a `SessionStart` project hook records which of the
|
|
1886
|
+
two Claude's own environment holds (`tests/e2e/claude-credential-hook.ts`).
|
|
1887
|
+
|
|
1888
|
+
The private dir carries none of the user's `settings.json`, so it shows what a fresh
|
|
1889
|
+
install does: the bypass-permissions dialog appears unless
|
|
1890
|
+
`skipDangerousModePermissionPrompt` is set (see above), and the footer shows Claude's
|
|
1891
|
+
default permission mode. C-E2E-23.
|
|
1892
|
+
|
|
1893
|
+
### A rate-limited Codex turn stays running for minutes (2026-10-25)
|
|
1894
|
+
|
|
1895
|
+
Codex 0.164.0 retries a `429 Too Many Requests` after the server's retry delay. In
|
|
1896
|
+
Codex's own log (`~/.codex/logs_2.sqlite`, target `codex_core::responses_retry`)
|
|
1897
|
+
the delay for four e2e turns on one account's default model was 110 to 509 s. All
|
|
1898
|
+
four retries were scheduled for the same instant, the end of a usage window. While
|
|
1899
|
+
it waits, Codex paints
|
|
1900
|
+
`• Reconnecting... 1/5 (44s • esc to interrupt)` over
|
|
1901
|
+
`└ Exceeded retry limit, last status: 429 Too Many Requests`. The rollout ends at
|
|
1902
|
+
the user message with no reply, and no `Stop` hook fires. Elwood correctly stays
|
|
1903
|
+
`running`, because the turn has not ended. A real-turn e2e that inherits the
|
|
1904
|
+
developer's `~/.codex` shares that model's limit with every Codex session on the
|
|
1905
|
+
account, so it can wait out a backoff no test timeout covers. A test avoids this by
|
|
1906
|
+
pinning a private `CODEX_HOME` and a small model. `tests/e2e/resume-ready-codex.e2e.ts`
|
|
1907
|
+
does, and bounds its readiness, turn and submission waits so a stuck step fails with
|
|
1908
|
+
its label and the screen.
|
|
1909
|
+
|
|
1910
|
+
A private home whose `config.toml` points
|
|
1911
|
+
`model_provider` at a local `[model_providers.<id>]` with `base_url =
|
|
1912
|
+
"http://127.0.0.1:<port>/v1"` and `wire_api = "responses"` reproduces this
|
|
1913
|
+
without quota. That provider answers every request with 429 and `retry-after: 300`.
|
|
1914
|
+
Codex still lists models from it before the first turn. C-API-28.
|
|
1915
|
+
|
|
1916
|
+
### Concurrent cold starts race Codex's state migrations (2026-10-25)
|
|
1917
|
+
|
|
1918
|
+
Codex 0.164.0 creates and migrates its SQLite state under `CODEX_HOME` at launch
|
|
1919
|
+
(61 migrations in `state_5.sqlite`, 2 in `goals_1.sqlite`), and nothing serializes
|
|
1920
|
+
those migrations across processes. When two Codex processes start together in a home
|
|
1921
|
+
whose databases are not yet migrated, both try to apply the same migration. The loser
|
|
1922
|
+
exits 1 with `Codex couldn't start because its local database appears to be damaged`,
|
|
1923
|
+
and the cause names the collision, such as `table thread_goals already exists` or
|
|
1924
|
+
`duplicate column name`. Six different migrations collided across the runs below. The
|
|
1925
|
+
exit landed 2 to 10 s after spawn. In every Elwood run that was after `startCodex` had
|
|
1926
|
+
resolved, so Elwood reported `running` → `exited` (`terminal_exited`) and the session
|
|
1927
|
+
never reached `ready`.
|
|
1928
|
+
|
|
1929
|
+
C-CODEX-14's e2e started its two sessions with `Promise.all` in a fresh sandbox. The
|
|
1930
|
+
same start lost a session in 4 of 5 probes and in 5 of 6 e2e runs. The test's readiness
|
|
1931
|
+
poll then ran out its full minute, and the surviving session, which `Promise.all`
|
|
1932
|
+
dropped, kept the test file running. Replaying Elwood's exact launch command through a
|
|
1933
|
+
bare node-pty, with no Elwood code, crashed one of the two in 3 of 4 runs. One launch
|
|
1934
|
+
alone never crashed. Bare pairs with only `--sandbox`, `--ask-for-approval`, and `--cd`
|
|
1935
|
+
never crashed in 16 runs. Adding `-c model_reasoning_effort="low"` alone or
|
|
1936
|
+
`--dangerously-bypass-hook-trust` alone crashed 3 of 3, and `-c features.hooks=true`
|
|
1937
|
+
alone crashed 0 of 3. The runs do not show why the bare pairs escaped, so treat any
|
|
1938
|
+
pair of simultaneous launches as exposed.
|
|
1939
|
+
|
|
1940
|
+
A single launch at load 50 finished every migration about 1.5 s after spawn, as its
|
|
1941
|
+
composer painted. The composer does not prove migration is done: a launch killed one
|
|
1942
|
+
second after its composer appeared left migrations for the next pair, which raced in 1
|
|
1943
|
+
of 4 runs. The e2e now starts the second session after the first reports `ready`, which
|
|
1944
|
+
on a cold start is the 10 s deadline from the first frame. The concurrency it tests is
|
|
1945
|
+
the model switch.
|
|
1946
|
+
|
|
1947
|
+
Production homes already hold migrated databases. The exposure is the first launch of
|
|
1948
|
+
a Codex release that adds a migration, if several sessions start at that moment.
|
|
1949
|
+
|
|
919
1950
|
### Codex commentary and final-answer phases (2026-10-20)
|
|
920
1951
|
|
|
921
1952
|
The reported completed `response_item` fixture retains assistant text in an
|
|
@@ -955,6 +1986,17 @@ partial model dialog is insufficient. The sanitized Claude frame is retained in
|
|
|
955
1986
|
`tests/fixtures/claude-2.1.278/model-cancelled.txt`. With this recognition fix,
|
|
956
1987
|
Claude also completed automated `listModels` and returned five rows (C-API-55).
|
|
957
1988
|
|
|
1989
|
+
### Idle CLIs do not repaint (2026-10-25)
|
|
1990
|
+
|
|
1991
|
+
A probe started real sessions through Elwood, waited for `ready` plus 3 s, and then
|
|
1992
|
+
counted PTY output chunks while the session sat idle. Codex 0.164.0 emitted none in
|
|
1993
|
+
15 s idle, and none in the 10 s after `listModels` opened and cancelled its picker.
|
|
1994
|
+
Claude Code 2.1.310 emitted one chunk in each window. Clearing a model-dialog hold
|
|
1995
|
+
therefore cannot wait for a run of new frames, because on Codex none arrive. The
|
|
1996
|
+
evidence has to be the same completed frame read over time. Readings count at
|
|
1997
|
+
least 40 ms apart, so neither bunched readings nor a fast burst of repaints can
|
|
1998
|
+
release a hold early (C-API-55).
|
|
1999
|
+
|
|
958
2000
|
|
|
959
2001
|
### Cursor provenance for Codex trust clearance
|
|
960
2002
|
|
|
@@ -979,6 +2021,13 @@ only the production clear write made both tests retain the image and text and fa
|
|
|
979
2021
|
This verifies an automation-owned idle composer, not cleanup across caller edits
|
|
980
2022
|
or blocking dialogs. Codex clipboard text is restored after attachment.
|
|
981
2023
|
|
|
2024
|
+
codex-cli 0.164 (2026-10-26) saves the pasted image as `codex-clipboard-<suffix>.png`
|
|
2025
|
+
in its temp dir when the `[Image #1]` chip appears, and clearing the draft leaves that
|
|
2026
|
+
file in place. The draft is never submitted, so no transcript names the copy, and a
|
|
2027
|
+
session removes only the copies its transcript names. So a cancelled Codex image draft
|
|
2028
|
+
leaves its copy in the temp dir. `composer-cleanup.e2e.ts` gives its Codex a `TMPDIR`
|
|
2029
|
+
inside the sandbox home, asserts the copy lands there, and removes it with the sandbox.
|
|
2030
|
+
|
|
982
2031
|
|
|
983
2032
|
Native startup protocol replies also emit xterm `onData`. The actual-session
|
|
984
2033
|
image-cancellation proof exposed this on both adapters: treating those replies as
|
|
@@ -1167,6 +2216,41 @@ bounds, not acceptance or post-acceptance behavior. The temporary Codex auth
|
|
|
1167
2216
|
copy was removed after teardown.
|
|
1168
2217
|
|
|
1169
2218
|
|
|
2219
|
+
## Claude's temp root (Claude 2.1.310, 2026-10-26)
|
|
2220
|
+
|
|
2221
|
+
Claude keeps its temp files under a root: `CLAUDE_CODE_TMPDIR` when set, else `/tmp`, so
|
|
2222
|
+
the macOS `TMPDIR` does not move it. Measured with real sessions started through Elwood:
|
|
2223
|
+
|
|
2224
|
+
- Under `<root>/claude-<uid>/`, Claude makes one folder per working directory, named for
|
|
2225
|
+
the directory's real path with each character other than a letter or digit replaced by
|
|
2226
|
+
`-`. It holds a folder per session with that session's `scratchpad/`. The folder is
|
|
2227
|
+
still there after `stop()` and after `teardown()`; nothing in Claude removes it. Every
|
|
2228
|
+
e2e scratch project left one in the shared `/tmp/claude-<uid>` until the suite gave each
|
|
2229
|
+
e2e process a private `CLAUDE_CODE_TMPDIR` (C-E2E-23).
|
|
2230
|
+
- Claude's messaging socket is `<root>/cc-socks/<pid>.sock`. The 2.1.310 bundle uses that
|
|
2231
|
+
path only while it is at most 103 bytes, and otherwise falls back to
|
|
2232
|
+
`/tmp/cc-socks-<uid>/<pid>.sock`, so a long root moves the socket back into the shared
|
|
2233
|
+
`/tmp`. The e2e root's prefix is short for that reason.
|
|
2234
|
+
- Claude, run directly in a PTY, removes its socket when it exits on SIGTERM, SIGHUP,
|
|
2235
|
+
`/exit`, or Ctrl-C. A SIGKILL leaves it: after `teardown()`, which SIGKILLs a live session,
|
|
2236
|
+
`/tmp/cc-socks/<pid>.sock` remained for the dead pid. After `stop()`, whose SIGTERM
|
|
2237
|
+
Claude exits on, it did not.
|
|
2238
|
+
|
|
2239
|
+
## Codex keeps a temp copy of every pasted image (codex-cli 0.164.0, 2026-10-26)
|
|
2240
|
+
|
|
2241
|
+
On Ctrl+V with an image on the clipboard, Codex writes the image to
|
|
2242
|
+
`$TMPDIR/codex-clipboard-<suffix>.png` before the `[Image #N]` chip appears, and nothing
|
|
2243
|
+
in Codex ever removes it: a real attach left the file in place after the turn finished
|
|
2244
|
+
and after `teardown()`. The suffix is six random characters on 0.164.0; an earlier
|
|
2245
|
+
rollout on the same machine names a UUID form (`codex-clipboard-<uuid>.png`). When the message is submitted,
|
|
2246
|
+
the rollout records the path twice: as `{"type":"local_image","path":...}` in the
|
|
2247
|
+
content of an `event_msg` `item_completed` `UserMessage`, and as
|
|
2248
|
+
`<image name=[Image #1] path="...">` text beside the inline `input_image` data in the
|
|
2249
|
+
`response_item` message. The image itself travels inline, so nothing reads the file
|
|
2250
|
+
after submission. Once the CLI exits, Elwood removes the copies the session's transcript
|
|
2251
|
+
names directly in Elwood's own temp directory (C-API-46). A Codex whose login shell sets
|
|
2252
|
+
another `TMPDIR` keeps its copies there, and Elwood does not remove them.
|
|
2253
|
+
|
|
1170
2254
|
## Cached image recovery frames (2026-10-01)
|
|
1171
2255
|
|
|
1172
2256
|
The existing captured image-chip and staged-text layouts establish live composer
|
|
@@ -1234,6 +2318,78 @@ profile timed out before `codex --version` at 15 seconds; the same pinned binary
|
|
|
1234
2318
|
with an empty private profile directory returned its version in 52 ms. This
|
|
1235
2319
|
preflight failure submitted no model turn. Parent environment values stay intact.
|
|
1236
2320
|
|
|
2321
|
+
### Claude named-session composer rule (2026-10-24)
|
|
2322
|
+
|
|
2323
|
+
`claude --name` (Elwood's `name` option; Coal Harbor names every agent) insets the
|
|
2324
|
+
name into the composer's top rule on Claude Code 2.1.310: `──── Question Asker ─`.
|
|
2325
|
+
The rule below the composer stays plain, and the terminal title becomes
|
|
2326
|
+
`✳ Question Asker` instead of `✳ Claude Code`. Composer recognition required plain
|
|
2327
|
+
rules on both sides, so trust clearance and empty-input observation never matched a
|
|
2328
|
+
named session. In an untrusted folder, autotrust answered the trust prompt but the
|
|
2329
|
+
session stayed `blocked` on the idle composer; in a trusted folder it reached `ready`
|
|
2330
|
+
through hooks and hid the gap. The top rule may now carry one inset label
|
|
2331
|
+
(`src/claude/composer/border.ts`). See `tests/fixtures/claude-2.1.310/`.
|
|
2332
|
+
|
|
2333
|
+
### Claude permission-mode footers (2026-10-25)
|
|
2334
|
+
|
|
2335
|
+
Startup-only captures from Claude Code 2.1.310 launched with each
|
|
2336
|
+
`--permission-mode`, with no model prompt sent. The footer is the row directly
|
|
2337
|
+
under the composer's bottom rule. With the developer's settings
|
|
2338
|
+
(`editorMode: "vim"`, `verbose: true`):
|
|
2339
|
+
|
|
2340
|
+
```
|
|
2341
|
+
-- INSERT -- ⏸ manual mode on · ← for agents 0 tokens
|
|
2342
|
+
-- INSERT -- ⏸ plan mode on (shift+tab to cycle) · ← for agents 0 tokens
|
|
2343
|
+
-- INSERT -- ⏵⏵ accept edits on (shift+tab to cycle) · ← for agents 0 tokens
|
|
2344
|
+
-- INSERT -- ⏵⏵ auto mode on (shift+tab to cycle) · ← for agents 0 tokens
|
|
2345
|
+
-- INSERT -- ⏵⏵ don't ask on (shift+tab to cycle) · ← for agents 0 tokens
|
|
2346
|
+
-- INSERT -- ⏵⏵ bypass permissions on (shift+tab to cycle) · ← for agents 0 tokens
|
|
2347
|
+
```
|
|
2348
|
+
|
|
2349
|
+
The effort level (`● high · /effort`) sits on the next row. With
|
|
2350
|
+
`editorMode: "normal"` and `verbose: false` (passed through `--settings`, never
|
|
2351
|
+
written to user config) there is no `-- INSERT --` and no token count, and the
|
|
2352
|
+
effort level moves to the footer's right edge:
|
|
2353
|
+
`⏸ manual mode on · ? for shortcuts · ← for agents ● high · /effort`. Only
|
|
2354
|
+
manual mode lacks `(shift+tab to cycle)`, and in this layout only manual mode
|
|
2355
|
+
shows `? for shortcuts`. A `default` launch renders `manual mode`. Vim normal mode,
|
|
2356
|
+
after an Escape at the idle composer, drops the `-- INSERT --` prefix. Each
|
|
2357
|
+
effort level has its own glyph: `○ low`, `◐ medium`, `● high`, `◉ xhigh`,
|
|
2358
|
+
`◈ max`. The captures only show `0 tokens`. The binary formats counts with
|
|
2359
|
+
`Intl.NumberFormat` compact notation, then lowercases them: `999`, `1.0k` (or
|
|
2360
|
+
`1k` where it strips `.0`), `12.3k`, `2.1m`. Counts are never digit-grouped.
|
|
2361
|
+
|
|
2362
|
+
Some pieces are transient. Release notes embedded in the 2.1.310 binary say
|
|
2363
|
+
`← for agents` hides 10 seconds after a session opens. In the captures it was still present at 13 s
|
|
2364
|
+
and gone by 27 s. Late frames also lost the effort level: no effort row in the
|
|
2365
|
+
vim layout, an empty right edge in the plain one.
|
|
2366
|
+
|
|
2367
|
+
Elwood accepted only the 2.1.278 auto and don't-ask footers with the agents hint
|
|
2368
|
+
and no right edge, plus the 2.1.305 manual footer. On 2.1.310 only the late
|
|
2369
|
+
plain manual footer, identical to 2.1.305's, still matched. Trust clearance then depended on the version banner, which
|
|
2370
|
+
scrolls out of the viewport in a long session. Model-picker hold release never
|
|
2371
|
+
saw the banner at all, because it reads only the last composer and the rows
|
|
2372
|
+
beneath it. Empty-input observation rejected every 2.1.310 effort row. The shared
|
|
2373
|
+
grammar is `src/claude/composer/footer.ts`, and trust clearance requires the
|
|
2374
|
+
footer directly under the bottom rule, so a quoted footer cannot pass for chrome.
|
|
2375
|
+
Empty-input observation requires the same row. None of the 159 composer frames
|
|
2376
|
+
captured on 2.1.310, and none in the 2.1.278 to 2.1.305 fixtures, has a blank row
|
|
2377
|
+
between the bottom rule and the footer. Overflow rows and the login-renewal notice
|
|
2378
|
+
only ever follow the footer.
|
|
2379
|
+
Sanitized 100x8 bannerless frames for every mode are in
|
|
2380
|
+
`tests/fixtures/claude-2.1.310/mode-footers/`. `tests/e2e/permission-footer-claude.e2e.ts`
|
|
2381
|
+
checks a real bypass-permissions session. C-TRUST-01, C-API-55, C-API-56.
|
|
2382
|
+
|
|
2383
|
+
Narrow terminals are not covered. Below roughly 64 to 90 columns, depending on the
|
|
2384
|
+
mode and layout, Claude cuts the footer mid-hint, sometimes with an ellipsis:
|
|
2385
|
+
`⏵⏵ bypass permissions on (shift+tab to`, `(shift+tab to cycle) · ← fo…`,
|
|
2386
|
+
or just `⏵⏵ bypass permissions`. It also moves the token count and effort level
|
|
2387
|
+
onto their own rows. A cut footer does not prove the composer, so the hold stays
|
|
2388
|
+
until the terminal is widened. The hand-opened 2.1.310 picker itself
|
|
2389
|
+
(`● High effort ←/→ to adjust`, a `↓ 8.` scroll marker, `… +5 models`) falls
|
|
2390
|
+
outside the model-dialog grammar on `main` at the time of writing, so that
|
|
2391
|
+
grammar is the remaining gap before a foreign hold engages on it.
|
|
2392
|
+
|
|
1237
2393
|
## Claude question editor captures (2026-10-20)
|
|
1238
2394
|
|
|
1239
2395
|
Claude Code 2.1.305 accepts a single question on Enter. Grouped questions advance
|
|
@@ -1254,6 +2410,24 @@ the highlighted option glyph. The captures used private configuration and
|
|
|
1254
2410
|
authentication copies with synthetic prompts; see
|
|
1255
2411
|
`tests/fixtures/claude-2.1.305/questions/`.
|
|
1256
2412
|
|
|
2413
|
+
### Claude 2.1.310 question chip and named-session border (2026-10-24)
|
|
2414
|
+
|
|
2415
|
+
Claude Code 2.1.310 renders a single question's header chip as ` ☐ Test pick `:
|
|
2416
|
+
the highlight's right padding is a written trailing space, so the terminal row
|
|
2417
|
+
keeps it. 2.1.305 rendered ` ☐ Note` with no padding. Exact equality on the tab
|
|
2418
|
+
row therefore rejected every single-question panel on 2.1.310, and Elwood never
|
|
2419
|
+
raised the request even though the native dialog was on screen. The pinned
|
|
2420
|
+
2.1.305 proof could not see it. Tab matching now ignores trailing whitespace
|
|
2421
|
+
only; the leading space and the header text still have to match exactly.
|
|
2422
|
+
|
|
2423
|
+
A session launched with `--name` (Coal Harbor names every agent) draws its label
|
|
2424
|
+
into the prompt box's bottom border — `──── Question Asker ─` — and that border
|
|
2425
|
+
stays one row under the questionnaire footer. The rule above the header chip
|
|
2426
|
+
stays plain. The questionnaire grammar used to require nothing but blank rows
|
|
2427
|
+
after the footer, so it rejected every question in a named session. It now
|
|
2428
|
+
accepts exactly one labelled border there and still rejects anything else. See
|
|
2429
|
+
`tests/fixtures/claude-2.1.310/questions/`.
|
|
2430
|
+
|
|
1257
2431
|
### Claude question dialogs and manual composer (2026-10-20)
|
|
1258
2432
|
|
|
1259
2433
|
Claude Code 2.1.305 renders `AskUserQuestion` as a fenced questionnaire with
|
|
@@ -1389,3 +2563,19 @@ Queued editors after the first use `⇧→ prev question`; only the first uses
|
|
|
1389
2563
|
frames. If a local skip and Esc collapse coalesce into one output render, the
|
|
1390
2564
|
smaller badge does not identify the removed question. Preserve the prior focused
|
|
1391
2565
|
editor count until a matching remaining editor confirms that removal.
|
|
2566
|
+
|
|
2567
|
+
## Login-shell not-found handlers (2026-10-26)
|
|
2568
|
+
|
|
2569
|
+
A probe runs its CLI through the user's interactive login shell, so a CLI missing
|
|
2570
|
+
from PATH meets whatever not-found handler the startup files define. zsh 5.9 runs
|
|
2571
|
+
`command_not_found_handler` and bash 4.0 and later run `command_not_found_handle`.
|
|
2572
|
+
Ubuntu's `/etc/bash.bashrc` defines the bash one when the `command-not-found`
|
|
2573
|
+
package is installed, to suggest a package. The handler's
|
|
2574
|
+
output goes to the shell's stdout and stderr, which a probe captures, and the
|
|
2575
|
+
handler's return status becomes the probe's. One that returns 1 turns "not found"
|
|
2576
|
+
(127) into an ordinary failure. Measured with a handler printing a marker: zsh 5.9
|
|
2577
|
+
on macOS, and bash 5.2.37 in a Debian trixie container, both captured the marker
|
|
2578
|
+
and reported the handler's status. macOS's bash 3.2 predates the hook. The probe
|
|
2579
|
+
script now runs `unset -f command_not_found_handler command_not_found_handle
|
|
2580
|
+
2>/dev/null || :` before its command. Each shell ignores the other's name. With it,
|
|
2581
|
+
both shells exit 127 and capture no marker.
|