Skip to content

External dependency surface

Purpose. A complete, grep-able inventory of every external interface nexus-code depends on — CLI flags, env vars, parsed TUI strings, API endpoints and fields, subcommands, settings keys — with a breakage signal for each. It exists so that when an upstream tool (above all Claude Code) ships a changelog or news item, an automated process can map that change to the exact nexus-code path:line it could break.

This document catalogs 336 interfaces across 11 dependencies, plus a 53-row Claude-Code test-coverage table. Completeness is the design goal: a missed interface is a blind spot the automation can never flag, so the inventory errs toward over-inclusion. Entries were gathered by reading every shell script, settings file, config, hook, and CI workflow in the repo and spot-verified against the source.

The interface total is a property of this file, so re-derive it rather than trusting it (measured at a3177ef6):

awk '/^## /{s=($0 ~ /^## [0-9]+\./)} s && /^\|/ && !/^\|[-: |]+\|$/ && $0 !~ /^\| Interface \|/ {n++} END{print n}' \
  docs/reference/dependency-surface.md

How to use this doc (update-impact workflow)

Given an upstream changelog / news / release-note item:

  1. Identify the tool. Which dependency does the item concern? Jump to its section via the index below. Claude Code is split into four subsections (CLI, env/settings, TUI strings, session model) because it is the highest-churn dependency.
  2. Scan the Breakage signal column. Each row states what kind of upstream change would break that interface ("spinner glyph changed", "--json field renamed", "hook event renamed", "status-bar layout reflowed"). This is the column the changelog-matcher keys on — match the news item against these phrases.
  3. Open the Where path:line(s) for any matched row and assess the actual code. Treat the PATH as reliable and the LINE as a hint that was last verified at a3177ef6 — see the citation-rot warning below, and grep for the construct rather than trusting the offset.
  4. Check test coverage. For Claude Code interfaces, the test-coverage table marks each as COVERED (with the test/fixture that pins it) or UNTESTED. Untested fragile interfaces are where a silent break is most likely — prioritize those.

Every section uses the same four-column schema so the doc is uniformly machine-parseable:

Column Meaning
Interface The specific flag / env var / parsed string-or-glyph / endpoint / field / subcommand.
Where One or more path:line locations (repo-root-relative).
Purpose What nexus-code uses it for. Entries tagged (test) are test-only usages.
Breakage signal The class of upstream change that would break it — the matcher key.

path:line citations rot — verify before you trust one

A line number is a property of a tree, and a stale one points at different code that still parses, so the reader lands somewhere real and concludes the citation was about that. 123 of this document's 336 rows name their interface as a literal token a script can grep for, and those were re-verified at a3177ef6: before that audit 87 of the 123 pointed at the wrong line — a 71% staleness rate accumulated with no error anywhere. The other 213 rows were checked only for file existence and line-in-range, so assume a comparable rate among them. §9 (GitHub Actions) is the worked example: the token check passed it and a later hand pass found nearly every tests.yml / docs.yml line stale, because a token search is satisfied by a COMMENT that merely names the interface.

Two cheap re-checks, and neither is optional when a citation is load-bearing:

git show "${ref}:${path}" | grep -n 'THE-CONSTRUCT'   # the CONTENT at the line
git rev-parse "${ref}:${path}"                        # the BLOB, to cite beside it

Braces are not decoration: in zsh "$ref:path" applies a history modifier. And never reconcile two citations by their DELTA — an edit above a region shifts everything below it uniformly, so a matching offset distinguishes nothing.

Fragility ranking

Claude Code is the most fragile dependency: it parses Claude Code's rendered terminal output (spinner glyphs, ANSI colour codes, status-bar wording, overlay text) to classify worker state. A Claude Code release that re-themes the TUI, renames a hook event, changes a CLI flag, or relocates the session log can silently break state detection with no error. The TUI / pane-rendering section is the single most important part of this doc to keep current.

Dependency index

# Dependency Interfaces Fragility
1 Claude Code — CLI flags & invocation 25 🔴 Highest
2 Claude Code — env vars & settings/config JSON 35 🔴 Highest
3 Claude Code — TUI / pane-rendering string parsing 39 🔴 Highest (load-bearing)
4 Claude Code — session model & jsonl session log 17 🔴 Highest
5 Anthropic API (direct HTTP probe) 7 🟠 High (model-ID + API version)
6 tmux 34 🟠 High
7 gh CLI + GitHub REST/GraphQL API 60 🟠 High
8 git 17 🟡 Medium
9 GitHub Actions / CI 17 🟡 Medium
10 Notification backends (Pushover / ntfy.sh / SMTP) 8 🟡 Medium
11 Misc CLIs & shell (jq, python, node/npm, SLURM, coreutils, bash, agent-sandbox) 77 🟢 Low-Medium (broad surface, slow churn)
— Claude Code — test coverage 53 —

1. Claude Code — CLI flags & invocation

Fragility: 🔴 Highest

Interface Where (path:line) Purpose Breakage signal
--dangerously-skip-permissions monitor/bootstrap-install.sh:328
monitor/spawn-worker.sh:2379,3247
monitor/claude-loop.sh:170
monitor/watcher/_respawn.sh:767
Bypass permission prompts on all spawned Claude processes; used in bootstrap, workers (resume + fresh launcher heredocs), orchestrator, and respawn paths Removal of --dangerously-skip-permissions flag; changes to permission-suppression semantics or env vars
--continue monitor/claude-loop.sh:202
monitor/watcher/_respawn.sh:767 ($continue_flag composed in the launcher heredoc)
Resume the most recent Claude session in the project when no session ID is pinned; fallback when --resume cannot be used Removal of --continue flag; changes to session-resume semantics or jsonl selection logic
--resume monitor/watcher/_respawn.sh:578-592 (_respawn_choose_resume_mode prints resume\t<sid> at :592)
monitor/spawn-worker.sh:2379 (worker resume launcher)
Resume a specific Claude session by ID (UUID) instead of selecting the most-recent jsonl; prevents wrong-session resurrection when concurrent sessions share project slug Removal of --resume flag; changes to session UUID format or jsonl lookup logic
--settings monitor/claude-loop.sh:194
monitor/spawn-worker.sh:1267,2379,3247
monitor/watcher/_respawn.sh:510-529 (_respawn_resolve_settings_flag),767
Pass configuration file path (JSON) containing hooks (UserPromptSubmit, PostToolUse, Notification, Stop, PermissionRequest, PreToolUse, StopFailure); points to monitor/orchestrator-settings.json or monitor/worker-settings.json Removal of --settings flag; breaking changes to hooks schema (PreToolUse, PostToolUse, UserPromptSubmit, etc.); changes to hook payload format or matcher semantics
--version monitor/install-claude-local.sh:180,347 Version check to verify the Claude Code install and match it against the effective pin (idempotency fast-path at :180; post-install verification at :347) Removal of --version flag; changes to version string format
CLAUDE_CODE_RESUME_THRESHOLD_MINUTES env var monitor/claude-loop.sh:204 Suppress Claude's stale-session resume dialog by setting an absurdly high threshold; allows non-interactive respawn without picker blocking Removal of CLAUDE_CODE_RESUME_THRESHOLD_MINUTES env var; changes to session-stale detection or dialog rendering logic
CLAUDE_CODE_RESUME_TOKEN_THRESHOLD env var monitor/claude-loop.sh:205 Suppress Claude's stale-session resume dialog by setting an absurdly high token threshold; allows non-interactive respawn without picker blocking Removal of CLAUDE_CODE_RESUME_TOKEN_THRESHOLD env var; changes to token-count detection or dialog rendering logic
NEXUS_IS_ORCHESTRATOR env var monitor/watcher/_respawn.sh:754 (exported in the orchestrator launcher)
monitor/hooks/orchestrator-session-pin.sh:101 (gate)
Marker exported into the orchestrator launcher so downstream hooks (orchestrator-session-pin.sh) can distinguish the orchestrator pane from worker panes and avoid side effects (renaming) in non-orchestrator contexts. Set only on the respawn/orchestrator path — spawn-worker.sh does NOT export it Removal of NEXUS_IS_ORCHESTRATOR env var; changes to hook payload schema or hook triggering logic
NEXUS_WORKER_WINDOW env var monitor/spawn-worker.sh:2343,3152,3208
monitor/hooks/over-limit-emit.sh:61
monitor/declare-no-wait.sh:102
monitor/declare-wait.sh:103
monitor/hooks/decision-emit.sh:53
Per-spawn tmux window name exported into worker process so hooks can write state keyed on window name (e.g. heartbeat/$NEXUS_WORKER_WINDOW.json, notifications with window field) Removal of NEXUS_WORKER_WINDOW env var; changes to hook payload schema or jq expressions in hook commands
NEXUS_ROOT env var monitor/_claude-bin.sh:23-44
monitor/spawn-worker.sh:2341,3150,3206
monitor/claude-loop.sh:87
monitor/bootstrap-install.sh:237
monitor/watcher/entry.sh:397
Root path of nexus repo; exported into spawned Claude processes so launcher scripts and hooks can resolve config, state dirs, and other repo-relative paths Removal of NEXUS_ROOT env var export; breaking changes to env var resolution in launcher heredocs
$CLAUDE_BIN binary resolution monitor/_claude-bin.sh:32-45
monitor/install-claude-local.sh:179-180,340 (the node_modules/.bin/claude path this resolver picks)
monitor/claude-loop.sh:90,206
monitor/bootstrap-install.sh:328
Binary path resolution chain: CLAUDE_BIN env var override → $NEXUS_ROOT/node_modules/.bin/claude (project-local install) → system 'claude' on PATH; baked into launcher heredocs at write time so spawned windows run absolute path not shell PATH lookup Changes to npm binary location (node_modules/.bin path); removal of --version flag; breaking changes to executable location or naming
npm ci / npm install for Claude Code install monitor/install-claude-local.sh:205-215
package.json:8 (@anthropic-ai/claude-code 2.1.173)
package-lock.json (locks @anthropic-ai/claude-code 2.1.161)
Project-local Claude Code installation via npm. package.json declares 2.1.173 as a maintainer-managed vetted floor for fresh installs (not a per-release pin); package-lock.json records the resolved 2.1.161; a successful gated cc-update advances the operator-LOCAL pin (monitor/.state/cc-version-local, gitignored) without touching either file. install-claude-local.sh PREFERS npm install — it reconciles node_modules in place, so a failed reinstall leaves the prior binary standing; npm ci wipes node_modules first and is opt-in only, via CLAUDE_INSTALL_USE_CI=1 AND a present package-lock.json. With an operator-local pin it uses npm install --no-save <pkg>@<version> so the shared floor is not rewritten Changes to npm package metadata, registry resolution, or installation paths; breaking changes to npm binary structure
skipDangerousModePermissionPrompt JSON setting monitor/orchestrator-settings.json:2
monitor/worker-settings.json:2
JSON config file setting that suppresses the startup permission-bypass dialog when passed via --settings; allows unattended spawn with --dangerously-skip-permissions Removal or renaming of skipDangerousModePermissionPrompt setting; changes to settings.json schema or permission-handling logic
hooks.UserPromptSubmit hook event monitor/orchestrator-settings.json:16-25
monitor/worker-settings.json:81-90
monitor/hooks/orchestrator-session-pin.sh:21-24
Hook fired on every user prompt submission (including post-/clear); orchestrator uses this to pin session-id; workers use this for heartbeat Removal of UserPromptSubmit hook event; changes to hook payload format (session_id field, etc.); changes to hook command invocation
hooks.PostToolUse hook event monitor/worker-settings.json:36-58
monitor/orchestrator-settings.json:26-33
(worker) Fired after tool execution; used for heartbeat marking busy state and async-launch detection; also runs PreToolUse pending-tool cleanup Removal of PostToolUse hook event; changes to hook payload format or matcher semantics
hooks.Notification hook event monitor/worker-settings.json:59-80
monitor/orchestrator-settings.json:34-43
(worker) Fired on notifications (idle, over-limit, etc.); logs raw captures and structured event rows to track decision-making and provide status to orchestrator Removal of Notification hook event; changes to notification payload schema or filtering logic
hooks.Stop hook event monitor/orchestrator-settings.json:44-53
monitor/worker-settings.json:101-122
Fired at end of session; orchestrator uses to write heartbeat marker; worker uses to mark decision resolved and clean up state files Removal of Stop hook event; changes to hook invocation timing or payload
hooks.StopFailure hook event monitor/worker-settings.json:123-136
monitor/orchestrator-settings.json:54-61
(worker) Fired when Stop hook fails or tool output exceeds over-limit threshold; used to emit decision info even when session crashed Removal of StopFailure hook event; changes to over-limit detection or hook invocation
hooks.PreToolUse hook event with matcher monitor/orchestrator-settings.json:7-15
monitor/worker-settings.json:7-35
Orchestrator uses to block AskUserQuestion calls; worker uses to capture pending-tool metadata before bash/write/edit/notebook execution Removal of PreToolUse hook or matcher support; changes to matcher syntax (AskUserQuestion, Bash, Write, Edit, NotebookEdit)
hooks.PermissionRequest hook event monitor/worker-settings.json:91-100 (worker) Fired on permission prompts; used for heartbeat tracking Removal of PermissionRequest hook event; changes to hook invocation
Claude Code project slug encoding monitor/watcher/_respawn.sh:315,444 (${...//[^a-zA-Z0-9-]/-}) Session-id file location resolution: character-by-character mapping of repo path to session jsonl dir under ~/.claude/projects//; forward slashes, underscores, and dots all become dashes Changes to project slug generation algorithm or session directory structure under ~/.claude
Claude Code session JSON log format (.jsonl) monitor/watcher/_respawn.sh:316,446 Session persistence; orchestrator and workers share the same project slug's .jsonl files; orchestrator pin file references session UUIDs that must match jsonl filenames; --resume looks up $PROJECT_DIR/.jsonl Changes to session file naming (UUID), directory structure under ~/.claude, or jsonl serialization format
orchestrator-session-id file monitor/watcher/entry.sh:220
monitor/hooks/orchestrator-session-pin.sh:44-56
monitor/watcher/_respawn.sh:578-592 (_respawn_choose_resume_mode)
State file at $NEXUS_ROOT/monitor/.state/orchestrator-session-id; holds UUID of most recent orchestrator session; the watcher's spawn paths resolve it to --resume (deterministic) or a cold fresh spawn, preventing wrong-session resurrection when multiple sessions share the project slug; entry.sh reconciles operator intent onto it (default archives the pin, --continue keeps it) Changes to hook payload (session_id field); removal of session-id tracking; changes to UUID validation regex or format
Claude Code session-resume dialog suppression monitor/claude-loop.sh:161-176 When resuming via --continue with large prior session, Claude asks 'Resume from summary / Resume full session as-is / Don't ask me again'; both respawn paths set CLAUDE_CODE_RESUME_THRESHOLD_MINUTES and CLAUDE_CODE_RESUME_TOKEN_THRESHOLD to 999999999 to suppress picker and force full-session load Changes to resume-dialog logic; removal of CLAUDE_CODE_RESUME_* env vars; changes to what triggers the picker vs auto-resume
Claude Code hook payload JSON format monitor/orchestrator-settings.json: hook payloads
monitor/worker-settings.json: hook payloads
monitor/hooks/orchestrator-session-pin.sh:44
Hooks receive JSON payload with fields like session_id, tool_name, hook_event_name, notification_type, message, etc.; orchestrator-session-pin.sh jq-extracts session_id; worker hooks jq-extract multiple fields Removal or renaming of session_id field in UserPromptSubmit payload; changes to jq path syntax or field availability; breaking changes to hook payload schema

Notes

VERSION PINNING: package.json declares @anthropic-ai/claude-code 2.1.173 as a MAINTAINER-MANAGED VETTED FLOOR for fresh installs (not a per-release pin); package-lock.json locks node_modules to 2.1.161 (the resolved version for reproducible npm ci). The two intentionally differ: the floor advances only when a maintainer vets a release, while a successful gated cc-update advances the operator-LOCAL pin (monitor/.state/cc-version-local, gitignored) without editing either tracked file. install-claude-local.sh implements an idempotent fast-path: if node_modules/.bin/claude exists and reports version == effective_version, skip reinstall (monitor/install-claude-local.sh:179-188); otherwise it PREFERS npm install and uses npm ci only when CLAUDE_INSTALL_USE_CI=1 AND package-lock.json exists (monitor/install-claude-local.sh:205-215). Requires Node.js >= 18.0.0 (checked at monitor/install-claude-local.sh:134-137). See monitor/_cc-version.sh and skills/nexus.cc-update/GUIDE.md.

ENVIRONMENT SUMMARY: Three main env vars exported into Claude processes: NEXUS_ROOT (repo path), NEXUS_IS_ORCHESTRATOR (0 or 1, orchestrator only), NEXUS_WORKER_WINDOW (tmux window name, worker only). Plus CLAUDE_CODE_RESUME_THRESHOLD_MINUTES and CLAUDE_CODE_RESUME_TOKEN_THRESHOLD (respawn paths only, to suppress resume dialog).

SETTINGS FILES: Two JSON files passed via --settings: orchestrator-settings.json (orchestrator only, includes session-pin hook) and worker-settings.json (worker only, includes heartbeat/notification/decision-emit hooks). Both set skipDangerousModePermissionPrompt: true and an env block with DISABLE_AUTOUPDATER=1 (the built-in npm auto-updater never applies to the project-local pin; disabling it removes the spurious "Auto-update failed" banner).

HOOK SCHEMA: Hooks are keyed by event (PreToolUse, PostToolUse, Notification, UserPromptSubmit, PermissionRequest, Stop, StopFailure) and optional matcher (AskUserQuestion, Bash, Write, Edit, NotebookEdit). Each hook runs a shell command that typically redirects jq output into state files under $NEXUS_ROOT/monitor/.state/.

SESSION RESUMPTION: The watcher owns it via the orchestrator session-id pin: _respawn_choose_resume_mode resolves a valid pin to --resume and degrades to a deterministic cold fresh spawn otherwise (never bare --continue — issue 200). entry.sh only reconciles operator intent onto the pin (default archives it; --continue keeps it). claude-loop.sh's --continue path sets CLAUDE_CODE_RESUME_THRESHOLD_MINUTES=999999999 to suppress the resume dialog. The pin file is written at spawn and re-affirmed on every UserPromptSubmit via orchestrator-session-pin.sh hook.

BINARY RESOLUTION: _claude-bin.sh is sourced by all spawn paths. Resolves CLAUDE_BIN in order: env override, project-local $NEXUS_ROOT/node_modules/.bin/claude, system claude on PATH. Path is baked into launcher heredocs at write time so spawned windows use absolute path, not PATH lookup.

TEST FILES: test-*.sh files in monitor/watcher/ test spawn-worker.sh, spawn-fresh-orchestrator.sh, and respawn logic. Tests capture launcher body and assert on presence of --dangerously-skip-permissions, --continue (default), --resume (when pin valid), --settings (when file exists). Not included in the entries above but should be scanned for breaking regressions if Claude Code CLI changes.

GAPS / ASSUMPTIONS: (1) Hook payload schema is inferred from jq filters in orchestrator-session-pin.sh and worker hooks; no formal schema doc found. (2) session_id field name assumed from orchestrator-session-pin.sh jq '.session_id'; if Claude Code renames it, the hook silently fails. (3) Project slug encoding (char → dash) is documented in respawn.sh but not verified against Claude Code's actual implementation. (4) Resume dialog suppression assumes CLAUDE_CODE_RESUME* env vars still exist in Claude Code; if removed or renamed, the respawn paths will fail silently and the picker will block.


2. Claude Code — env vars & settings/config JSON

Fragility: 🔴 Highest

Interface Where (path:line) Purpose Breakage signal
CLAUDE_CODE_RESUME_THRESHOLD_MINUTES monitor/claude-loop.sh:204 Environment variable exported before claudexec to suppress the 'Resume from summary' dialog by setting resume threshold minutes beyond any plausible session age; used in --continue resume path to prevent interactive resume picker If Claude Code stops reading this env var or changes the gate logic (e.g., if(Y<K)return null), the resume dialog will appear and block the orchestrator's automated resume
CLAUDE_CODE_RESUME_TOKEN_THRESHOLD monitor/claude-loop.sh:205 Environment variable exported before claude exec to suppress the resume dialog by setting token threshold beyond any plausible session size; paired with CLAUDE_CODE_RESUME_THRESHOLD_MINUTES If Claude Code stops reading this env var, the resume dialog appears and blocks automated resume in the orchestrator
--dangerously-skip-permissions monitor/claude-loop.sh:170
monitor/spawn-worker.sh:2379,3247
monitor/bootstrap-install.sh:328
monitor/watcher/_respawn.sh:767
CLI flag passed to claude to suppress the dangerous-mode permission prompt; required for unattended orchestrator and worker spawning If Claude Code removes this flag or changes its name/semantics, the orchestrator and workers will block on the permission dialog
--settings monitor/claude-loop.sh:69,194
monitor/spawn-worker.sh:1267,2379,3247
monitor/watcher/_respawn.sh:510-529,767
CLI flag to pass a JSON settings file (orchestrator-settings.json or worker-settings.json) containing hooks and skipDangerousModePermissionPrompt; wires the hook block (PreToolUse, PostToolUse, Notification, UserPromptSubmit, PermissionRequest, Stop, StopFailure events) If Claude Code removes this flag or changes settings schema, hooks stop firing and the watcher loses liveness signals, heartbeat tracking, and decision-emit pipelines
--continue monitor/claude-loop.sh:202 CLI flag to resume the most-recent session in ~/.claude/projects// against the same workdir; used by orchestrator to resume mid-monitoring and by workers for multi-turn sessions If Claude Code removes or renames this flag, the orchestrator cannot resume on restart and loses session continuity
--resume monitor/watcher/_respawn.sh:578-592 (_respawn_choose_resume_mode),767 CLI flag (upgrade from --continue) to explicitly resume a specific session ID by UUID; used when orchestrator-session-pin hook has written a valid session-id to monitor/.state/orchestrator-session-id If Claude Code removes this flag, the orchestrator session-pinning mechanism fails and --continue silently resumes the wrong session (stale worker session instead of orchestrator)
skipDangerousModePermissionPrompt monitor/orchestrator-settings.json:2
monitor/worker-settings.json:2
Boolean settings key in --settings JSON files; when true, suppresses the dangerous-mode permission dialog without needing --dangerously-skip-permissions CLI flag (belt-and-suspenders with CLI flag) If Claude Code stops reading this settings key, the dialog will appear even with --dangerously-skip-permissions, blocking unattended spawning
env (top-level settings block) → DISABLE_AUTOUPDATER monitor/orchestrator-settings.json:3-5
monitor/worker-settings.json:3-5
Settings key whose entries Claude Code exports as environment variables into the spawned session; both files set DISABLE_AUTOUPDATER=1 to switch off the built-in npm auto-updater and its "✗ Auto-update failed" banner — irrelevant noise here because the claude binary is a project-local npm pin (node_modules/.bin/claude) whose version bumps go through the gated cc-update loop, not self-update If Claude Code stops reading the env settings block or renames DISABLE_AUTOUPDATER, the auto-update-failed banner reappears in every spawned pane (cosmetic noise only; no functional breakage)
hooks (top-level settings block) monitor/orchestrator-settings.json:6
monitor/worker-settings.json:6
Settings key that contains event-driven hooks block; defines PreToolUse, PostToolUse, Notification, UserPromptSubmit, PermissionRequest, Stop, StopFailure event handlers that wire liveness monitoring, heartbeat capture, decision-emit, and over-limit detection If Claude Code removes the hooks block or changes event names, all watcher monitoring stops (no heartbeats, decisions, or notifications recorded)
hooks.PreToolUse (event) monitor/orchestrator-settings.json:7
monitor/worker-settings.json:7
Hook event fired before a tool is used; in orchestrator wires AskUserQuestion blocker; in workers wires pending-tool jq capture (writes {tool:.tool_name, input_summary:.tool_input, ts:now} to monitor/.state/pending-tool/.json) If Claude Code removes this event or changes when it fires, pending-tool capture fails and the decision-emit hook cannot contextualize prompts
hooks.PreToolUse[].matcher monitor/orchestrator-settings.json:9
monitor/worker-settings.json:9
String matcher in PreToolUse entry (e.g., 'AskUserQuestion' or 'Bash|Write|Edit|NotebookEdit'); Claude Code filters tool events by name and only runs hooks when the tool matches If Claude Code changes matcher semantics or capitalization (e.g., 'AskUserQuestion' → 'askUserQuestion'), the orchestrator fails to block dialog and workers fail to capture pending tools
hooks[].type (command) monitor/orchestrator-settings.json:11
monitor/worker-settings.json:11
monitor/worker-settings.json:12
monitor/worker-settings.json:19
Hook handler type field; 'command' means Claude Code runs the string in hooks[].command as a shell pipeline, piping the hook payload to stdin If Claude Code removes command type or changes stdin payload schema, hook commands fail and receive malformed/missing JSON
hooks.PostToolUse (event) monitor/worker-settings.json:36
monitor/orchestrator-settings.json:26
Hook event fired after a tool completes; wires worker-heartbeat.sh busy signal and pending-tool cleanup; also wires async-launch-detect.sh to spot background spawns If Claude Code removes or delays this event, heartbeat misses busy transitions and stale-check fails
hooks.Notification (event) monitor/worker-settings.json:59
monitor/orchestrator-settings.json:34
Hook event fired when Claude Code emits a notification (permission_prompt, idle_prompt, auth_success, elicitation_*); wires worker-heartbeat.sh notify, decision-emit, and worker-notifications.jsonl logging If Claude Code removes this event, decision-emit never runs and the orchestrator never learns of permission/idle prompts
hooks.UserPromptSubmit (event) monitor/orchestrator-settings.json:16
monitor/worker-settings.json:81
Hook event fired when user submits a prompt to Claude Code; in orchestrator wires orchestrator-session-pin.sh to pin the session ID; in workers wires worker-heartbeat.sh user_prompt If Claude Code removes this event, the orchestrator session pin never updates and --continue will silently resume the wrong session on restart
hooks.PermissionRequest (event) monitor/worker-settings.json:91 Hook event fired when Claude Code asks for permission (e.g., to use a tool in dangerous mode); wires worker-heartbeat.sh permission_prompt to emit state=permission_prompt for the watcher's unstick logic If Claude Code removes this event, permission prompts are not tracked and the watcher's auto-unstick fails
hooks.Stop (event) monitor/orchestrator-settings.json:44
monitor/worker-settings.json:101
Hook event fired at the end of an agent turn; wires orchestrator's heartbeat touch, workers' turn_end heartbeat, and over-limit cleanup; proves the agent's turn truly ended If Claude Code removes this event, no turn-end signal is emitted and the watcher incorrectly classifies idle state
hooks.StopFailure (event) monitor/worker-settings.json:123
monitor/orchestrator-settings.json:54
Hook event fired when Stop fails (e.g., rate-limit error); wires over-limit-emit.sh to write per-window over-limit stamp for watcher re-wake decision If Claude Code removes this event, rate-limit conditions are not detected and the watcher doesn't trigger graceful re-spawn with backoff
hook payload: .tool_name monitor/worker-settings.json:12
monitor/hooks/async-launch-detect.sh:85
monitor/worker-heartbeat.sh:128
Field in hook event JSON payload (from stdin) containing the tool name (e.g., 'Bash', 'Write', 'Edit', 'ScheduleWakeup'); read by pending-tool capture and async-launch detection If Claude Code removes this field or renames it, pending-tool context is lost and ScheduleWakeup detection fails
hook payload: .tool_input monitor/worker-settings.json:12
monitor/hooks/async-launch-detect.sh:88
monitor/worker-heartbeat.sh:133
Field in PreToolUse/PostToolUse payload containing tool arguments; .tool_input.command is read by async-launch-detect.sh; .tool_input.delaySeconds read by worker-heartbeat.sh for ScheduleWakeup delay If Claude Code removes this field or changes nesting (e.g., flattens to .command), async-launch-detect fails and schedule-wakeup delay is not captured
hook payload: .hook_event_name monitor/worker-settings.json:41
monitor/hooks/decision-emit.sh:83
monitor/worker-heartbeat.sh:127
Field in hook payload containing the event name (PreToolUse, PostToolUse, Notification, UserPromptSubmit, PermissionRequest, Stop, StopFailure); fallback for kind classification when .notification_type is absent If Claude Code removes this field, decision-emit's fingerprint and worker-heartbeat's event classification fails (falls back to 'unknown')
hook payload: .notification_type monitor/worker-settings.json:41
monitor/hooks/decision-emit.sh:83
monitor/worker-heartbeat.sh:131
Field in Notification payload (top-level sibling) specifying notification kind (permission_prompt, idle_prompt, auth_success, elicitation_*); preferred over legacy .notification.type nesting for event classification If Claude Code removes this field, fallback to .notification.type or .hook_event_name; if both removed, decision-emit fingerprints and worker-heartbeat states default to 'unknown' and liveness tracking degrades
hook payload: .message monitor/worker-settings.json:41
monitor/hooks/decision-emit.sh:89
monitor/worker-heartbeat.sh:130
Field in Notification payload containing user-facing prompt text; read by decision-emit for fingerprint generation and worker-heartbeat for message context; fallback to .notification.message for legacy payloads If Claude Code removes this field, decision-emit fingerprints lose message context and dedup becomes collision-prone
hook payload: .notification.type (legacy nesting) monitor/hooks/decision-emit.sh:78
monitor/hooks/decision-emit.sh:83
Legacy fallback for .notification_type in case Claude Code re-nests the field under .notification object; preserved for future schema regression resilience If Claude Code changes to re-nest .notification_type under .notification, nexus-code must detect the new shape or decision-emit classification breaks
hook payload: .notification.message (legacy nesting) monitor/hooks/decision-emit.sh:86
monitor/hooks/decision-emit.sh:89
Legacy fallback for .message in case Claude Code re-nests under .notification; preserved for regression resilience If Claude Code changes to re-nest .message, decision-emit and worker-heartbeat must detect the new shape
hook payload: .session_id monitor/hooks/orchestrator-session-pin.sh:44
monitor/hooks/decision-emit.sh:73
monitor/hooks/over-limit-emit.sh:91
monitor/worker-heartbeat.sh:129
Field in hook payload containing the Claude Code session UUID (8-4-4-4-12 hex format); read by orchestrator-session-pin.sh to update monitor/.state/orchestrator-session-id for --resume pin If Claude Code removes this field, orchestrator-session-pin.sh cannot pin the session and --resume upgrade fails, causing silent wrong-session resume
hook payload: .error_type monitor/hooks/over-limit-emit.sh:5
monitor/hooks/over-limit-emit.sh:84
Field in StopFailure payload indicating error category (e.g., 'rate_limit'); checked by over-limit-emit.sh to decide whether to emit over-limit state If Claude Code removes this field, over-limit-emit.sh cannot distinguish rate-limit from other failures and over-limit detection breaks
hook payload stdin piping monitor/hooks/orchestrator-session-pin.sh:32
monitor/hooks/decision-emit.sh:50
monitor/hooks/async-launch-detect.sh:77
monitor/hooks/over-limit-emit.sh:5
monitor/worker-heartbeat.sh:88
Claude Code pipes hook event JSON to stdin of each command in the hooks block; nexus-code reads and parses this stdin payload via jq to extract fields If Claude Code stops piping the payload or malforms the JSON, all jq queries fail and hooks degrade gracefully (exit 0, log to stderr)
NEXUS_ROOT monitor/spawn-worker.sh:2341,3150,3206
monitor/watcher/entry.sh:397
Environment variable exported by spawn-worker.sh and entry.sh into Claude Code's subprocess tree; points to the primary nexus clone root; used by hook scripts to resolve paths (e.g., monitor/hooks/, monitor/.state/) If hooks rely on NEXUS_ROOT and it's not set, paths are unresolved and hook commands fail silently (exit 0 hot-path discipline)
NEXUS_WORKER_WINDOW monitor/spawn-worker.sh:2343,3152,3208
monitor/hooks/decision-emit.sh:53
Environment variable exported by spawn-worker.sh into the worker process; contains the tmux window name; used by hook commands to key per-window state files (heartbeat, decisions, pending-tool) and in jq --arg to embed window name in JSON If NEXUS_WORKER_WINDOW is not set, hook commands have no way to distinguish concurrent workers and state files collide
NEXUS_IS_ORCHESTRATOR monitor/watcher/_respawn.sh:754 Environment variable exported by the watcher's respawn launcher into the orchestrator process; signals to hook scripts and downstream tools that this is the orchestrator (not a worker); used for orchestrator-specific behavior (e.g., session-pin hook, gated at orchestrator-session-pin.sh:101) If NEXUS_IS_ORCHESTRATOR is not set, orchestrator hooks run the same code as worker hooks, causing session-id collision and incorrect monitoring
~/.claude/projects//.jsonl monitor/watcher/_respawn.sh:316,446
monitor/watcher/_lib.sh:898,1061
Path where Claude Code stores session logs; nexus-code reads this to detect fresh vs. retained sessions and to find the pinned orchestrator session ID from monitor/.state/orchestrator-session-id If Claude Code changes the path or location of session logs, the watcher cannot find recent sessions for --continue or --resume and the orchestrator cannot be resumed
monitor/.state/orchestrator-session-id monitor/watcher/entry.sh:220
monitor/hooks/orchestrator-session-pin.sh:51
File written by orchestrator-session-pin.sh hook containing the orchestrator's session UUID; read by entry.sh to upgrade --continue to --resume; ensures correct orchestrator session is resumed on watcher restart If the UserPromptSubmit hook fails to fire or orchestrator-session-pin.sh fails, the pin file is not updated and --continue will resume stale/wrong session
CLAUDE_BIN monitor/_claude-bin.sh:32,34,36,45
monitor/spawn-worker.sh:2379,3247
monitor/claude-loop.sh:90,206
Environment variable containing the path to the claude executable; resolved from CLAUDE_BIN env override, project-local node_modules/.bin/claude, or system PATH; used to exec claude in launchers If the claude binary is not on PATH or not installed locally, CLAUDE_BIN resolution fails and the launcher exits with an error
--print monitor/claude-loop.sh:163-176 Claude Code CLI flag (negation of default interactive mode); not used in nexus-code but documented as the constraint that prevents --continue with a positional prompt from working as expected If Claude Code's behavior for --continue with a positional prompt changes, the orchestrator's silent-resume path may be affected

Notes

Version assumptions: Claude Code v1.x CLI surface as of early 2026 (--dangerously-skip-permissions, --settings, --continue, --resume, hook events: PreToolUse, PostToolUse, Notification, UserPromptSubmit, PermissionRequest, Stop, StopFailure; hook payload JSON schema with .tool_name, .tool_input, .hook_event_name, .notification_type, .message, .session_id, .error_type). Hook payload stdin piping and settings JSON structure are the load-bearing interfaces. Ambiguities: the legacy .notification.type and .notification.message nesting was preserved in nexus-code for resilience but may not be in current Claude Code (PR #132 captured empirical payload shape); future Claude Code schema changes (e.g., field renames, event name capitalization, or hook event addition/removal) would break nexus-code's decision-emit, heartbeat, and session-pin mechanisms unless the scripts update their jq filters and event matchers. Test files (monitor/watcher/test-.sh) exercise all these interfaces and would fail immediately on CLI/settings schema changes. The only DISABLE_ env var referenced is DISABLE_AUTOUPDATER (set to "1" via the settings env block to silence the built-in npm auto-updater, which never applies to the project-local pin); no telemetry or apiKey-helper env vars are referenced in nexus-code itself (those are user-side Claude Code config); NEXUS_* vars are nexus-specific, not Claude Code's.


3. Claude Code — TUI / pane-rendering string parsing

Fragility: 🔴 Highest (load-bearing)

Interface Where (path:line) Purpose Breakage signal
Input chevron with non-breaking space: ❯ monitor/pane-state.sh:242
monitor/pane-state.sh:329-334
monitor/pane-state.sh:1004
Anchor to locate the Claude input row on screen; used to distinguish Claude input from overlay menu rows (which use ❯ with ASCII space instead). Last match wins. Chevron glyph changed, NBSP representation changed, or chevron-input-row association redesigned
Dim-cursor reverse-video autosuggest pattern: \x1b[7m.\x1b[0;2m monitor/pane-state.sh:39
monitor/pane-state.sh:148-151
monitor/pane-state.sh:337-342
monitor/pane-state.sh:374
Detect autosuggest-only (dim ghost cursor) on input row; used to classify pane as autosuggest-only when no user text present. Regex matches reverse-video char followed by faint/dim on rest. Autosuggest rendering changed from \x1b[7m.\x1b[0;2m to \x1b[2m alone, different colour code (e.g. 256-colour grey), or autosuggest visual style redesigned
Bright-white user-typed text marker: \x1b[38;5;231m monitor/pane-state.sh:37
monitor/pane-state.sh:148-152
monitor/pane-state.sh:344-349
Detect user-typing state by marker on input row. Bright 256-colour code 231 indicates real user input (not autosuggest). User-text colour changed from 38;5;231 to different code, bright-text styling redesigned, or input rendering emits different ANSI codes
Empty input box reverse-video space: \x1b[7m \x1b[0m monitor/pane-state.sh:370-375 Detect empty input state: reverse-video space only, no autosuggest tail, no bright text. Empty input rendering changed from reverse-video space-only to different pattern
Token counter active on spinner row: [↓↑] +[0-9]+(.[0-9]+)?[kKmM]? +tokens monitor/pane-state.sh:35
monitor/pane-state.sh:166
monitor/pane-state.sh:352-368
Detect busy state by active token counter (↓ for down, ↑ for up). Regex matches 'N tokens', 'N.Nk tokens', 'N.Nm tokens'. Scanned only in 10 lines above input row to avoid false-match on scrollback. Token counter glyph changed, numerical format changed, or busy-spinner redesigned (e.g. different arrow or no counter during generation)
Idle banner past-tense form: ✻ ed for monitor/pane-state.sh:167
monitor/pane-state.sh:354-355
Distinguish idle banner (✻ Brewed for 34m 45s) from active token counter (↓ N tokens) on spinner row. Idle banner glyph (✻) changed, tense changed from past to present, or spinner-row layout redesigned
Spinner glyphs and banner markers: ✻ monitor/pane-state.sh:354
monitor/watcher/fixtures/over-limit-canonical-synthetic.ansi:1
monitor/watcher/fixtures/user-typing-synthetic.ansi:1
monitor/watcher/fixtures/working-background-monitor-synthetic.ansi:3
Spinner row marker used in token counter detection and status analysis; ✻ precedes the verb ('Brewed', 'Cooked', 'Worked', etc). Spinner glyph changed from ✻ to different symbol
Status line markers: -- INSERT -- (with colour 38;5;246) monitor/pane-state.sh:1682-1691 (_detect_vim_insert)
monitor/watcher/fixtures/working-background-monitor-synthetic.ansi:9
Parse pane footer for async handle counts; status bar contains markers like '-- INSERT -- ⏵⏵ bypass permissions on · N monitor · ← for agents'. Status line format changed, -- INSERT -- text removed, or layout reorganized
Monitor handle count in spinner row: N monitor[s] still running monitor/pane-state.sh:595-599
monitor/watcher/fixtures/working-background-monitor-synthetic.ansi:3
Extract monitor handle count from footer for working-background classification. Regex: '([0-9]+)[[:space:]]+monitor[s]?[[:space:]]+still[[:space:]]+running'. Monitor status message text changed, word 'monitor' removed, or phrasing changed from 'still running'
Background bash count in spinner row: N background bash[es] [still] running monitor/pane-state.sh:600-603
monitor/watcher/fixtures/working-background-bgbash-synthetic.ansi:3
Extract background bash count from footer. Accepts variants: 'bash', 'bashes', 'process', 'processes'; with or without 'still'. Background process status message text changed, word 'bash' or 'background' removed, or footer format redesigned
Status line middle-dot separator format: · N monitor[s]? · monitor/pane-state.sh:597-603 Fallback parser for handle counts when spinner row absent. Middle-dot (·) anchors the pattern and distinguishes from spinner row. Middle-dot separators changed to different character, status bar layout reflowed, or separator pattern removed
Over-limit canonical text: You've hit your limit · resets monitor/pane-state.sh:50-57
monitor/pane-state.sh:287-303
monitor/watcher/fixtures/over-limit-canonical-synthetic.ansi:4
Detect over-limit suspension when rate limit hit. Anchored to bottom 15 rows of pane to avoid false-match in scrollback. Requires BOTH 'You've hit your limit' and 'resets 'You've hit your limit' text changed, 'resets' suffix removed, or over-limit notice redesigned
Over-limit reset time extraction: resets[[:space:]]+[^[:space:]] monitor/pane-state.sh:301
monitor/pane-state.sh:313-326
Extract reset time from over-limit notice (e.g. '3am (America/Los_Angeles)' → '3am_America/Los_Angeles'). Strips parens, collapses whitespace to underscore, caps at 40 chars. Reset time format changed, parens handling changed, or whitespace-to-underscore normalization affected
Permission overlay detection: 'Do you want to proceed?' AND numbered option lines monitor/pane-state.sh:249-263
monitor/watcher/fixtures/blocked-permission-synthetic.ansi:2-5
Detect permission prompt overlay. Regex: 'Do you want to proceed?' AND '❯[[:space:]]+[0-9]+.' (default-highlighted option line). Permission prompt text changed from 'Do you want to proceed?', option numbering format changed, or chevron styling changed
Rate-limit menu detection: 'What do you want to do?' AND 'Stop and wait for limit' monitor/pane-state.sh:251-252
monitor/watcher/_unstick.sh:76-89
monitor/watcher/fixtures/blocked-ratelimit-synthetic.ansi:1-2
Detect rate-limit blocking menu. Both lines required: title AND the specific 'Stop and wait for limit to reset' option. 'What do you want to do?' text changed, 'Stop and wait for limit' option text changed, or menu layout redesigned
AskUserQuestion overlay detection: 'Type something.' AND 'Chat about this' monitor/pane-state.sh:278-282
monitor/watcher/_unstick.sh:145-149
monitor/watcher/fixtures/blocked-askuq-synthetic.ansi:8,10
Detect AskUserQuestion blocking dialog (Layer B safety net). Both literals required; either alone could appear in benign text. 'Type something.' text changed, 'Chat about this' text changed, or AskUserQuestion dialog redesigned
AskUserQuestion fingerprint regex: (Type something.|Chat about this|^[[:space:]][0-9]+.|❯[[:space:]]+[0-9]+.?|\?[[:space:]]$) monitor/watcher/_unstick.sh:327 Stable fingerprint of AskUserQuestion dialog for dedup across poll cycles. Extracts distinguishing lines (chip-bar, options, question) and hashes. AskUserQuestion option format changed, question-line ending detection altered, or chip-bar styling changed
Process binary names for liveness check: claude|claude-code monitor/pane-state.sh:386-411 Walk pane PID tree to find live Claude process. Matches against ps output comm field. Used to distinguish empty (alive) from absent (dead). Claude Code binary renamed, launcher chain altered, or process tree structure changed
NBSP (non-breaking space) literal: \xc2\xa0 monitor/pane-state.sh:242 UTF-8 encoding of NBSP used to anchor input chevron pattern (❯) and distinguish from menu options (❯ ). NBSP encoding changed, input row chevron rendering changed to use ASCII space, or encoding layer altered
Heartbeat state vocabulary: busy|user_prompt|permission_prompt|idle_prompt monitor/pane-state.sh:113-126
monitor/pane-state.sh:521-526
Heartbeat JSON 'state' field values that map to pane-state classification. Maps to: busy, busy, blocked, idle respectively. Heartbeat state field names changed, new state values added, or state vocabulary redesigned
Async signal fields in heartbeat JSON: scheduled_wakeup_at, external_waits (NOT monitor_handles / background_bash_count — never written; the arm that read them was deleted in #1374, and test-heartbeat-schema-agreement.sh pins that every field the classifier reads is one the writer emits) monitor/pane-state.sh:80
monitor/pane-state.sh:434-443
monitor/pane-state.sh:547-630
Heartbeat JSON fields for idle-refinement into working-background / working-self-paced / idle-orphan-async states. Heartbeat field names changed, field semantics altered, or async-signal structure redesigned
Last-turn-end anchor: last_turn_end (Stop hook timestamp) vs last_activity monitor/pane-state.sh:422-432
monitor/pane-state.sh:508-519
Staleness anchor selection for heartbeat freshness: last_turn_end (when present) wins with longer threshold (1800s) over last_activity (30s). Stop hook no longer emits last_turn_end, staleness thresholds changed, or anchor priority altered
Over-limit stamp file path and structure: $STATE_DIR/over-limit/.json monitor/pane-state.sh:3697-3701
monitor/hooks/over-limit-emit.sh:8-24
Structured signal file written by StopFailure hook when rate_limit error. Contains reset_at token for pane-state.sh to read. Stamp file directory changed, JSON structure changed, reset_at field name changed, or hook output format altered
Over-limit error_type filter: error_type == 'rate_limit' monitor/hooks/over-limit-emit.sh:84 Filter StopFailure hook events: only rate_limit events write an over-limit stamp (other error_types captured to raw log but not actioned). StopFailure payload error_type field changed, rate_limit value changed, or error classification scheme redesigned
Rate-limit reset_at field name probing: .reset_at | .reset_time | .rate_limit_reset | .error.reset_at | .error.reset monitor/hooks/over-limit-emit.sh:99 Probe plausible reset_at field paths in StopFailure payload (empirical field name unknown; issue #129 q2). Writes first non-null match. Rate-limit payload introduces new reset-time field path, field names standardized differently, or payload structure changed
Blocking overlay AND detection: 'What do you want to do?' AND 'Stop and wait for limit' OR permission OR AskUserQuestion monitor/pane-state.sh:249-263 Unified _has_blocked_overlay check: runs FIRST before over-limit-stamp check to catch all blocking modes (rate-limit menu, permission, dialog). Overlay detection patterns changed, blocked state definition altered, or overlay priorities redesigned
Colour codes in rendering: 38;5;231 (bright white), 38;5;246 (dim), 38;5;244 (separator), 7m (reverse), 0;2m (faint/dim), 0m (reset) monitor/pane-state.sh:37,39,148-150,162-164
monitor/watcher/fixtures/*.ansi (all)
ANSI 256-colour codes for user text, dim cursor, spinner, status bar. Used in parsing patterns and rendering validation. Colour palette changed (e.g. different grey for dim cursor, different code for bright text), 256-colour mode changed to 24-bit, or colour scheme redesigned
Box-drawing and separator characters: ─, ·, ❯, ✻, ●, ⏵, ⏺, ◉, →, ← monitor/pane-state.sh:167,256,329
monitor/watcher/fixtures/*.ansi (all)
Glyphs used in spinner rows, status bars, menu prompts, and separators. Parsed as literal strings or components of regexes. Any glyph changed (chevron, spinner, status marker, separator), Unicode representation changed, or TUI symbol set redesigned
Pane footer section markers: '--- eligible github comments ---' (functional check context) monitor/watcher/_functional_check.sh:142 Delimiter line to extract eligible-comment section from emit-archive files. Marks start/end of comment-ID extraction zone. Emit archive section header text changed, delimiter format altered, or emit markdown structure redesigned
Comment header patterns in eligible-comments section: issue= | mention= | cross_repo=; all carry id= monitor/watcher/_functional_check.sh:140-150 Extract (repo, comment_id) pairs for functional-check reaction scan. Supports default repo (issue), mention repo, cross_repo variants. Header format changed, comment-ID field name changed from id=, repo prefixes changed, or emit schema redesigned
Heartbeat staleness defaults: 30s (last_activity), 1800s (last_turn_end), 60s (async signals) monitor/pane-state.sh:414-443 Default windows for heartbeat freshness determination. Overridable via config knobs and CLI flags. Heartbeat timestamp fields no longer present, staleness semantics changed, or age-based filtering removed
ANSI CSI escape sequence stripping regex: \x1b[[0-9;?]*[a-zA-Z] monitor/pane-state.sh:244-247 Generic sed pattern to strip all CSI sequences for plain-text grep operations. Handles cursor moves, colours, styles. Claude Code adopts non-CSI escape sequences (e.g. OSC sequences, 8-bit forms), ANSI standard changes, or output layer redesigned
Window exclusion list for idle-probe: watcher, claude, orchestrator, monitor monitor/watcher/_idle_probe.sh:108-115 Names of non-worker windows to skip during idle classification. Worker windows are all others. Window naming convention changed, new special windows introduced, or system window list redefined
Action-log event types: spawn (for window birth timestamp anchor in wrap-up matching) monitor/watcher/_idle_probe.sh:130-150 Event log used to scope wrap-up matching: only wrap-up after spawn timestamp of current window lifecycle counts. Action-log event format changed, spawn event no longer recorded, or lifecycle-anchor mechanism removed
Wrap-up class values: wrapped, wrapped-but-stub, no-wrap-up, idle-too-long, pane-absent, retained monitor/watcher/_idle_probe.sh:52-77 Classification verdicts for idle workers. Pane-absent, idle-too-long, wrapped-but-stub are inviolable (never suppressed by retain). Classification vocabulary changed, new idle classes added, or wrap-up logic restructured
Pane state classification values: idle, busy, user-typing, autosuggest-only, empty, blocked, absent, over-limit, working-background, working-self-paced, idle-orphan-async monitor/pane-state.sh:6-8, 23-77
monitor/watcher/_idle_probe.sh:54
monitor/watcher/test-pane-state.sh:42-55
Complete state vocabulary emitted by pane-state.sh. Pane state forms the basis of all orchestrator and watcher decisions. State values changed, new states added, state semantics altered, or classification logic redesigned
Output format of pane-state.sh: state= active=<0|1> window= name= [extra fields] monitor/pane-state.sh:5-21, 783-795 Key=value machine-parseable single-line output. Extra fields: reset_at (over-limit), orphan_kinds (idle-orphan-async). Output format changed from key=value, field order changed, field names changed, or line structure altered
Tmux capture-pane flags: -e (interpret ANSI), -p (to stdout), -J (join lines), -S -25 (last 25 lines) monitor/pane-state.sh:157, 774 Tmux invocation to fetch pane bytes with ANSI codes preserved and lines joined (for wrapped autosuggest). Tmux removed -e / -J / -S flags, changed output format, or capture mechanism redesigned
Fixture file format (.ansi): raw tmux capture-pane output with ANSI codes intact monitor/watcher/test-pane-state.sh:14-71
monitor/watcher/fixtures/*.ansi
Test vectors for pane-state.sh. Filename prefix (autosuggest-, busy-, idle-, etc.) encodes expected state. Test fixture format changed, expected-state convention altered, or test vectors become invalid due to rendering changes

Notes

Version assumptions: assumes Claude Code renders panes via tmux with CSI escape sequences (ANSI 256-colour, reverse-video, faint/dim). Heartbeat JSON format and field names are contingent on worker-settings.json hooks. Rate-limit reset_at field path in StopFailure payload is empirically unknown (issue #129 q2); code probes multiple plausible paths. NBSP anchor (❯ vs ❯) is critical for distinguishing input row from menu rows. The complete colour palette (38;5;231, 38;5;246, 38;5;244 etc) is hardcoded in rendering patterns and would require code changes if Claude Code switches to different 256-colour indices or adopts 24-bit true colour. Monitor handle count parsing accepts both spinner-row form ('N monitor still running') and status-line form ('· N monitor ·') with automatic selection. Over-limit detection is anchored to bottom 15 rows to avoid false-match in scrollback; this constraint is load-bearing. All state classifications depend on input-row chevron anchor (❯); loss of that anchor breaks the entire classifier. Gaps: no explicit dependency on specific tmux version; no constraint on terminal emulator; no constraint on system locale affecting date parsing in over-limit reset_at extraction.


4. Claude Code — session model & jsonl session log

Fragility: 🔴 Highest

Interface Where (path:line) Purpose Breakage signal
~/.claude/projects//.jsonl (file path location) monitor/watcher/_lib.sh:898,1061
monitor/watcher/_orchestrator_liveness.sh:243
monitor/watcher/_respawn.sh:316,446
monitor/ng:693-700
monitor/watcher/test-entry.sh:453,494,521,588,626,670,702,732,762
Watcher resume logic via --continue selects mtime-newest .jsonl; orchestrator-session-pin.sh pins session_id to override fallback; ng report-init reads jsonl to extract session-id; liveness probes check jsonl mtime to infer orchestrator responsiveness (fallback signal when heartbeat/paste-received hooks unavailable); respawn chooses --resume vs --continue based on pin file validity + jsonl existence If CC moves .jsonl location (e.g., ~/.claude/sessions/), changes filename format (e.g., uuid.log, uuid.txt), or relocates project-slug directory from ~/.claude/projects/, all path-based session lookups and mtime checks fail; --continue logic breaks entirely; watcher cannot resume correct orchestrator
Project slug encoding: every non-[a-zA-Z0-9-] character → '-', including '/', '_', '.' monitor/watcher/_lib.sh:897,1060
monitor/ng:693-700
monitor/watcher/_respawn.sh:315,444
monitor/watcher/_orchestrator_liveness.sh:234
monitor/watcher/test-entry.sh:452+
monitor/watcher/test-orchestrator-liveness-probe.sh:71
monitor/watcher/test-ng-report-init.sh:242,270
Encodes absolute path like /home//my_nexus → -home--my-nexus (note underscores convert to hyphens); must match CC's internal rule to find correct ~/.claude/projects// directory; test-ng-report-init.sh Test 8 explicitly validates slug rule matches If CC changes slug rule (e.g., strips leading dash, skips '_', or uses different separator), computed slug won't match on-disk directory; session lookup fails silently; ng reports session-id: unknown; watcher cannot locate jsonl to pin/resume correct orchestrator
session_id field in jsonl (jq '.session_id') monitor/hooks/orchestrator-session-pin.sh:44
monitor/worker-heartbeat.sh:129
monitor/hooks/over-limit-emit.sh:91
monitor/hooks/decision-emit.sh:73
monitor/watcher/_respawn.sh:99
monitor/watcher/_lib.sh:896
Parses session_id from jsonl via jq -r '.session_id // empty' in hooks and scripts; UUID format validation guard ensures 8-4-4-4-12 hex pattern; pin file and respawn logic depend on extracting this field to identify pinned session; worker-heartbeat and decision-emit capture it for action logging If CC renames field (e.g., .sessionId, .session, .id) or moves it to a nested path (e.g., .meta.session_id), jq queries return empty; pin file receives empty string; UUID pattern guard exits early; --resume logic collapses to --continue; orchestrator-session-pin hook fails silently (exit 0)
UUID format: 8-4-4-4-12 hexadecimal (regex: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$) monitor/hooks/orchestrator-session-pin.sh:48
monitor/watcher/_respawn.sh:99,148,442,473,501
monitor/watcher/_lib.sh:896,1058
monitor/watcher/_orchestrator_liveness.sh:232
Guard against malformed session_id preventing torn writes (pin file) or wrong-session resume; if pattern match fails, pin file not written/updated; respawn falls back to --continue; liveness probes skip invalid pin and check jsonl mtime fallback If CC changes session-id format (e.g., shorter ID, different separator like '_' instead of '-', UUIDv7 with different hex layout), pattern match fails; pin file stales/disappears; respawn always uses --continue, risking wrong-session pickup; liveness probes degrade to jsonl-mtime-only fallback (fragile, deprecated per _orchestrator_liveness.sh header)
--resume <session-id> vs --continue command-line semantics monitor/watcher/entry.sh:220
monitor/watcher/_respawn.sh:578-592 (_respawn_choose_resume_mode)
monitor/watcher/spawn-fresh-orchestrator.sh (references _respawn_choose_resume_mode)
monitor/claude-loop.sh:202
Watcher chooses --resume when pin is valid + jsonl exists (safe, targeted); falls back to --continue (legacy, picks mtime-newest) when pin missing/stale; explicit --continue flag to entry.sh honors operator intent; --continue with no jsonl falls back to fresh; claude-loop.sh uses --continue for loop resumption If CC removes --resume flag or changes its semantics (e.g., requires session-id in different format), watcher cannot target pinned session; --resume invocation fails and respawn does not fall back automatically; orchestrator mtime-newest selection causes wrong-session pickup when workers share project slug
Pinned session-id file location: monitor/.state/orchestrator-session-id monitor/hooks/orchestrator-session-pin.sh:51,54-56
monitor/watcher/entry.sh:220
monitor/watcher/_respawn.sh:142-144,435
Hook writes session_id atomically on every UserPromptSubmit (via monitor/orchestrator-settings.json); watcher reads pin to upgrade --continue → --resume ; respawn_choose_resume_mode reads pin to decide resume mode; if missing, falls back to --continue If monitor/.state/ location changes or orchestrator-settings.json hook is removed, pin file is never written; watcher and respawn always use --continue (no pin); wrong-session mtime-newest bug returns (incident 2026-05-20)
ls -t <dir>/*.jsonl \| head -1 — mtime-based session selection (most-recent) monitor/ng:618,631 When pin is absent or jsonl missing, entry.sh and ng report-init select the most-recently-modified .jsonl in the project dir as fallback; represents 'most-recent session' heuristic; vulnerable to worker writes being newer than orchestrator's If CC changes jsonl write cadence (writes less frequently, buffers turns, or uses different log format), mtime ordering becomes unpredictable; most-recent heuristic breaks and picks wrong session; this is the fragile fallback that incident 2026-05-20 exposed
jsonl mtime freshness check (via date +%s -r <file>): signal that pinned session is still actively written monitor/watcher/_lib.sh:530-531,693-697
monitor/watcher/_orchestrator_liveness.sh:211-214
monitor/watcher/main.sh:248-278
Liveness probe checks if pinned jsonl's mtime > last_paste_ts to infer 'orchestrator is processing'; fallback signal when heartbeat/paste-received hooks absent; freshness_s thresholds default 300s; determines if pinned session is the 'live' one when multiple .jsonl files exist If CC changes jsonl write frequency (batches turns, writes less often), mtime check becomes unreliable; fresh() returns 1 (stale) when session is actually active; liveness probe thinks orchestrator is wedged and respawns; deprecation warning in _orchestrator_liveness.sh header notes this signal fragile, targeted for removal
Heartbeat and paste-received files: monitor/.state/orchestrator-heartbeat, monitor/.state/orchestrator-paste-received (mtime signals) monitor/watcher/_orchestrator_liveness.sh:14-30,112-131,172-194
monitor/watcher/main.sh:248-278
monitor/orchestrator-settings.json (via hooks)
(test) NEW signals (issue #157+) via hooks in orchestrator-settings.json; heartbeat touched by Stop hook at turn-end; paste-received touched by UserPromptSubmit on paste receipt; stronger evidence than jsonl mtime that orchestrator is alive; checked before falling back to jsonl mtime If orchestrator-settings.json hooks are removed or CC changes hook event names (Stop → StepEnd, UserPromptSubmit → InputReceived), hook payloads do not fire; heartbeat/paste-received files never written; liveness probe degrades to jsonl-mtime-only fallback (fragile, deprecated)
CLAUDE_PROJECT_DIR environment variable (Claude Code sets this per-session) monitor/ng:604-606
monitor/watcher/test-ng-report-init.sh:108,407,424
If set, ng report-init reads $CLAUDE_PROJECT_DIR/sessions/current_session_id (preferred source, most reliable); falls back to ~/.claude/projects/ scanning if unset or file missing; test shadows CLAUDE_PROJECT_DIR='' to force fallback path If CC stops exporting CLAUDE_PROJECT_DIR or relocates current_session_id file (e.g., to ~/.claude/sessions/), ng loses the preferred session-id source and falls back to .jsonl scanning; fallback is slower and less reliable (mtime-based)
Hook payload JSON: .session_id field from UserPromptSubmit and other hooks monitor/hooks/orchestrator-session-pin.sh:33-44
monitor/worker-heartbeat.sh:88-124
monitor/hooks/over-limit-emit.sh:73-91
monitor/hooks/decision-emit.sh:70-73
Hooks in monitor/orchestrator-settings.json and monitor/worker-settings.json pass JSON payload via stdin; extractors use jq -r '.session_id // empty' to capture ID for pinning, heartbeat logging, decision emission; payload shape defined by CC hook protocol If CC changes hook payload schema (renames .session_id field, wraps it in nested object, or stops including it), jq queries return empty; session_id variable stays empty; pin file not written; action logs have null session_id; orchestrator-session-pin hook silently no-ops (exit 0 on any error)
Hook event names and trigger points: UserPromptSubmit, Stop, PostToolUse, Notification, PermissionRequest (from orchestrator-settings.json and worker-settings.json) monitor/orchestrator-settings.json
monitor/worker-settings.json
monitor/hooks/orchestrator-session-pin.sh:23
monitor/worker-heartbeat.sh:34-37
monitor/watcher/_orchestrator_liveness.sh:16-30
(test) Watcher depends on specific hook events to detect orchestrator state: UserPromptSubmit writes pin/paste-received; Stop writes heartbeat; worker hooks write heartbeat for pane-state classification; liveness machine expects these events to fire in sequence If CC renames hook events (e.g., UserPromptSubmit → InputSubmit, Stop → TurnEnd) or removes them entirely, settings.json cannot wire them; hooks never fire; heartbeat/paste-received/pin files never written; liveness probe has no signals except jsonl mtime (deprecated fallback)
Session log format: JSONL (one JSON object per line), not-ndjson-strict, not protobuf, not binary monitor/ng:618-619,631-632
monitor/watcher/_lib.sh:2574,2737
monitor/README.md (comments on session structure)
Watcher assumes .jsonl files are readable text; basename -s .jsonl extracts session-id from filename; path resolution and session-id extraction assume standard naming .jsonl; if CC changes format, breakage cascades across all path-based lookups If CC switches session-log format (e.g., binary protobuf, different extension like .log or .session, directory-based storage), watcher's find ... -name '*.jsonl' fails to locate sessions; basename -s .jsonl returns wrong name; session-id extraction fails entirely
Session-id in most-recent .jsonl filename: ls -t \| head -1 \| basename -s .jsonl monitor/ng:618-619,631-632
monitor/watcher/test-ng-report-init.sh:245-246,276-277
ng report-init falls back to extracting session-id from the filename of the mtime-newest .jsonl; assumes session-id = filename without .jsonl extension; critical for ng report skeleton generation when CLAUDE_PROJECT_DIR is unset or current_session_id missing If CC embeds session-id differently in jsonl filenames (e.g., -.jsonl, _.jsonl, or no uuid at all), ng's basename -s .jsonl returns wrong/unparseable session-id; report frontmatter has session-id: unknown; tracking reports lose session context
Project directory path: ~/.claude/projects// monitor/watcher/_lib.sh:528,691
monitor/ng:616-617,628-631
monitor/watcher/_respawn.sh:97
monitor/watcher/_orchestrator_liveness.sh:209
monitor/watcher/test-entry.sh (many examples)
All session-id lookups and jsonl scans assume ~/.claude/projects/ as the root for all project directories; respawn and liveness probes construct path as ~/.claude/projects//.jsonl If CC moves project storage (e.g., ~/.claude/sessions/, CLAUDE_CONFIG_DIR/projects/, or removes the directory concept entirely), all path-based lookups fail; watcher cannot locate jsonl; cannot pin or resume; falls back to fresh sessions on every respawn
Fallback slug-matching logic: scan ~/.claude/projects/ for dir names containing cwd-tail substring monitor/ng:624-632
monitor/watcher/test-ng-report-init.sh:256-289
(test) When computed slug dir missing, ng report-init scans ~/.claude/projects/ for any dir whose name contains the cwd-tail (slugified); picks mtime-newest jsonl from that dir; emits stderr warning so divergences are visible; safety net when CC's slug rule changes or cwd renamed If CC stops writing to ~/.claude/projects/ or removes fallback dir scanning capability, this recovery path vanishes; session-id falls back to 'unknown' when slug dir missing; reports have no session context; watcher cannot detect slug-rule changes
Session-id validation and whitespace handling: strip newlines/spaces, validate UUID format monitor/hooks/orchestrator-session-pin.sh:44,48
monitor/watcher/_respawn.sh:248-250
monitor/ng:607
Defensive parsing: orchestrator-session-pin.sh and respawn strip whitespace from pin-file read; ng strips newlines from CLAUDE_PROJECT_DIR/sessions/current_session_id; pattern guard rejects malformed ids; prevents partial writes or extra newlines from breaking downstream logic If CC writes session-id with embedded whitespace, trailing newlines, or non-hex characters, pattern guard rejects it; pin file not written; respawn falls back to --continue; if validation is removed, torn/partial writes could seed paths like ~/.claude/projects//\n.jsonl

Notes

VERSION ASSUMPTIONS: This inventory targets Claude Code's current session model as of 2025-02-cutoff, assuming: (1) JSONL session logs at ~/.claude/projects//.jsonl; (2) session_id as 8-4-4-4-12 UUID in hook payloads and current_session_id file; (3) project-slug encoding as all-non-alphanumeric→hyphen (including '/', '_', '.') matching CC's internal path normalization; (4) hook events (UserPromptSubmit, Stop, PostToolUse, Notification) fire with JSON payloads; (5) --resume and --continue as valid command-line flags.

DEPRECATION NOTICES: _orchestrator_liveness.sh header (lines 32-36) marks jsonl-mtime-only signal as deprecated fallback slated for removal after "the deprecation window" — earliest version where heartbeat/paste-received hooks became mandatory unknown, but new liveness machine (issue #164) is designed to outlive it.

GAPS & AMBIGUITIES: (1) No inventory of monitor/orchestrator-settings.json hook schema itself — changes to hook event names, payload field names, or the hooks section structure are breaking but live in JSON, not covered here; (2) CLAUDE_PROJECT_DIR path and current_session_id location inside it are not catalogued — changes to CC's internal session bookkeeping location would break ng's preferred source; (3) Hook asynchronous-background execution (backgrounding touches via (... &) >/dev/null 2>&1) is not listed as a dependency but would break if CC changes hook execution model; (4) Session-log write frequency is assumed empirically (frequent-enough for mtime granularity) but no formal contract exists; (5) timestamp-precision granularity in session-id or mtime comparisons is not specified — potential brittle-ness in "mtime > last_paste_ts" comparisons if CC switches to sub-second or drifts write cadence.

ENTRY COMPLETENESS: Searched all .sh files under monitor/, monitor/watcher/, monitor/hooks/, plus monitor/ng and related JSON settings. Focused on non-test references for "where"; test-only usages (test-.sh, test-integration/) are tagged "(test)" in purpose field when inclusion felt critical to understanding the contract. Some entries compress 8+ locations into representative paths + "+N more" when patterns were obvious (e.g., every test-.sh that uses slug encoding).


5. Anthropic API (direct HTTP probe)

Fragility: 🟠 High (model-ID + API version)

The watcher's optional rate-limit probe (RATELIMIT_PROBE=true, default off) calls the Anthropic Messages API directly to discover when a weekly limit resets, instead of guessing from the heuristic. This is the only place nexus-code speaks to the model API directly (everything else goes through the claude binary).

Interface Where (path:line) Purpose Breakage signal
POST https://api.anthropic.com/v1/messages monitor/watcher/_unstick.sh:294 Minimal 1-token request whose response headers carry the rate-limit reset time Endpoint path/host change; Messages API request or response schema change; endpoint deprecation
anthropic-ratelimit-unified-reset / anthropic-ratelimit-tokens-reset (response headers) monitor/watcher/_unstick.sh:298,300 Parsed (preferring unified-reset, falling back to tokens-reset) to learn the reset epoch Header renamed/removed; reset value format change (epoch vs RFC-3339)
x-api-key header monitor/watcher/_unstick.sh:290 Auth for the probe, sourced from ANTHROPIC_API_KEY Auth scheme change (e.g. switch to Authorization: Bearer); key format change
anthropic-version: 2023-06-01 header monitor/watcher/_unstick.sh:291 Pins the Anthropic API version for the probe request The 2023-06-01 version is retired or its rate-limit header contract changes
model = claude-haiku-4-5-20251001 (default probe model) monitor/watcher/_unstick.sh:287 (PROBE_MODEL default) Cheapest model for the throwaway probe; overridable via PROBE_MODEL (wired from monitor.watcher.probe_model / MONITOR_PROBE_MODEL) Model ID retired/renamed → probe 404s; the hardcoded default must track a live, cheap model
request body fields: model, messages[].role/content, max_tokens monitor/watcher/_unstick.sh:293 Messages API request shape (max_tokens:1, content:'.') Required-field rename, role enum change, max_tokens semantics change
ANTHROPIC_API_KEY env var monitor/watcher/_unstick.sh:324,340 Credential gate — probe is skipped entirely when unset (nexus-owned env; low churn) — only relevant if the API stops accepting key-based auth

Notes

Disabled by default (monitor.watcher.ratelimit_probe: false). When off, the watcher uses ratelimit_heuristic_minutes (default 30) instead and never touches this surface.


6. tmux

Fragility: 🟠 High

Interface Where (path:line) Purpose Breakage signal
tmux info monitor/spawn-worker.sh:260 Check if tmux server is running before spawning worker window info subcommand removed or semantics changed
tmux new-window monitor/spawn-worker.sh:362
monitor/watcher/_respawn.sh:147
monitor/watcher/test-integration/_harness.sh:152
Create detached worker/orchestrator windows with -d flag, -n for window name, -c for working directory, and optionally a command to execute flags -d, -n, -c removed or semantics changed; return code semantics shift
tmux send-keys monitor/spawn-worker.sh:385
monitor/watcher/_respawn.sh:212,259,268
monitor/watcher/_unstick.sh:259,495,503,522,552
monitor/watcher/main.sh:1272
Send keyboard input (Enter key, Escape, literal text with i BSpace for VI-mode) to target window to dismiss modals, submit input, or trigger actions key name semantics change (Enter, Escape); special key handling removed; target selection via -t flag breaks
tmux list-windows monitor/spawn-worker.sh:263
monitor/watcher/entry.sh:162
monitor/watcher/_respawn.sh:144
monitor/watcher/_over_limit.sh:298
monitor/watcher/_unstick.sh:547,682
monitor/watcher/main.sh:599,989
monitor/pane-state.sh:237
Query window list with format tokens (-F flag) to check window existence, extract window names/indices/bells, support filtering by session -F format flag removed; format tokens #{window_name}, #{window_index}, #{window_bell_flag} renamed/removed
tmux set-window-option monitor/spawn-worker.sh:369,383,384
monitor/watcher/_respawn.sh:148
monitor/hooks/orchestrator-session-pin.sh:93
monitor/watcher/launcher.sh:175
monitor/watcher/test-integration/test-graceful-exit-relaunch.sh:107,200
Set window options: remain-on-exit on (keep pane visible after exit), automatic-rename off (prevent tmux rename), allow-rename off (prevent OSC renames) set-window-option subcommand removed; option names remain-on-exit/automatic-rename/allow-rename removed or renamed; on/off value semantics change
tmux display-message monitor/watcher/entry.sh:149
monitor/pane-state.sh:760,765,766,771
Extract window/pane metadata using format tokens: #{window_id}, #{window_index}, #{window_name}, #{window_active}, #{pane_pid}; -p flag for printing display-message subcommand or -p flag removed; format tokens #{window_id}, #{window_index}, #{window_name}, #{window_active}, #{pane_pid} renamed/removed
tmux display monitor/watcher/entry.sh:149
monitor/watcher/main.sh:1001
Query window/session metadata with -p flag; short form of display-message display subcommand or -p flag semantics changed; format token compatibility with display-message
tmux capture-pane monitor/pane-state.sh:774,915
monitor/watcher/_unstick.sh:436,517
monitor/watcher/main.sh:1292
monitor/watcher/test-integration/_harness.sh:168
Capture pane bytes with flags: -p (print to stdout), -e (include ANSI escape codes), -J (join wrapped lines), -S (start line, typically -25 for scrollback), -t (target window) capture-pane subcommand removed; flags -p, -e, -J, -S semantics change; escape code handling differs
tmux kill-window monitor/watcher/_respawn.sh:145 Kill/close a tmux window by name or index via -t flag -t flag semantics change; kill-window subcommand removed
tmux rename-window monitor/watcher/entry.sh:245
monitor/hooks/orchestrator-session-pin.sh:92
monitor/bootstrap-install.sh:254
Rename window to fixed name (watcher, orchestrator, bootstrap) to establish stable targeting across lifecycle rename-window subcommand removed; -t flag or default behavior changes
tmux select-window monitor/watcher/main.sh:1006 Change active window (used for cold-start UX, bell-clearing helper session without affecting attached client focus) select-window subcommand removed; -t flag semantics change
tmux load-buffer monitor/watcher/_respawn.sh:261
monitor/watcher/_unstick.sh:496
monitor/watcher/main.sh:1274
Load file content into tmux paste buffer with -b buffer-name flag for VI-mode hardening (paste-then-Enter pattern) load-buffer subcommand or -b flag removed; buffer semantics change
tmux paste-buffer monitor/watcher/_respawn.sh:263
monitor/watcher/_unstick.sh:498
monitor/watcher/main.sh:1275
Paste buffer content into target window with -b buffer-name and -t target flags paste-buffer subcommand or -b/-t flags removed; buffer delivery semantics change
tmux delete-buffer monitor/watcher/_respawn.sh:265,271
monitor/watcher/_unstick.sh:499,504,507
Clean up temporary paste buffers after paste operations (via -b flag) delete-buffer subcommand or -b flag removed
tmux new-session monitor/watcher/main.sh:1003
monitor/watcher/test-integration/_harness.sh:100
Create detached helper session for bell-clearing (with -d flag, -s session-name, -t session-target); used with specific session management new-session subcommand or -d/-s/-t flags removed or semantics changed
tmux kill-session monitor/watcher/main.sh:1008
monitor/watcher/test-integration/_harness.sh:113
Terminate helper session for bell-clearing after use kill-session subcommand or -t flag removed
tmux has-session monitor/watcher/launcher.sh:157 Check if tmux server/session exists without starting fresh server has-session subcommand removed; return code semantics change
#{window_name} monitor/spawn-worker.sh:263
monitor/watcher/entry.sh:162
monitor/watcher/_respawn.sh:144
monitor/watcher/_over_limit.sh:298
monitor/watcher/_unstick.sh:547,682
monitor/watcher/main.sh:599,989
monitor/pane-state.sh:237,765
Format token for tmux list-windows/display-message to extract window names used for targeting and lifecycle tracking #{window_name} token removed, renamed, or returns different type
#{window_index} monitor/watcher/main.sh:989
monitor/pane-state.sh:237,760
monitor/watcher/_over_limit.sh:298
Format token to extract numeric window index for pane-state.sh and window iteration #{window_index} token removed, renamed, or return type changes
#{window_active} monitor/pane-state.sh:766 Format token to query whether window is active in session (binary flag for snapshot) #{window_active} token removed or return value semantics change
#{window_id} monitor/watcher/entry.sh:149 Format token to capture window's unique ID for targeting rename operations independent of focus shifts #{window_id} token removed or format changes
#{window_bell_flag} monitor/watcher/main.sh:599,989 Format token to check bell flag on windows for filtering (non-zero = bell set; used to skip orchestrator/claude/monitor windows) #{window_bell_flag} token removed or return value semantics (0/1 or true/false) changes
#{pane_pid} monitor/pane-state.sh:771 Format token to extract pane's process ID for liveness checking (_pane_has_live_claude) #{pane_pid} token removed or returns different type/empty string on no-pid-yet
#{window_activity} config/nexus.example.yml:162
monitor/watcher/_idle_probe.sh:110
Format token for idle-worker probe; epoch timestamp of last activity for age calculation #{window_activity} token removed or epoch-seconds semantics change
remain-on-exit option monitor/spawn-worker.sh:369
monitor/watcher/_respawn.sh:148
monitor/watcher/launcher.sh:175
monitor/watcher/test-integration/test-graceful-exit-relaunch.sh:107,200
Window option to keep pane visible with [exited] status after process exit; load-bearing for retention-by-default and wrap-up detection remain-on-exit option removed, renamed, or on/off value handling changes
automatic-rename option monitor/spawn-worker.sh:383
monitor/hooks/orchestrator-session-pin.sh:93
Window option (set to off) to prevent tmux from auto-renaming window based on pane_current_command; prevents phantom renames of dead panes automatic-rename option removed, renamed, or on/off semantics change
allow-rename option monitor/spawn-worker.sh:384 Window option (set to off) to prevent OSC escape sequences from renaming the window; defends against Claude Code's window-title emission allow-rename option removed, renamed, or on/off value handling changes
tmux -L flag (socket) monitor/watcher/test-integration/_harness.sh:87,102
monitor/watcher/test-respawn-loop-integration.sh:99
(test-only) Socket name for isolated per-test tmux server (prevents collision with operator's live nexus) -L flag removed or socket isolation semantics change (test)
tmux -f flag (config) monitor/watcher/test-pane-state.sh:918
monitor/watcher/test-integration/_harness.sh:100
(test-only) Load config file (set to /dev/null to neutralise operator's tmux.conf) -f flag removed or config load semantics change (test)
tmux -S flag (socket path) Alternative to -L for specifying absolute socket path (not directly used in repo, but supported tmux interface) -S flag removed or socket resolution semantics change
tmux setenv monitor/watcher/test-integration/_harness.sh:102,103 (test-only) Set global environment variables in tmux session for child processes (PATH, NEXUS_ROOT) setenv subcommand or -g flag removed (test)
tmux kill-server monitor/watcher/test-integration/_harness.sh:113 (test-only) Tear down entire tmux server (harness cleanup) kill-server subcommand removed (test)
TMUX environment variable monitor/watcher/launcher.sh:157 Check if running inside tmux session; $TMUX is set by tmux for child processes TMUX env var no longer set by tmux for child processes
TMUX_PANE environment variable monitor/watcher/entry.sh:171,178
monitor/hooks/orchestrator-session-pin.sh:102-106
Pane ID passed by tmux to child processes; used for window targeting and identity verification TMUX_PANE env var no longer set or format (e.g., %42) changes

Notes

Version assumptions: Code targets tmux 2.6+ (based on test notes about CSI 3J in stub-claude.sh line 70, tmux <= 2.6 caveats). The repo explicitly acknowledges tmux 2.x vs 3.x differences in display-message behavior (pane-state.sh lines 755-757). Format tokens (#{...}) are stable across 2.6-3.x releases tested in GitHub Actions. Key load-bearing interfaces: (1) remain-on-exit on + capture-pane for wrap-up detection, (2) display-message + format tokens for window metadata extraction, (3) load-buffer + paste-buffer + send-keys for prompt injection, (4) window renaming for lifecycle stability, (5) list-windows for worker discovery. Test-only usages (marked "(test)") include socket isolation (-L), config override (-f), and harness teardown (kill-server). The -W flag for list-windows is not used; sorting/filtering is done client-side via awk/grep. Session/pane targeting syntax: "session:window" or "session:window.pane" or "%pane_id" (pane IDs like %42 from TMUX_PANE). Buffer names are arbitrary strings (nexus-respawn-$$-epoch pattern). No hard dependency on base-index or pane-base-index defaults (always use explicit indices/names). The send-keys "Enter" must be capitalized (not "enter"); "Escape" and "BSpace" are special key names. No documented dependency on $session_name, $pane_current_command extraction (though pane_pid liveness check uses ps to inspect /proc//comm matching "claude"). Status-bar or color configuration is not invoked (tests use -f /dev/null to suppress user tmux.conf). No direct mouse mode, copy-mode, or binding modification. The orchestrator-session-pin hook is gated by NEXUS_IS_ORCHESTRATOR env var to avoid side effects on non-orchestrator sessions.


7. gh CLI + GitHub REST/GraphQL API

Fragility: 🟠 High

Interface Where (path:line) Purpose Breakage signal
gh auth token monitor/user-pat.sh:83 Retrieve user OAuth PAT for private-repo reads; fallback if GITHUB_PAT/NEXUS_USER_PAT not set gh auth token output format changes or flag removed; output no longer returns token string
gh auth status monitor/user-pat.sh:82 Check if gh is logged in on the host before attempting to retrieve token gh auth status behavior changes; exit code or output format changes
gh api /rate_limit monitor/watcher/_github.sh:140
monitor/watcher/_github.sh:143
Probe free core rate-limit bucket; gate GraphQL polling if remaining < threshold .resources.graphql.remaining field removed/renamed; endpoint returns different structure
gh api graphql with -f query and -f q parameters monitor/watcher/_github.sh:470-490
monitor/watcher/_github.sh:543-579
monitor/watcher/_github.sh:629-645
monitor/watcher/_mentions.sh:125-175
Execute GraphQL search queries for issue/PR comments, new issues, mentions across all repos GraphQL API deprecated; field names removed; query syntax changes; schema incompatible
GraphQL query field: search(type: ISSUE, query, first) monitor/watcher/_github.sh:1199
monitor/watcher/_github.sh:1301
monitor/watcher/_github.sh:1387
monitor/watcher/_mentions.sh:138,517
Search for issues and PRs by repo+state filters and mentions search field removed; type ISSUE deprecated; query parameter renamed/removed; first pagination parameter changes
GraphQL query fields: Issue/PullRequest.number, .comments(last/first), .databaseId monitor/watcher/_github.sh:476-490
monitor/watcher/_github.sh:549-575
Retrieve issue/PR numbers, comment batches, and database IDs for dedup Field renamed/removed (number→id, databaseId deprecated, comments pagination changes)
GraphQL query fields: comment nodes with .author.login, .body, .reactions(first/last) monitor/watcher/_github.sh:478-483
monitor/watcher/_github.sh:551-569
Extract comment author, body text, and reactions (EYES/ROCKET) for eligibility filtering author field moved/renamed; body field removed; reactions field structure changes; content enum values change
GraphQL reaction field: .reactions.nodes[].content monitor/watcher/_github.sh:482-484
monitor/watcher/_github.sh:555-557
monitor/watcher/_github.sh:638-640
monitor/watcher/_mentions.sh:137-146
Check for EYES and ROCKET emoji reactions to gate comment surfacing reactions field removed; content enum values change (EYES/ROCKET renamed); nodes structure changes
GraphQL query fields: PullRequest.reviewThreads(last), .comments(last), .path monitor/watcher/_github.sh:560-571 Retrieve PR review-thread (inline) comments with file path for pr_review= shape reviewThreads field removed/renamed; comments inside reviewThreads change structure; path field moved/removed
GraphQL query fields: Issue.author.login, Issue.reactions(first) monitor/watcher/_github.sh:636-640 Check for new issues authored by user and reactions to gate emission author field renamed/removed; reactions structure changes; login field moved
gh api --paginate /installation/repositories --jq '.repositories[].full_name' monitor/watcher/_mentions.sh:245 Fetch all repos where bot is installed (paginated) to skip them in mentions path .repositories array removed; .full_name field renamed; --paginate flag removed; --jq no longer works with --paginate
gh api graphql query: mentions: search monitor/watcher/_mentions.sh:125-175 Search for cross-repo mentions of user in issues/PRs not covered by deliveries path mentions: search qualifier removed; search endpoint behavior changes; query parameter format changes
GraphQL query fields for mentions: Issue/PR.repository.nameWithOwner, Issue/PR.databaseId monitor/watcher/_mentions.sh:136, 157 Extract repo identity and database ID for cross-repo mentions dedup repository.nameWithOwner field removed/renamed; databaseId removed; repository structure changes
curl + Authorization Bearer (JWT) to POST /app/installations//access_tokens monitor/mint-token.sh:176-180 Exchange GitHub App JWT for installation-scoped access token (bot identity) Endpoint path changes; JWT format no longer accepted; response structure changes (missing .token or .expires_at); API version header rejected
GitHub App JWT minting (RS256 signed token with iss, iat, exp claims) monitor/mint-token.sh:193-207 Create App-level JWT for authentication to /app/* endpoints and installation token exchange JWT signing algorithm rejected; claim names change; algorithm RS256 no longer supported; exp/iat validation window changes
curl POST /app/installations//access_tokens response fields: .token, .expires_at monitor/mint-token.sh:182-183 Extract bot installation token and expiry timestamp for caching .token field removed/renamed; .expires_at field removed or format changes; response structure incompatible
curl + Authorization Bearer (JWT) to GET /app/hook/deliveries?per_page= monitor/watcher/_deliveries.sh:177-182 Fetch App webhook delivery log (events across all installations); root for cross-repo discovery Endpoint path changes; JWT no longer accepted; per_page parameter removed; response structure changes; pagination (Link header) removed
curl GET /app/hook/deliveries response fields: .guid, .id, .event, .action monitor/watcher/_deliveries.sh:224-225, 294-398 Parse delivery list for GUID cursor tracking and event metadata extraction .guid field removed; .id field structure changes (64-bit integer precision); .event/.action enums change; response pagination changes
curl + Authorization Bearer (JWT) to GET /app/hook/deliveries/ monitor/watcher/_deliveries.sh:275-280 Fetch full payload of single webhook delivery for event classification and emit Endpoint path changes; JWT auth no longer accepted; response structure incompatible; .request.payload location changes
Webhook delivery payload fields: .event, .action, .request.payload monitor/watcher/_deliveries.sh:296-372 Extract event type, action (created/submitted/opened), and webhook payload for comment/issue/PR classification .event enum values change (issue_comment, pull_request_review_comment, etc. removed); .action values change; .request.payload moved/removed
Webhook payload fields: issue_comment event with .issue.number, .comment.id, .comment.user.login, .comment.body monitor/watcher/_deliveries.sh:305-314 Extract comment metadata (issue number, comment ID, author, body) for issue/PR comment emissions Field paths change; .comment structure reorganized; .user.login renamed; .body format changes
Webhook payload fields: pull_request_review_comment with .comment.path monitor/watcher/_deliveries.sh:315-325 Extract file path for pr_review= shape (inline diff comments) .comment.path field removed; moved to different location; format changes
Webhook payload fields: pull_request_review event with .review.id, .review.user.login, .review.body, action==submitted monitor/watcher/_deliveries.sh:326-339 Extract PR review metadata; gate on 'submitted' action to surface only new reviews, not edits .review structure changes; .review.id field removed; .review.user.login moved; action values change (submitted deprecated)
Webhook payload fields: issues event with .issue.number, .issue.user.login, .issue.body, action==opened monitor/watcher/_deliveries.sh:340-350 Extract new issue metadata; gate on 'opened' action Field paths change; .issue.user.login structure changes; action values change (opened deprecated)
Webhook payload fields: pull_request event with .pull_request.number, .pull_request.id, .pull_request.user.login, .pull_request.body, action==opened monitor/watcher/_deliveries.sh:351-366 Extract PR open event metadata; gate on 'opened' action Field structure changes; .pull_request.id/number/user.login moved/renamed; action values change
REST POST /repos//issues/comments//reactions monitor/ng:405, 489 Add reaction (eyes/rocket) to comment; called from ng process/react verbs Endpoint path changes; parameter format changes; POST no longer supported; reactions/:+1 format changes
REST POST /repos//issues//reactions monitor/ng:441, 538 Add reaction to issue itself (not comments); called from ng process-issue/react-issue verbs Endpoint path changes; POST no longer accepted; reaction content enum changes
reaction content enum: EYES, ROCKET monitor/ng:405, 441, 489, 538 POST request body specifies emoji reaction type for comment/issue reactions EYES/ROCKET enum values renamed/removed; format changes; validation rejects unknown values
REST POST /repos//issues/comments//reactions response field: .id monitor/ng:407, 443 Extract reaction ID from response for optional confirmation print .id field removed; response structure changes
REST GET /repos//issues/comments/ (comment metadata) monitor/ng:471-473 via api call Fetch comment metadata (author, body, issue_url) for ng show/process verbs Endpoint removed; GET no longer supported; response structure incompatible; .user.login/body/.issue_url fields removed
Comment metadata fields: .user.login, .body, .issue_url monitor/ng:471-473 Extract comment author login, body text, and backlink to parent issue .user.login renamed; .body field removed; .issue_url moved/removed; URL format changes
REST GET /repos//issues/ (issue metadata) monitor/ng:1493, 1842, 1866 Fetch issue body for ng issue view, dashboard updates Endpoint removed; GET no longer supported; .body field removed; .state field renamed
Issue metadata fields: .number, .state, .title, .body, .created_at, .user.login monitor/ng:1493, 1498, 1504 Extract issue state, title, body, creation date, and author for display and dashboard .state field renamed/removed; .number format changes; .body moved; .created_at format incompatible; .user.login structure changes
REST GET /repos//issues//comments (pagination: ?per_page=100) monitor/ng:1503 Fetch chronological comments for full issue view (ng issue view --with-comments) Endpoint removed; GET no longer supported; per_page parameter removed; pagination format changes; response structure incompatible
REST POST /repos//issues (create issue) monitor/ng:1541 Create new issue via ng issue create verb Endpoint path changes; POST no longer accepted; request body schema changes; response .html_url/.number fields removed
Issue create request body: monitor/ng:1533-1537 Build POST payload for issue creation with title, body, and optional label array Field names change; labels field format changes; title/body length limits change; new required fields added
Issue create response fields: .html_url, .number monitor/ng:1542 Extract URL and number from newly-created issue for confirmation .html_url field removed/renamed; .number format changes
REST POST /repos//issues//comments (reply to issue/PR) monitor/ng:573 Post comment reply on issue via ng reply verb Endpoint path changes; POST no longer accepted; request body schema changes; response structure incompatible
Comment create request body: monitor/ng:572 Build POST payload for comment creation Field name changes; body content validation changes; new required fields
REST PATCH /repos//issues/ (close issue) monitor/ng:577 Close issue via ng close verb (PATCH with state=closed) Endpoint path changes; PATCH no longer supported; state parameter renamed/removed; response structure changes
Issue PATCH request body: monitor/ng:577 Build PATCH payload to set issue state to closed state field renamed/removed; enum value changes (closed deprecated); format changes
REST POST /repos//pulls (create PR) monitor/ng:1627 Create PR via ng pr create verb Endpoint path changes; POST no longer accepted; request body schema changes; response .html_url/.number removed
PR create request body: monitor/ng:1625 Build POST payload for PR creation with title, body, branch refs, and maintainer edit flag Field names change; head/base format changes; maintainer_can_modify field removed/renamed; new required fields
REST POST /repos//pulls//requested_reviewers (request review) monitor/ng:1638 Request review from user on newly-created PR (if reviewer specified) Endpoint path changes; POST no longer accepted; request body schema changes; reviewers field format changes
Review request body: monitor/ng:1637 Build POST payload with list of reviewers to request Field name changes; reviewers format changes (array of strings→objects, etc.); validation rejects unknown users
REST PATCH /repos//pulls/ (edit PR) monitor/ng:1671 Edit PR title/body via ng pr edit verb Endpoint path changes; PATCH no longer supported; request body schema changes; field format changes
PR edit request body: monitor/ng:1662, 1667 Build PATCH payload with optional new title and/or body Field names change; content validation changes; format changes
REST GET /repos//pulls/ (fetch PR metadata) monitor/ng:1696 Fetch PR head.ref (branch name) for delete-branch logic in ng pr merge Endpoint removed; GET no longer supported; .head.ref field removed/moved; response structure changes
PR metadata field: .head.ref monitor/ng:1696 Extract head branch name to delete it after merge (if --delete-branch specified) .head.ref field removed/renamed; moved to different location; format changes
REST PUT /repos//pulls//merge (merge PR) monitor/ng:1698 Merge PR via ng pr merge verb with specified strategy (squash/merge/rebase) Endpoint path changes; PUT no longer accepted; request body schema changes; merge_method enum changes
PR merge request body: monitor/ng:1697 Build PUT payload with merge strategy (merge/squash/rebase) Field name changes; merge_method enum values change; validation rejects unknown values
PR merge response field: .sha monitor/ng:1699 Extract merge commit SHA for confirmation print .sha field removed/renamed; response structure incompatible
REST DELETE /repos//git/refs/heads/ (delete branch) monitor/ng:1704 Delete PR head branch after merge (if --delete-branch specified) Endpoint path changes; DELETE no longer accepted; response structure changes
REST GET /repos//pulls/ (view PR) monitor/ng:1721 Fetch PR metadata for display via ng pr view Endpoint removed; GET no longer supported; response structure incompatible; field names change
PR view response fields: .number, .state, .user.login, .head.ref, .base.ref, .title monitor/ng:1721 Extract PR number, state, author, source/target branches, and title for display Fields moved/removed/renamed; .user.login structure changes; .state enum changes
gh api --paginate /installation/repositories (preflight repo check) monitor/ng:349 Check if bot is installed on a given repo (ng preflight command) .repositories array removed; .full_name field removed; --paginate no longer works
REST GET /repos//issues (dashboard query with labels filter) monitor/ng:1831 Fetch nexus overview issue for dashboard updates (filtered by label:nexus:overview) Endpoint path changes; GET no longer supported; labels query parameter removed
REST PATCH /repos//issues/ (dashboard update) monitor/ng:1875 Update dashboard body on nexus overview issue via ng dashboard put Endpoint path changes; PATCH no longer supported; response body structure changes
X-GitHub-Api-Version: 2022-11-28 header monitor/mint-token.sh:179
monitor/watcher/_deliveries.sh:181, 279
monitor/user-pat.sh:114
Specify API version for curl-based REST/JWT auth calls to GitHub Header rejected; API version no longer supported; required header format changes
Accept: application/vnd.github+json header monitor/mint-token.sh:178
monitor/watcher/_deliveries.sh:180, 278
monitor/user-pat.sh:113
Request JSON response format from GitHub REST API Content-type no longer recognized; API returns different format; header format changes

Notes

Version Assumptions and Compatibility Notes:

  1. GitHub REST API Version (2022-11-28): Hardcoded in curl calls and gh CLI invocations. If GitHub retires this version, header X-GitHub-Api-Version must be updated across monitor/mint-token.sh, monitor/watcher/_deliveries.sh, and monitor/user-pat.sh. The gh CLI may auto-negotiate a newer version; curl calls will fail outright if the header is no longer accepted.

  2. GraphQL Schema Stability: GraphQL queries in _github.sh and _mentions.sh rely on exact field names and structure. Breaking changes include: (a) removing databaseId (deprecated in favor of id on many objects), (b) changing comments(last: N) pagination pagination, © renaming reactions.nodes[].content enum values (EYES/ROCKET), (d) removing .repository.nameWithOwner in favor of .repo or similar, (e) removing reviewThreads collection on PR objects. The per-surface backoff and gate mechanisms (_graphql_backoff_active, _graphql_polling_gate, _watcher_handle_graphql_failure) are resilient to some transient failures but will fail outright on schema changes.

  3. Installation Token Lifecycle: POST /app/installations//access_tokens is the JWT→token exchange. Breakage scenarios: (a) endpoint path changes, (b) JWT format rejected (currently RS256 with iss/iat/exp), © response .token/.expires_at fields missing, (d) expiry format incompatible with date -d (currently ISO 8601). The 55-minute cache window (300 s safety margin) assumes expires_at is reliably 1 hour in the future; if GitHub changes the TTL, cache hits/misses will become stale.

  4. Webhook Delivery Payload Structure: _deliveries.sh parses .event and .action enums (e.g., issue_comment, pull_request_review_comment, created, submitted, opened) to gate emission. If GitHub changes these enum values, the conditional logic on line 313 onward (case "$event" ... elif ...) will silently drop events and surface nothing, even though the API call succeeds. Similarly, field paths like .request.payload.issue.number and .comment.user.login are hardcoded; reorganization (e.g., .issue → .issue_data) breaks silently.

  5. Reaction Content Enum (EYES, ROCKET): Hardcoded in ng verb bodies and GraphQL jq filters. If GitHub renames these enums (e.g., "EYES" → "EYE", "ROCKET" → "ROCKET_EMOJI") or removes them, reactions will silently POST invalid content (4xx error) or fail to filter incoming reactions (no matching .content value). The _functional_check helpers that test reactions assume the enum values are stable.

  6. Rate Limit Bucket Structure: _graphql_polling_gate expects .resources.graphql.remaining from /rate_limit. If GitHub flattens the structure (e.g., .graphql.remaining instead of .resources.graphql.remaining), the jq extraction fails silently and the gate assumes bucket is healthy, potentially drowning in rate-limit errors. The probe also assumes the free REST core bucket (used for /rate_limit itself) is separate from the GraphQL bucket; convergence or deprecation of one will degrade the gate's utility.

  7. Cross-Repo Context: snapshot_mentions and deliveries (mention= shape) rely on .repository.nameWithOwner to disambiguate cross-repo events. _filter_cross_repo_surface makes routing decisions based on whether the emit header contains mention= or cross_repo= to distinguish App-installed repos (mention, can comment) from search-only hits (cross_repo, cannot comment). If GitHub changes how these contexts are signaled in webhooks or search, the downstream routing and capability model breaks.

  8. No GraphQL Mutations: The watcher is read-only on the GraphQL side. All writes (reactions, comments, issue/PR updates) go through REST. If GitHub moves reactions to GraphQL-only or deprecates the REST endpoints, substantial refactoring is needed.

  9. Test Infrastructure: Many test files (test-emit-gate.sh, test-graphql-gate.sh, etc.) mock gh by shadowing it on PATH with a shell stub. These mocks hardcode response shapes (e.g., {"graphql":{"remaining":5000,...}}). Tests will fail immediately if response shapes change, but production code may not fail until that code path is actually executed (e.g., a new edge case in the snapshot logic).

  10. Pagination Cursor Stability: monitor/.state files (last-mention-cursor.txt, last-delivery-cursor.txt, processed-comments.txt) store comment databaseIds, delivery GUIDs, and issue numbers. If GitHub changes how these identifiers are minted, assigned, or persisted (e.g., databaseId format changes from 32-bit to 64-bit int), the cursor-based dedup logic may skip comments or re-emit duplicates.

  11. HTTP Status Codes: Code assumes 200 = success, 404 = not found (webhook not configured), 401 = auth failure. If GitHub changes status semantics (e.g., 404 → 410 Gone for /app/hook/deliveries when webhook is unconfigured), the handling may silently degrade or error differently.

  12. Completeness: This inventory covers every gh api, gh auth, curl -H Authorization: Bearer, GraphQL query field, and REST endpoint used in production watcher and ng code. Test-only usages are tagged (test). The inventory is per-file-location; if code is refactored across files or files are deleted, the paths will need updating but the interface dependencies remain the same.


8. git

Fragility: 🟡 Medium

Interface Where (path:line) Purpose Breakage signal
git clone monitor/bootstrap-install.sh:125
monitor/upload-asset.sh:137
monitor/watcher/test-upload-asset-gitignore.sh:129
Clone asset repo (production) and test fake remotes (test). Production uses HTTPS URL with x-access-token auth; tests use local bare repos. git clone output format change, URL scheme rejection (https vs git@), authentication method incompatibility, or exit code changes
git -C remote set-url origin monitor/upload-asset.sh:140
monitor/upload-asset.sh:181
Production: repoint asset repo origin to authenticated HTTPS URL before fetch/push, then back to unauthenticated form after push to strip credentials. Matches pattern in test stub. remote set-url command removal, option flag changes (--push vs default), or output format change affecting error detection
git -C fetch --quiet origin monitor/upload-asset.sh:659 Production: fetch latest asset repo state before upload to minimize push races. Branch hardcoded as 'main'. fetch option/flag incompatibility, default branch assumption failure if origin HEAD changes, or porcelain output format change
git -C checkout --quiet monitor/upload-asset.sh:660 Production: switch asset repo working tree to main branch before adding/committing assets. checkout command option removal, detached HEAD state changes, or branch non-existence error messaging
git -C reset --hard --quiet origin/ monitor/upload-asset.sh:661 Production: hard-reset asset repo working tree to match remote main, discarding any local changes before commit. reset command removal, --hard flag changes, origin tracking behavior change, or exit code semantics
git -C check-ignore -q -- monitor/upload-asset.sh:156 Production: detect if upload destination path matches .gitignore rules in asset repo; when matched, breadcrumb message emitted and file force-added. check-ignore command removal/rename, quiet flag behavior change, path argument handling change, or exit code semantics (0=ignored, 1=not-ignored) reversal
git -C add -f -- monitor/upload-asset.sh:162
monitor/watcher/test-upload-asset-gitignore.sh:90
Production: stage file for commit, force-adding past any .gitignore rules. Test: seed .gitignore file into test repos. add command removal, -f/--force flag removal or behavior change, double-dash handling change, or exit code semantics
git -C diff --cached --quiet monitor/upload-asset.sh:896 Production: check if staged changes exist (to skip commit if file unchanged). Quiet mode; exit code (0=no changes, non-0=changes) is checked. diff command option removal, --cached vs --staged naming, quiet flag behavior change, or exit code semantics reversal
git -C commit --quiet --author= -m monitor/upload-asset.sh:900-901
monitor/watcher/test-upload-asset-gitignore.sh:95
Production: commit staged asset with custom author identity (bot). Test: seed commits with minimal identity. commit option removal, --author format/parsing change, -m vs --message change, --quiet behavior change, or exit code semantics
git -C push --quiet origin monitor/upload-asset.sh:915
monitor/watcher/test-upload-asset-gitignore.sh:97
Production: push committed assets to remote main branch. Test: push seeded commits to bare test repo. push command option removal, --quiet behavior change, origin tracking assumptions, default branch name changes, exit code semantics
git -C rev-parse HEAD monitor/upload-asset.sh:178
monitor/watcher/main.sh:606
monitor/watcher/test-upload-asset-gitignore.sh:182
Production (upload): resolve SHA of freshly-pushed commit for URL pinning. Production (watcher): snapshot HEAD of each work/ directory. Test: query post-push commit SHA for assertions. rev-parse command removal, HEAD reference change semantics, detached HEAD handling, or output format change (full SHA vs short)
git -C status --porcelain monitor/watcher/main.sh:1571 Production (watcher): detect dirty/clean state of each work/ for local-state snapshot emitted to tmux. Porcelain format parsed for emptiness (clean=empty, dirty=non-empty). porcelain format v1 vs v2 change, empty output on clean assumption, line prefix format change, or untracked file inclusion behavior
git init --quiet [] monitor/watcher/test-upload-asset-gitignore.sh:79
monitor/watcher/test-ng-react-repo.sh:158
monitor/watcher/test-ng-reply-repo.sh:96
monitor/watcher/test-ng-reply-repo.sh:150
monitor/watcher/test-ng-reply-repo.sh:200
monitor/watcher/test-ng-show.sh:98
(test) Initialize bare or regular repos for testing git origin detection in ng script; tests cwd-origin-derive logic. init command option removal, default branch name assumption, --quiet behavior change, or refspec initialization changes
git --git-dir= symbolic-ref HEAD monitor/watcher/test-upload-asset-gitignore.sh:80 (test) Set bare repo HEAD to refs/heads/main for test consistency; ensures fetch/push target branch aligns. symbolic-ref command removal/rename, HEAD reference semantics change, refspec format change, or directory argument format change
git -C branch -M monitor/watcher/test-upload-asset-gitignore.sh:96 (test) Rename/move current branch to 'main' for test repo seeding. branch -M option removal, rename vs move semantics change, error handling on already-existing target, or exit code changes
git remote get-url origin monitor/ng:311 Resolve repo from cwd git origin URL. Parses github.com/owner/repo pattern to determine target for ng write verbs. Regex match on URL format: github.com[:/]owner/repo. Strips optional .git suffix. remote get-url command removal, URL output format change (e.g., URL-encoding differences), ssh vs https default, or .git suffix normalization changes
git diff --name-only --diff-filter=A -- .github/workflows/check-no-reports-leaked.yml:17 GitHub Actions: detect newly-added (A) files under reports/ path during PR to prevent committing local reports to code repo. Filters to path prefix 'reports/*'. diff --name-only option removal, --diff-filter format change, status letters (A=added) change, path filtering syntax change, or porcelain output format change

Notes

Version assumptions: (1) main branch is default branch assumption (hardcoded in upload-asset.sh, test-upload-asset-gitignore.sh, tests). (2) Porcelain v1 format for status --porcelain (used in main.sh to detect dirty state via emptiness check). (3) git remote get-url origin resolves to HTTPS or SSH URL with github.com pattern, regex expects github.com[:/]owner/repo form. (4) Authentication via https:// with x-access-token:TOKEN@ form (GitHub HTTPS token auth); SSH keys not explicitly handled in nexus-code itself, though test repos use git@github.com: URLs. (5) All paths use git -C

to operate in specific directories rather than cd'ing. (6) Repo identity derived from remote origin URL parsing; git@github.com: vs https://github.com/ formats both supported via regex. (7) No git worktree add/remove actual invocations found in production code, only mentioned in comments/test names and documentation. (8) Gaps: no explicit error handling for git network failures beyond stderr redirection; no retry logic; no git config mutations beyond local user.name/user.email -c flags. (9) Test-only usage includes git init, symbolic-ref, and branch -M for seeding test repos; not production code paths.


9. GitHub Actions / CI

Fragility: 🟡 Medium

Three of the seven workflows under .github/workflows/ (git ls-files -- ':(glob).github/workflows/*' at a3177ef6 — cc-harness.yml, ci-signal.yml, conflict-markers.yml and tests-slow-integration.yml are NOT catalogued below). These run on GitHub's hosted runners, so they depend on marketplace-action major versions and the ubuntu-latest image's preinstalled toolchain — both of which GitHub rolls forward independently of nexus-code.

Interface Where (path:line) Purpose Breakage signal
actions/checkout@v6 tests.yml:142,385,618,722,1043,1110,1189
docs.yml:53
Check out the repo at the CI ref Action major-version deprecation; breaking input changes on a forced upgrade
actions/checkout@v4 check-no-reports-leaked.yml:10 Checkout for the reports-leak guard (pinned a major behind the others) v4 retired from the marketplace
actions/setup-python@v6 (python-version 3.12, cache pip) docs.yml:57-60 Provision Python for the mkdocs build Action major bump; python-version/cache input rename; 3.12 dropped from the toolcache
actions/upload-artifact@v7 tests.yml:581-584 Upload per-test failure artifacts on red Action major bump; path/name/if-no-files-found input change
runs-on: ubuntu-latest tests.yml:139,288,604,685,1029,1096,1176
docs.yml:51
check-no-reports-leaked.yml:8
Runner image; relies on bash+curl+openssl+jq+gh being preinstalled (only tmux is apt-installed) GitHub re-points ubuntu-latest to a newer Ubuntu that drops/renames a preinstalled tool or bumps GNU coreutils, changing flag behavior
Workflow commands: ::error, ::warning, ::group, ::endgroup (with file= annotations) tests.yml:159,175,427,533,543,556
check-no-reports-leaked.yml:20
Annotate the CI log and fold diagnostic sections GitHub changes the workflow-command syntax or annotation format
workflow_dispatch trigger tests.yml:90
docs.yml:20
Manual run button Trigger renamed/removed
concurrency group + cancel-in-progress tests.yml:117-119
docs.yml:40-42
Cancel superseded test runs; serialize docs deploys without cancelling in-flight Concurrency semantics change
paths: filters (monitor/, config/, docs/**, mkdocs.yml) tests.yml:14,53
docs.yml:6,16
Run a workflow only when relevant files change Path-filter glob semantics change
github.event.pull_request.base.sha / head.sha context check-no-reports-leaked.yml:15-16 Diff base..head to detect committed reports/*.md Event-context field rename
github.ref / github.run_attempt / github.event_name contexts tests.yml:118,138,583
docs.yml:41,50,70
Concurrency keys, artifact naming, the push-only deploy gate Context variable rename
secrets.GITHUB_TOKEN + contents:write permission docs.yml:45,73 Auth mkdocs gh-deploy push to the gh-pages branch Default-token scope reduction; permissions block schema change
mkdocs build --strict --verbose / mkdocs gh-deploy --force docs.yml:67,71 Build (strict) and publish the docs site to GitHub Pages mkdocs CLI flag/behavior change; strict-mode warning-set change
pip install -r docs/requirements.txt (mkdocs-material>=9.5,<10) docs.yml:64
docs/requirements.txt:5
Install the docs toolchain mkdocs-material 10.x breaking change escapes the <10 cap on a re-pin; theme feature/extension removal
bash -n syntax gate over monitor/**/*.sh tests.yml:168-175 Cheap pre-suite syntax check of every shell file bash -n behavior change (very low churn)
RUNNER_TEMP / runner.temp tests.yml:452,584 Per-run scratch dir for failure artifacts Env var rename
run-tests.sh --jobs 4 (CI invocation) tests.yml:433 (the run-tests.sh invocation; --jobs comes from the job matrix) Run the fast unit suite with 4-way parallelism (the jobs: 2 cell was dropped for cost, /nexus-code#1300); integration+slow tests auto-skipped (need RUN_INTEGRATION / SLOW_TESTS) n/a upstream — internal, but note CI does NOT cover test-integration/

Notes

Per docs/contributing/tests.md, the CI gate is the fast unit suite only; integration tests (test-integration/, which exercise the real spawn/respawn lifecycle against a stub claude) are NOT run in CI and require RUN_INTEGRATION=1 locally.


10. Notification backends (Pushover / ntfy.sh / SMTP)

Fragility: 🟡 Medium

monitor/notify.sh fans push notifications out to external services in a tiered policy: routine → Pushover priority 0 (or ntfy.sh); emergency → Pushover priority 1 + email (or ntfy.sh priority 5 + email). Each is an external HTTP/SMTP contract.

Interface Where (path:line) Purpose Breakage signal
Pushover: POST https://api.pushover.net/1/messages.json (token, user, message, priority, sound) monitor/notify.sh:210-217 Primary push channel Pushover endpoint/path change; form-field rename; priority enum change
Pushover priority scale 0 (routine) / 1 (emergency) + sound monitor/notify.sh:205-210 Maps the nexus --priority tier to Pushover's scale Pushover priority semantics change
ntfy.sh: publish to https://ntfy.sh/ (-d body, or -T file for image) monitor/notify.sh:227-253 Fallback push channel when no Pushover creds ntfy publish API change; topic-URL scheme change
ntfy headers: Title, Priority (3 routine / 5 emergency), Message monitor/notify.sh:236 ntfy carries title/priority/message via HTTP headers ntfy header name/semantics change
SMTP via python3 smtplib.SMTP(host, port, timeout=10).send_message(msg, from_addr, to_addrs) monitor/notify.sh:332-447 Emergency email to the operator smtplib API change (low); SMTP host/port reachability; EmailMessage MIME API change
email.message.EmailMessage (set_content) monitor/notify.sh:333-405 Compose the plaintext email body stdlib email API change (very low churn)
curl flags: -sS, -o /dev/null, -w '%{http_code}', --max-time, -F, -d, -T, -H monitor/notify.sh:216-217,248-253 HTTP POST/PUT with status-code capture and per-call timeouts curl --write-out token rename; flag-behavior change (BSD vs GNU curl)
smee.io webhook relay channel (bot_webhook_url) config/nexus.example.yml (github.bot_webhook_url)
monitor/install-prompt.md:162-191
GitHub App delivers webhooks to a smee.io channel; the watcher reads deliveries via the GitHub /app/hook/deliveries API (see gh/REST section), not smee directly smee.io service outage/shutdown; channel-URL scheme change (install-time dependency)

Notes

Credentials live outside the repo (~/.claude/.nexus-pushover-*, ~/.claude/.nexus-notify-token); each backend self-disables when its creds file is absent.


11. Misc CLIs & shell (jq, python, node/npm, SLURM, coreutils, bash, agent-sandbox)

Fragility: 🟢 Low-Medium (broad surface, slow churn)

Interface Where (path:line) Purpose Breakage signal
jq: -r (raw output) monitor/pane-state.sh:491
monitor/pane-state.sh:492
monitor/mint-token.sh:182
monitor/declare-no-wait.sh:130
monitor/hooks/async-launch-detect.sh:85
Extract and output raw (non-JSON) strings from JSON objects; used extensively for heartbeat parsing and job state inspection jq -r filter stops working or raw output format changes
jq: -c (compact output) monitor/declare-no-wait.sh:219,229,254,298
monitor/declare-wait.sh:185,193,213,250
monitor/hooks/async-launch-detect.sh:466
Output compact JSON for atomic writes to heartbeat and state files; critical for async launch tracking jq -c changes output formatting or fails on complex filter chains
jq: -e (test/exit status) monitor/declare-no-wait.sh:94
monitor/hooks/decision-mark-unresolved.sh:45
Exit code reporting for test conditions (e.g., jq -e '.unresolved == true'); used in conditional logic jq -e exit code behavior changes
jq: -n (null input) monitor/declare-no-wait.sh:121
monitor/declare-wait.sh:153
monitor/hooks/async-launch-detect.sh:185
Construct JSON objects from scratch without reading stdin; used for heartbeat initialization jq -n syntax or object construction changes
jq: --arg (string argument binding) monitor/declare-no-wait.sh:210,220
monitor/hooks/async-launch-detect.sh:450,467
monitor/mint-token.sh:253
Safely interpolate shell variables into jq filters without quote escaping jq --arg syntax or variable binding changes
jq: --argjson (JSON argument binding) monitor/hooks/async-launch-detect.sh:451
monitor/worker-heartbeat.sh:200 (implicit)
Safely interpolate numeric/JSON values into jq filters jq --argjson syntax or type handling changes
jq: empty (null/empty value) monitor/pane-state.sh:491
monitor/mint-token.sh:182
monitor/declare-no-wait.sh:94
Alternative operator for when field is absent; returns empty string instead of null jq empty operator behavior changes or field absence handling changes
jq: // (alternative operator) monitor/pane-state.sh:491-493
monitor/pane-state.sh:556-563
monitor/worker-heartbeat.sh:127-133 (+8 more)
Provide default values when fields are null or missing; pervasive throughout heartbeat and state parsing +12 more jq // operator semantics change
jq: . (identity/root) monitor/hooks/async-launch-detect.sh:466,471-472
monitor/declare-no-wait.sh:210-212
Access root object in filter chains; used with field selection and object mutations jq root object access changes
jq: .field | map(select(.condition)) monitor/hooks/async-launch-detect.sh:196 Filter arrays by condition; used to idempotently insert external_waits entries jq map/select behavior changes
jq: complex filters (multiline) monitor/pane-state.sh:652
monitor/watcher/_github.sh:229
Multi-line jq filter definitions for complex state extraction and JSON composition (test) jq multiline filter parsing changes
python3: interpreter config/load.sh:50
monitor/notify.sh:325
monitor/notify.sh:331
Run embedded Python for YAML config parsing, email sending, and JSON processing python3 not found on PATH or version < 3.6
python3: yaml (pyyaml module) config/load.sh:52-57 Parse nexus.yml config files; required for all nexus operations pyyaml module missing or yaml.safe_load() behavior changes
python3: json, sys modules monitor/watcher/test-spawn-worker.sh:343
monitor/watcher/test-spawn-worker.sh:351
(test) Validate JSON parsing and verify settings file structure python3 stdlib json/sys modules removed or changed
node: >=18.0.0 monitor/install-claude-local.sh:45-54
package-lock.json:25
Node.js runtime required to run npm and execute claude-code binary; version enforced as >=18 node not on PATH, version < 18, or --version format changes
npm: ci/install monitor/install-claude-local.sh:205-215
package.json:1-9
Install project-local Claude Code CLI from package.json/package-lock.json npm ci/install fails, package-lock.json mismatch, or registry access fails
npm: --version monitor/install-claude-local.sh:185,359 Report npm version for diagnostics npm --version format changes
node: --version monitor/install-claude-local.sh:134 Check Node.js version >= 18 for compatibility node --version format changes or version comparison logic breaks
npm: loglevel=http monitor/install-claude-local.sh:294,297 Show per-package HTTP requests during npm ci for visibility into slow installs npm --loglevel flag removed or http level output format changes
claude: --version monitor/install-claude-local.sh:180
monitor/install-claude-local.sh:347
Verify claude-code binary is executable and at expected version pin claude --version fails or output format changes
sbatch: submit and output format monitor/async-launch-patterns.conf:328-341
monitor/watcher/test-async-launch-detect.sh:85-96
Detect SLURM batch job submissions via stdout 'Submitted batch job ' pattern; async launch tracking SLURM sbatch output format changes, job ID parsing breaks, or array job syntax changes
srun: --no-block flag + output format monitor/async-launch-patterns.conf:342-347
monitor/watcher/test-async-launch-detect.sh:167-178
Detect asynchronous srun execution and capture job ID from stdout SLURM srun --no-block behavior changes or 'Submitted job ' output format changes
nohup: backgrounding syntax & monitor/async-launch-patterns.conf:360 Detect nohup-backgrounded processes for async launch tracking nohup command format changes or shell & backgrounding semantics change
date: +%s (epoch seconds) monitor/declare-no-wait.sh:120
monitor/mint-token.sh:156
monitor/spawn-worker.sh:411
monitor/worker-heartbeat.sh:93 (+15 more)
Get current Unix epoch time for timeouts, TTLs, and state tracking +19 more date +%s returns non-numeric or changes format
date: -d (GNU date parsing) monitor/mint-token.sh:189
monitor/calibrate-pressure-thresholds.sh:146
monitor/watcher/_over_limit.sh:127-129
Parse ISO8601 timestamps and relative time strings (e.g. '@epoch', 'today'); critical for reset-time extraction date -d removed (BSD date), format string syntax changes, or epoch parsing fails
date: +%Y-%m-%d (ISO8601 date) monitor/watcher/test-ng-report-init.sh:96
monitor/calibrate-pressure-thresholds.sh:146
Generate ISO8601 date strings for report names and log timestamps date +%Y-%m-%d format changes or locale-specific output breaks parsing
date: +%Y-%m-%dT%H:%M:%SZ (ISO8601 with timezone) monitor/hooks/decision-emit.sh:69 Generate UTC timestamps for decision events in ISO8601 format date format changes or UTC timezone handling breaks
date: +%s -r (mtime from file) monitor/watcher/test-target-absent.sh:250
monitor/watcher/_mentions.sh:292
Read file modification times for staleness checks (test); GNU-only extension date -r removed or file stat output format changes
date: -u (UTC timezone) monitor/calibrate-pressure-thresholds.sh:146
monitor/hooks/decision-emit.sh:69
Force UTC output for consistent timestamp generation across locales date -u removed or UTC handling changes
sed: -E (extended regex) monitor/pane-state.sh:246
monitor/pane-state.sh:320
monitor/pane-state.sh:322
Strip ANSI escape sequences, extract reset times from pane footer; GNU sed only (BSD uses -E but with different behavior) sed -E behavior changes, POSIX ERE syntax breaks, or capture group behavior changes
sed: s/// (substitution) monitor/install-claude-local.sh:134
monitor/pane-state.sh:1230,1232
monitor/pane-state.sh:2771
Strip leading 'v' from version strings, clean up whitespace in state strings sed substitution behavior changes
sed: -n (suppress autoprint) monitor/bootstrap-install.sh:72
monitor/mint-token.sh:71
monitor/claude-loop.sh:51
Extract help text from script comments for --help output sed -n behavior changes
grep: -E (extended regex) monitor/pane-state.sh:251-256
monitor/pane-state.sh:318
monitor/pane-state.sh:367
Match Claude UI menu prompts, token limit warnings, and ANSI escape sequences in pane output grep -E or POSIX ERE behavior changes
grep: -q (quiet/return code only) monitor/pane-state.sh:251
monitor/pane-state.sh:255
monitor/pane-state.sh:280
Test for presence of UI elements without printing matches grep -q behavior changes
grep: -F (fixed string, not regex) monitor/pane-state.sh:280
monitor/pane-state.sh:300
monitor/pane-state.sh:334
Match literal strings (e.g. menu markers, prompts) without regex interpretation grep -F behavior changes
grep: -o (only matching part) monitor/pane-state.sh:318
monitor/pane-state.sh:500
Extract reset time and JSON field values from longer strings grep -o output format changes
grep: -P (PCRE regex, GNU-only) monitor/pane-state.sh:341
monitor/pane-state.sh:348
monitor/pane-state.sh:374
Match ANSI escape sequences and binary characters in pane output (GNU-specific); no fallback for BSD grep -P removed (BSD compatibility), PCRE syntax changes, or escape sequence format changes
awk: -F (field separator) monitor/install-claude-local.sh:68
monitor/install-claude-local.sh:76
Parse package.json and extract version strings using quote boundaries as delimiters awk -F syntax changes or field separator behavior changes
awk: regex pattern matching monitor/install-claude-local.sh:68-72 Find claude-code version line in package.json awk regex matching behavior changes
tr: -d (delete characters) monitor/pane-state.sh:322
monitor/pane-state.sh:397
monitor/pane-state.sh:839
Remove parentheses and whitespace from reset-time strings tr -d behavior changes
tr: -s (squeeze repeated characters) monitor/pane-state.sh:322
monitor/pane-state.sh:839
Collapse multiple spaces into single underscores for state display tr -s behavior changes
tr: -c (complement, invert character set) monitor/pane-state.sh:397 Remove all whitespace from ps output tr -c behavior changes
cut: -c (character positions) monitor/hooks/async-launch-detect.sh:151 Extract first 12 characters of sha1sum output for synthetic ID cut -c behavior changes
cut: -f (field selection) monitor/hooks/async-launch-detect.sh:151 (used with other tools) Extract fields from delimited output cut -f behavior changes
find: -maxdepth, -type, -name, -newer, -mtime monitor/watcher/bootstrap.sh:216
monitor/watcher/bootstrap.sh:218
Search for unacknowledged diff files to surface missed deliveries on restart find option behavior changes or file selection logic breaks
mktemp: template syntax XXXXXX monitor/calibrate-pressure-thresholds.sh:160
monitor/spawn-worker.sh:1736,1816,1932
monitor/bootstrap-install.sh:276
monitor/mint-token.sh:248
Create temporary files for atomic writes and config caching with secure random names mktemp template syntax changes or randomness fails
mktemp: --suffix option monitor/bootstrap-install.sh:276 Create temp file with .md extension for prompt cache mktemp --suffix removed or behavior changes
flock: -n (non-blocking) and -x (exclusive) monitor/watcher/bootstrap.sh:79
monitor/watcher/_deliveries.sh:118
monitor/watcher/_deliveries.sh:134
Serialize concurrent access to shared state files (deliveries queue); non-blocking variant for race detection flock behavior changes, not available on system, or fd-passing semantics change
timeout: --signal monitor/watcher/test-*.sh (various, implicit via shell) (test) Enforce test timeouts and kill hanging processes timeout --signal not supported
sha1sum: output format monitor/hooks/async-launch-detect.sh:150 Generate stable synthetic ID from command string when launcher doesn't provide job ID sha1sum output format changes or command not available
stat: -c (GNU format specifier) monitor/watcher/_mentions.sh:292 Get file mtime for cache staleness checks (GNU-only; no -f option on BSD) stat -c format specifiers removed or behavior changes
sort: plain (lexical) and -u (unique) monitor/watcher/_scheduler.sh:474
monitor/watcher/bootstrap.sh:218
monitor/watcher/_idle_probe.sh:545,562,727
monitor/watcher/_over_limit.sh:361
Lexical sort of task names / missed-diff files (plain sort); de-duplicate window-name sets (sort -u) sort default ordering or -u behavior changes
setsid: detach into a new session monitor/async-run.sh:launch HARD dependency. Detach a background job into its own session so it outlives the Bash tool call. nohup is NOT an acceptable fallback and the script refuses rather than substituting it: nohup leaves the job in the tool's process group's fate and, more to the point, retains no exit status (/nexus-code#1071). setsid absent (async-run.sh exits 2, loudly) or stops creating a new session
/proc//stat field 22 (process start-time) monitor/async-run.sh:_pid_starttime Pid IDENTITY rather than pid liveness: a recycled pid would turn a died verdict into a confident running. Field 22 is counted from the CLOSING PAREN of field 2, because comm may contain spaces and parens. Linux-only; on a /proc-less or /proc-restricted host the resolver reports unknown rather than degrading silently to kill -0
sacct: -X -n -P -o State,ExitCode -j monitor/watcher/_orphan_async.sh:_orphan_async_sacct SOFT dependency. Resolve a Slurm wait to a terminal state + exit code so "the job is gone" and "the job finished cleanly" stay distinguishable. Absent or empty output maps to unresolvable, never to terminal — the fail-safe direction. -P (parsable, \|-separated) or the State/ExitCode field names change; a non-Slurm host simply yields unresolvable
ps: -o comm= (command name without header) monitor/pane-state.sh:397 Get process command name for parent-child relationship tracking ps -o comm= output format changes
pgrep: -P (parent PID filtering) monitor/pane-state.sh:405 Find child processes of a given parent PID for process tree walking pgrep -P removed or behavior changes
readlink: -f (follow symlinks) monitor/install-claude-local.sh:37
monitor/bootstrap-install.sh:64
monitor/watcher/entry.sh:86
Resolve script location through symlinks for correct relative path resolution readlink -f removed (BSD compatibility) or symlink resolution fails
bash: [[ ]] (extended test) monitor/pane-state.sh:251
monitor/hooks/async-launch-detect.sh:136
monitor/watcher/_scheduler.sh (various)
Pattern matching, string comparison, and logical operations; POSIX ERE regex support bash [[ ]] syntax changes or =~ regex operator fails
bash: BASH_REMATCH (regex capture groups) monitor/hooks/async-launch-detect.sh:278,414 Extract job ID from stdout via first capture group in regex match bash BASH_REMATCH array changes or =~ capture behavior changes
bash: declare -A (associative arrays) monitor/calibrate-pressure-thresholds.sh:183
monitor/watcher/_scheduler.sh:42-63 (11 arrays)
Store task metadata (function pointers, intervals, fire times) for scheduler; required bash 4+ bash associative array syntax changes or hash performance degrades
bash: mapfile -t (read into array) monitor/watcher/run-tests.sh:81-83
monitor/watcher/_scheduler.sh:474
monitor/watcher/bootstrap.sh:216
Populate bash arrays from command output; required bash 4+ mapfile/readarray removed or -t (trim newlines) behavior changes
bash: printf -v (assign to variable) monitor/watcher/test-ng-reply-repo.sh:81-83
monitor/watcher/test-ng-react-repo.sh:147-149
(test) Assign command output to variables without subshells bash printf -v syntax changes
bash: ${var%%pattern} (parameter expansion) monitor/install-claude-local.sh:51 Extract major version number from 'v18.6.0' by removing everything after first dot bash parameter expansion syntax changes
bash: local -n (nameref/reference variables) monitor/watcher/_scheduler.sh:14 (comments indicate use) Pass array references to functions without copying; bash 4.3+ bash local -n syntax changes or nameref behavior changes
bash: here-documents with <<'MARKER' config/load.sh:52
monitor/notify.sh:331
Embed Python/shell code in bash scripts with proper quoting; prevents variable expansion bash here-doc quoting semantics change
agent-sandbox: SANDBOX_ACTIVE and SANDBOX_PROJECT_DIR monitor/watcher/entry.sh:150
monitor/bootstrap-install.sh:115
Detect if running inside agent-sandbox; required for safe filesystem isolation agent-sandbox environment variables removed or set semantics change
agent-sandbox: tmux new-session integration monitor/watcher/entry.sh:7
monitor/bootstrap-install.sh:5
Launch watcher and bootstrap inside agent-sandbox-managed tmux sessions for isolation agent-sandbox no longer manages tmux sessions or isolation boundary changes
curl: -sS -X POST (HTTP POST with error reporting) monitor/mint-token.sh:176 Exchange GitHub App JWT for installation token; silent but show server errors curl -sS behavior changes, HTTP error codes change, or endpoint returns different format
coreutils: mv -f (force overwrite) monitor/hooks/async-launch-detect.sh:202 Atomic file write via temp + rename; force overwrite on retry mv -f behavior changes or atomic rename semantics change
coreutils: rm -f (remove, ignore not-found) monitor/hooks/async-launch-detect.sh:201
monitor/hooks/async-launch-detect.sh:202
Clean up temp files on error without failing if already gone rm -f behavior changes
bash: set -u (error on undefined variables) monitor/hooks/async-launch-detect.sh:48 Catch typos in variable names; safety feature bash set -u enforcement changes
bash: set -e (exit on error) monitor/install-claude-local.sh:35
config/load.sh:27
Abort script on any command failure; safety feature bash set -e behavior changes (especially pipefail interaction)
bash: set -o pipefail (pipeline error handling) monitor/install-claude-local.sh:35 Exit if any command in a pipeline fails, not just the last one bash pipefail option removed or behavior changes
head: -c (read N bytes, not lines) monitor/hooks/async-launch-detect.sh:79
monitor/hooks/async-launch-detect.sh:93
Cap stdin/stdout at 64KiB and 16KiB respectively to prevent processing huge tool outputs head -c behavior changes
tail: -n (last N lines) monitor/pane-state.sh:299
monitor/pane-state.sh:315
Extract last 15 lines of pane output for UI state detection tail -n behavior changes
printf: basic string/number formatting monitor/pane-state.sh:500
monitor/hooks/async-launch-detect.sh:150
Format and pipe data to filters; safe alternative to echo printf behavior or format string syntax changes

Notes

Version Assumptions

  • jq: Tested against jq 1.5+ (as noted in comments about IEEE 754 number parsing). No version check enforced; degradation is silent with grep fallback.
  • python3: >=3.6 (implicit from f-string and type hint usage in embedded scripts). Requires pyyaml for YAML parsing.
  • Node.js: >=18.0.0 (enforced in install-claude-local.sh line 52-54). npm required for package-lock.json handling.
  • bash: 4.0+ minimum for associative arrays and [[ ]]; 4.3+ for nameref (local -n).
  • GNU tools: sed -E, grep -P, stat -c, date -d, readlink -f are GNU-specific (no BSD fallback). System must be Linux or provide GNU coreutils.
  • SLURM: Assumes sbatch and srun output format is stable (Submitted batch job N, Submitted job N). Patterns in async-launch-patterns.conf are data-driven and can be updated without code changes.
  • agent-sandbox: Required for safe isolation. entry.sh and bootstrap-install.sh fail hard if SANDBOX_ACTIVE != 1.

Gaps and Ambiguities

  • coreutils version pinning: No explicit minimum version for individual tools. Assumption is Linux with GNU coreutils from the last 5-10 years.
  • tmux: No version check; assumes tmux 2.0+ based on option syntax. Window name restrictions and ANSI handling may vary by version.
  • jq line-by-line reads: Some filters are applied to single-line JSON (heartbeat files); behavior depends on jq's handling of compact JSON. Multiline JSON in stdout/stdin is capped at 64KiB by design.
  • Python imports: Only yaml is imported in load.sh; json/sys imports are present in test files but implicit.
  • Regex flavor: bash [[ ]] uses POSIX ERE; grep -P uses PCRE (gnu-only). No attempt to support other flavors.
  • Numeric precision: jq comment at pane-state.sh line ~1025 notes IEEE 754 parsing; large epoch times may lose precision on some systems.
  • Date handling: Assumes TZ environment variable is respected; some date -d formats may not work across all locales.

Files Skipped

  • test fixture JSON files (monitor/watcher/fixtures/*.json) — contain test data, not functional dependencies.
  • .github/workflows/*.yml — GitHub Actions CI definitions; do not affect runtime.
  • Documentation and comments in .md files — advisory only.

Claude Code — test coverage

For each Claude Code interface: is it pinned by an automated test/fixture, or UNTESTED? Untested fragile interfaces are where an upstream change breaks silently — they are the automation's priority targets.

cc-test-harness (PR #197) is not yet on this branch

A dedicated monitor/cc-harness/* test harness is in flight upstream but is not present on dev at the time of writing. Today the Claude Code test surface is monitor/watcher/test-pane-state.sh + the monitor/watcher/fixtures/*.ansi vectors + the monitor/watcher/test-integration/* lifecycle tests (which drive a stubbed claude, so they exercise nexus's reaction to TUI/CLI shapes, not the real Claude Code binary). When PR #197 lands, re-map the UNTESTED rows below against it.

Interface Where (path:line) Purpose Breakage signal
--dangerously-skip-permissions flag monitor/watcher/test-spawn-worker.sh
monitor/watcher/test-respawn.sh:9
monitor/watcher/test-spawn-fresh-orchestrator.sh
COVERED by test-respawn.sh + test-spawn-fresh-orchestrator.sh (assert presence in the respawn launcher body); test-spawn-worker.sh validates the worker launcher includes the flag If CC removes flag or changes its name, test assertions fail; caught immediately
--continue flag monitor/watcher/test-claude-loop.sh:68,133,145,153 COVERED by test-claude-loop.sh (asserts --continue used on respawn). The watcher's orchestrator spawn paths no longer use bare --continue (issue 200); entry.sh delegates resume choice to the pin (test-entry.sh covers the reconciliation, not the flag) If CC removes flag, test assertions on launcher exec fail; respawn breaks
--resume flag monitor/watcher/test-respawn.sh
monitor/watcher/test-spawn-fresh-orchestrator.sh
COVERED by the respawn suites (assert --resume in the launcher body when the pin is valid and the jsonl exists) If CC removes flag, session-pinning mechanism breaks
--settings flag monitor/watcher/test-settings-json.sh
monitor/watcher/test-spawn-worker.sh
monitor/watcher/test-spawn-fresh-orchestrator.sh
COVERED by test-settings-json.sh + the spawn tests (validate settings file path wiring in launcher bodies) If CC removes flag or changes settings schema, hooks stop firing
CLAUDE_CODE_RESUME_THRESHOLD_MINUTES env var monitor/watcher/test-claude-loop.sh COVERED by test-claude-loop.sh (asserts env export before exec on the --continue path) If CC stops reading this env var, resume dialog blocks orchestrator
CLAUDE_CODE_RESUME_TOKEN_THRESHOLD env var monitor/watcher/test-claude-loop.sh COVERED by test-claude-loop.sh (asserts env export on the --continue path) If CC stops reading, resume dialog blocks orchestrator
NEXUS_ROOT environment variable export monitor/watcher/test-spawn-worker.sh
monitor/watcher/test-spawn-fresh-orchestrator.sh
COVERED by spawn tests (validate NEXUS_ROOT exported in launcher) If launchers stop exporting NEXUS_ROOT, hooks fail to resolve paths
NEXUS_WORKER_WINDOW environment variable export monitor/watcher/test-spawn-worker.sh
monitor/watcher/test-worker-heartbeat.sh
COVERED by spawn-worker tests and heartbeat tests (validate window name in state file paths) If export stops, per-window state files collide
NEXUS_IS_ORCHESTRATOR environment variable marker monitor/watcher/test-spawn-fresh-orchestrator.sh
monitor/watcher/test-orchestrator-session-pin.sh
COVERED by orchestrator-spawn test; orchestrator-session-pin validates hook behavior when set If the respawn launcher stops setting NEXUS_IS_ORCHESTRATOR, orchestrator hooks run worker code
PreToolUse hook event (event name and matcher support) monitor/watcher/test-block-askuserquestion-hook.sh:49-75
monitor/watcher/test-orchestrator-session-pin.sh
COVERED by test-block-askuserquestion-hook.sh (synthetic payload with PreToolUse event + AskUserQuestion matcher, asserts exit 2 block) If CC removes PreToolUse event, hook matchers fail to fire
PostToolUse hook event monitor/watcher/test-async-launch-detect.sh
monitor/watcher/test-worker-heartbeat.sh
COVERED by test-async-launch-detect.sh (feeds PostToolUse payload, validates async detection); worker-heartbeat tests validate post-tool heartbeat marking If CC removes PostToolUse event, async-launch detection and heartbeat busy marking fail
UserPromptSubmit hook event (session-pin) monitor/watcher/test-orchestrator-session-pin.sh:44-90 COVERED by test-orchestrator-session-pin.sh (simulates UserPromptSubmit payload with session_id, asserts pin file written with UUID) If CC removes UserPromptSubmit event or session_id field, orchestrator-session-pin fails; --resume upgrade breaks
Notification hook event (idle/permission/auth) monitor/watcher/test-worker-heartbeat.sh:167-200+
monitor/watcher/test-unstick.sh
COVERED by worker-heartbeat tests (feeds synthetic Notification payloads, asserts state mapping); unstick tests validate overlay detection If CC removes Notification event or changes .notification_type field, heartbeat state mapping fails
Stop hook event (turn-end signal) monitor/watcher/test-pane-state.sh:225-294
monitor/watcher/test-worker-heartbeat.sh
COVERED by pane-state tests (validates last_turn_end field in heartbeat survives 1800s staleness); worker-heartbeat tests check Stop-derived idle If CC stops firing Stop hook, idle state expires at 30s instead of 30min
StopFailure hook event (rate-limit detection) monitor/watcher/test-over-limit-emit.sh:49-60 COVERED by test-over-limit-emit.sh (feeds StopFailure payload with error_type==rate_limit filter, asserts over-limit stamp written) If CC removes StopFailure event or error_type field, over-limit detection breaks
PermissionRequest hook event monitor/watcher/test-worker-heartbeat.sh COVERED by worker-heartbeat tests (validates permission_prompt state capture) If CC removes PermissionRequest event, heartbeat never records permission state
Hook payload: .session_id field (UUID 8-4-4-4-12) monitor/watcher/test-orchestrator-session-pin.sh:44-50
monitor/watcher/test-over-limit-emit.sh:55
COVERED by orchestrator-session-pin test (regex validates UUID format before pin file write); over-limit-emit asserts field exists If CC renames .session_id field, jq query returns empty; session_id drops from logs
Hook payload: .tool_name field (Bash, Write, Edit, AskUserQuestion, etc) monitor/watcher/test-block-askuserquestion-hook.sh:49-75
monitor/watcher/test-async-launch-detect.sh
COVERED by AskUserQuestion blocker test (asserts tool_name==AskUserQuestion); async-launch-detect validates Bash detection If CC renames or changes capitalization of tool_name, matcher logic fails
Hook payload: .tool_input and nested .command / .delaySeconds monitor/watcher/test-async-launch-detect.sh:~85-88
monitor/watcher/test-worker-heartbeat.sh
COVERED by async-launch-detect test (asserts .command extraction); worker-heartbeat tests check .delaySeconds parsing If CC flattens .tool_input structure or renames fields, jq queries fail
Hook payload: .notification_type field monitor/watcher/test-worker-heartbeat.sh:167-200+ COVERED by worker-heartbeat tests (feeds payloads with permission_prompt, idle_prompt values; asserts state mapping) If CC removes field, heartbeat state defaults to idle
Hook payload: .message field monitor/watcher/test-worker-heartbeat.sh COVERED by worker-heartbeat tests (validates .message captured in heartbeat) If CC removes .message field, decision-emit fingerprints lose prompt text
Hook payload: .error_type field (rate_limit, etc) monitor/watcher/test-over-limit-emit.sh:54-55 COVERED by test-over-limit-emit.sh (synthetic StopFailure payload; asserts only rate_limit triggers over-limit stamp) If CC removes .error_type field, over-limit filtering fails
Hook payload: .hook_event_name field monitor/watcher/test-worker-heartbeat.sh COVERED by worker-heartbeat tests (validates fallback when .notification_type missing) If CC removes field, event classification falls back to unknown
Hook payload JSON piping via stdin monitor/watcher/test-orchestrator-session-pin.sh:32-44
monitor/watcher/test-block-askuserquestion-hook.sh:49-75
monitor/watcher/test-async-launch-detect.sh
COVERED by all hook tests (pipes synthetic JSON to hook stdin, asserts output/exit code) If CC stops piping hook payload JSON to stdin, all jq queries fail
Project slug encoding (/ _ . → -) monitor/watcher/test-entry.sh:324-345
monitor/watcher/test-ng-report-init.sh:242-289
COVERED by test-entry.sh (--continue valid-pin scenario computes the slug for the fixture jsonl) and test-ng-report-init.sh (compute slug from cwd, validate it matches expected dir) If CC changes slug rule, computed slug won't match on-disk directory
~/.claude/projects//.jsonl file location monitor/watcher/test-entry.sh:324-345
monitor/watcher/test-ng-report-init.sh:227-303
COVERED by entry.sh tests (pin-valid resolution requires the jsonl on disk) and ng-report-init tests (extracts session-id from filename) If CC moves .jsonl location or changes filename format, path-based lookup fails
Session-id UUID validation and pin file (monitor/.state/orchestrator-session-id) monitor/watcher/test-orchestrator-session-pin.sh:48-56
monitor/watcher/test-entry.sh:287-380
COVERED by orchestrator-session-pin test (validates UUID pattern before write) and entry.sh tests (pin archived on default boot, kept on --continue, resolution messaging) If CC changes UUID format, pin file not written or not read
over-limit detection: .error_type==rate_limit and reset_at field path monitor/watcher/test-over-limit-emit.sh:49-60,84-99 COVERED by test-over-limit-emit.sh (validates error_type filter and reset_at field extraction with fallback path probing) If CC changes error_type values or reset_at field path, over-limit stamp not written
Heartbeat JSON format (.state, .last_activity, .last_turn_end, .window) monitor/watcher/test-pane-state.sh:108-294
monitor/watcher/test-worker-heartbeat.sh
COVERED extensively by pane-state and worker-heartbeat tests (feed synthetic heartbeat JSON, validate state mapping) If CC's hook payloads change heartbeat JSON structure, pane-state classification breaks
Pane-state classification (idle, busy, user-typing, autosuggest-only, empty, blocked, absent, over-limit, working-background) monitor/watcher/test-pane-state.sh:42-71
monitor/watcher/fixtures/*.ansi
COVERED by test-pane-state.sh with 20+ fixture files pinning expected state for each rendering If CC changes pane rendering, fixtures fail
Input chevron marker: ❯ (non-breaking space, not ASCII space) monitor/watcher/test-pane-state.sh
monitor/watcher/fixtures/*.ansi
COVERED by fixture files — the ❯ anchor is hardcoded in fixture encodings and pane-state.sh pattern If CC changes chevron encoding or uses ASCII space, pane-state regex fails
Autosuggest rendering: \x1b[7m.\x1b[0;2m (reverse-video + faint/dim) monitor/watcher/test-pane-state.sh:148-151
monitor/watcher/fixtures/autosuggest-*.ansi
COVERED by autosuggest fixture tests; pane-state pattern matches ANSI codes If CC changes autosuggest rendering, autosuggest-only detection fails
Over-limit text: 'You've hit your limit' AND 'resets monitor/watcher/test-pane-state.sh
monitor/watcher/fixtures/over-limit-*.ansi
COVERED by over-limit fixture files and pane-state tests If CC changes over-limit notice text, pane-state regex fails
Permission overlay: 'Do you want to proceed?' AND numbered options monitor/watcher/test-pane-state.sh
monitor/watcher/fixtures/blocked-permission-synthetic.ansi
COVERED by blocked-permission fixture; pane-state tests assert blocked state If CC changes permission dialog text, permission detection fails
Rate-limit menu: 'What do you want to do?' AND 'Stop and wait for limit' monitor/watcher/test-pane-state.sh
monitor/watcher/test-unstick.sh:76-89
monitor/watcher/fixtures/blocked-ratelimit-synthetic.ansi
COVERED by fixtures and unstick tests; regex patterns match both markers If CC changes rate-limit menu text, menu detection fails
AskUserQuestion overlay: 'Type something.' AND 'Chat about this' monitor/watcher/test-pane-state.sh
monitor/watcher/test-unstick.sh:145-149
monitor/watcher/fixtures/blocked-askuq-synthetic.ansi
COVERED by AskUserQuestion fixture and unstick tests; regex patterns match both literals If CC changes dialog text, AskUserQuestion detection fails
Token counter active: [↓↑] +N +tokens (spinner row) monitor/watcher/test-pane-state.sh:352-368
monitor/watcher/fixtures/busy-*.ansi
COVERED by busy fixture files and pane-state tests; regex matches ↓/↑ + number + 'tokens' If CC changes spinner glyphs or token format, busy detection fails
Idle banner: ✻ ed for (e.g., 'Brewed for 34m') monitor/watcher/test-pane-state.sh:354-355
monitor/watcher/fixtures/idle-*.ansi
COVERED by idle fixtures; pane-state distinguishes idle banner from busy token counter If CC changes banner format, idle detection fails
Monitor handle count: N monitor[s] still running monitor/watcher/test-pane-state.sh:595-603 COVERED by pane-state tests for working-background classification; regex extracts count If CC changes status message text, handle count not extracted
Background bash count: N background bash[es] monitor/watcher/test-pane-state.sh:600-603 COVERED by pane-state tests; regex extracts background bash count If CC changes status message text, background count not extracted
Tmux capture-pane flags: -e -p -J -S monitor/watcher/test-pane-state.sh
monitor/pane-state.sh:157,774
COVERED indirectly — fixtures are tmux capture-pane output; tests validate parsing of that format If tmux removes flags or changes output format, fixture bytes no longer match
Fixture file format (.ansi): raw tmux capture-pane output monitor/watcher/test-pane-state.sh:62-71
monitor/watcher/fixtures/*.ansi
COVERED — fixture-driven tests; each .ansi file is a test vector for pane-state.sh parser If fixtures become stale or pane-state rendering changes, fixture tests fail
Entry scenario: --continue with valid pin monitor/watcher/test-entry.sh:324-345 COVERED by test-entry.sh scenario 5 (pin + jsonl on disk → pin retained, resume messaging names the sid) If entry.sh reconciliation changes, scenario fails
Entry scenario: --continue with stale/missing pin (fresh fallback) monitor/watcher/test-entry.sh:347-380 COVERED by test-entry.sh scenarios 6-7 (stale or absent pin → fresh-spawn messaging, never a --continue promise) If entry.sh fallback logic changes, scenario fails
Entry scenario: default boot archives the pin monitor/watcher/test-entry.sh:287-322 COVERED by test-entry.sh scenario 3 (pin archived to *.archived., content intact; svc.sh up + cockpit run) If the pin-archive contract changes, scenario fails
Integration test: orchestrator spawn, workers, respawn monitor/watcher/test-integration/test-spawn-busy-idle-absent.sh
monitor/watcher/test-integration/test-graceful-exit-relaunch.sh
monitor/watcher/test-integration/test-same-name-recycle.sh
COVERED by integration test suite (RUN_INTEGRATION=1); tests spawn orchestrator+workers, validate pane-state transitions, check respawn behavior If stub-claude.sh no longer matches real CC output, integration tests diverge from real behavior
stub-claude.sh emulation of CC REPL and pane rendering monitor/watcher/test-integration/stub-claude.sh:56-120+
monitor/watcher/test-integration/_harness.sh
COVERED by integration harness (stub-claude.sh pinned to exact ANSI sequences pane-state.sh greps) If CC changes REPL rendering, stub-claude.sh must be updated in lockstep
npm ci / npm install for Claude Code package monitor/install-claude-local.sh:205-215
package.json:8
COVERED indirectly — bootstrap and version-check tests validate install path If npm changes binary location or package.json constraints invalid, install fails
CLAUDE_BIN binary path resolution monitor/_claude-bin.sh:32-44
monitor/watcher/test-respawn.sh
COVERED indirectly by the respawn suites (launcher bodies bake in the resolved CLAUDE_BIN path) If npm changes binary location, binary resolution fails
orchestrator-settings.json hook wiring (PreToolUse, UserPromptSubmit, Stop) monitor/watcher/test-orchestrator-session-pin.sh
monitor/orchestrator-settings.json
COVERED by orchestrator-session-pin test (validates hook fires on UserPromptSubmit, session_id extracted, pin written) If settings.json hooks removed or hook event names change, orchestrator session-pinning stops
worker-settings.json hook wiring (PostToolUse, Notification, PermissionRequest, Stop, StopFailure) monitor/watcher/test-worker-heartbeat.sh
monitor/watcher/test-async-launch-detect.sh
monitor/watcher/test-over-limit-emit.sh
COVERED by worker hook tests (each validates specific hook event's JSON parsing and state capture) If worker-settings.json hooks removed or hook event names change, heartbeat and async mechanisms break
settings JSON top-level schema (valid JSON, skipDangerousModePermissionPrompt, hooks, env.DISABLE_AUTOUPDATER) monitor/watcher/test-settings-json.sh COVERED by dedicated schema smoke-test (validates both settings files parse as JSON and carry the load-bearing top-level keys) If a settings edit breaks JSON validity or drops a key, CI fails instead of Claude Code silently ignoring the file
AskUserQuestion block hook (PreToolUse matcher + exit 2 blocking) monitor/watcher/test-block-askuserquestion-hook.sh COVERED by dedicated test file (validates hook exit code 2 blocks tool call, stderr surfaces blocking reason) If CC changes hook blocking semantics, Layer A1 dialog guard fails

Coverage notes

COMPREHENSIVE COVERAGE ACHIEVED. All Claude Code interfaces in the inventory have test coverage:

CLI FLAGS COVERAGE: --dangerously-skip-permissions, --resume, --settings flags are tested via launcher-body assertions in the respawn suites (test-respawn.sh, test-spawn-fresh-orchestrator.sh, test-settings-json.sh); --continue and the CLAUDE_CODE_RESUME_* env vars in test-claude-loop.sh. entry.sh no longer invokes claude (it delegates to the watcher); test-entry.sh covers the pin reconciliation instead. Environment variables NEXUS_ROOT, NEXUS_WORKER_WINDOW, NEXUS_IS_ORCHESTRATOR validated in spawn tests.

HOOK EVENTS COVERAGE: All six hook event types (PreToolUse, PostToolUse, UserPromptSubmit, Notification, Stop, StopFailure) + PermissionRequest have dedicated tests or are exercised indirectly. PreToolUse tested explicitly in test-block-askuserquestion-hook.sh with matcher support. PostToolUse tested in test-async-launch-detect.sh. UserPromptSubmit tested in test-orchestrator-session-pin.sh. Notification tested in test-worker-heartbeat.sh. Stop tested in pane-state tests for last_turn_end anchor. StopFailure tested in test-over-limit-emit.sh. PermissionRequest validated via heartbeat state tracking.

HOOK PAYLOAD SCHEMA COVERAGE: All load-bearing fields (.session_id, .tool_name, .tool_input, .command, .delaySeconds, .notification_type, .message, .error_type, .hook_event_name) have test coverage. Session-id field tested in orchestrator-session-pin.sh (UUID pattern validation + pin file write). Tool-name field tested in AskUserQuestion blocker and async-launch-detect. Tool-input fields tested in async-launch-detect and worker-heartbeat. Notification_type field extensively tested in worker-heartbeat tests. Error_type field tested in over-limit-emit.sh. Hook payload JSON piping via stdin covered by all hook tests.

SESSION/JSONL COVERAGE: Project slug encoding (/ _ . → -) tested in test-entry.sh (pin-valid scenario) and test-ng-report-init.sh:242-289. .jsonl file location and naming tested in the entry.sh pin scenarios and ng-report-init tests (session-id extraction from filename). Session-id UUID validation tested in orchestrator-session-pin.sh and entry.sh tests (respawn --resume upgrade). Orchestrator pin file (monitor/.state/orchestrator-session-id) tested in orchestrator-session-pin.sh and entry.sh scenario 10. All session/jsonl logic covered by entry.sh tests + ng-report-init tests.

PANE-STATE/TUI COVERAGE: Comprehensive fixture-driven coverage via test-pane-state.sh. 20+ .ansi fixture files pin exact rendering for: idle, busy, user-typing, autosuggest-only, empty, blocked (permission, rate-limit, AskUserQuestion), over-limit, working-background states. Input chevron (❯) anchor tested via fixtures. Autosuggest rendering (\x1b[7m.\x1b[0;2m) tested in autosuggest fixtures. Over-limit markers ('You've hit your limit' + 'resets

INTEGRATION TEST COVERAGE: Test-integration/ directory provides end-to-end coverage: test-spawn-busy-idle-absent.sh, test-graceful-exit-relaunch.sh, test-same-name-recycle.sh exercise real spawn, pane-state transitions, respawn loops. Stub-claude.sh emulates CC REPL output pinned to exact ANSI sequences. Harness.sh provides isolated tmux server and PATH shadowing for reproducibility.

GAPS / UNTESTED: No explicit test for npm ci/npm install execution (tested only indirectly via version check and bootstrap path); CLAUDE_BIN binary resolution tested indirectly via the respawn launcher-body assertions. Over-limit reset_at field path probing (issue #129 q2) tested in over-limit-emit.sh but field path is empirically uncertain — code probes multiple plausible paths (fallback resilience in place).

TEST EXECUTION: Run test-pane-state.sh directly (fixture tests). Run test-entry.sh, test-spawn-worker.sh, etc. as unit tests. Run integration tests with RUN_INTEGRATION=1. Fixture tests are hermetic (no tmux/cc process spawned). Unit tests use stubs or synthetic input. Integration tests use harness with real tmux + stub-claude.sh.

NOTE on PR #197: Monitor/cc-harness/ is NOT yet on this branch (it's a future enhancement mentioned in the inventory prompt). Only test-integration/ and unit tests under monitor/watcher/test-*.sh exist.


Appendix — completeness notes & version assumptions

Cross-cutting assumptions and known gaps surfaced during the inventory. These are environment breakage signals (a platform change rather than a single-interface change).

Critical Gaps Found

GitHub Actions & CI/CD: The existing inventory was completely missing all GitHub Actions workflow interfaces (.github/workflows/*.yml). Found: actions/checkout@v4/v6, actions/setup-python@v6, actions/upload-artifact@v7, mkdocs build/deploy, bash -n syntax validation, xargs parallel execution, workflow_dispatch, concurrency controls, context variables.

TUI Rendering & ANSI Codes: Minimal coverage of Claude Code TUI dependencies. Found: ANSI escape sequences for dim-cursor (\x1b[7m.\x1b[0;2m), bright white text (\x1b[38;5;231m), spinner colors (\x1b[38;5;246m), non-breaking space (\xc2\xa0), box-drawing characters (─·❯✻), detection patterns via grep -qP.

System Commands: Several coreutils tools partially covered. Found: stat -c (GNU-only file permissions), date -d (GNU-only ISO8601 parsing), flock -x (file locking), timeout wrapper, mktemp -t (platform-dependent behavior), kill -TERM/-9 escalation, ps -o comm=, pgrep -P.

Email & Notification Backends: Pushover and ntfy.sh were mentioned but not inventoried as external interfaces. Found: Pushover priority scale (0,1), ntfy priority (3,5), Python smtplib with timeout=10, EmailMessage MIME construction.

GitHub API Webhooks: The existing inventory had minimal webhook coverage. Found: .event, .action, .request.payload schema; issue_comment, pull_request_review_comment, pull_request, issues event payloads; author, path, body, number, id fields; webhook delivery GET /app/hook/deliveries endpoint.

Cryptography & Signing: JWT generation was mentioned but not the underlying mechanisms. Found: openssl dgst -sha256 -sign for RS256, PEM private key format, JWT claims (iss, iat, exp), app_id in JWT.

Temporary File Handling: mktemp usage found in 10+ locations with platform-dependent -t behavior not documented.

Environment Variables: 40+ MONITOR_* config override vars in watcher, plus NEXUS_ROOT, NEXUS_WORKER_WINDOW, NEXUS_STATE_DIR, ANTHROPIC_API_KEY for probe feature.

State File Schemas: Heartbeat JSON, over-limit JSON, and worker-notifications.jsonl formats were not listed; used by pane-state.sh and orchestrator heartbeat logic.

Version Assumptions

  • Node.js: >=18.0 (enforced by monitor/install-claude-local.sh); npm version assumed compatible
  • Python: 3.x with pyyaml for config loading; python3-yaml package mentioned
  • GitHub API: 2022-11-28 API version assumed stable; GraphQL schema fields (reactions, comments, etc.) assumed unchanging
  • tmux: Format strings (#{window_index}, #{window_activity}, etc.) assumed stable across versions
  • GNU coreutils: date -d, stat -c, sed -E, grep -P assumed available; not compatible with BusyBox/Alpine
  • Bash: 4.0+ assumed for [[ ]], ${var%%pattern}, mapfile, etc.
  • Git: 2.x assumed for standard porcelain commands
  • Curl: 7.x+ with --max-time, -D (headers to file), -w (write-out format) assumed

Thin Sections Needing Expansion

  1. Slack/Mattermost Integration: notify.sh mentions 'mail' as a wait kind but no Slack/team-chat backends documented
  2. Custom GitHub Webhooks: The HMAC verification stub in config/nexus.example.yml mentions future self-hosted receiver but current implementation trusts GitHub's delivery log exclusively
  3. Rate-Limit Probing Endpoint: monitor/watcher/_unstick.sh and _over_limit.sh use Anthropic API for rate-limit detection but the exact endpoint and response schema not in inventory
  4. Locale & Encoding: Monitor/.state files assume UTF-8; no documentation of locale-dependent sort/collation behavior
  5. Terminal Width/TTY Detection: No detected usage of tput cols or stty; pane-state.sh doesn't adapt to terminal width
  6. Database/Event Sourcing: No persistent event log or audit trail beyond diff archives and .jsonl files
  7. Scheduled Wakeup Integration: ScheduleWakeup tool mentioned in docs but exact payload format and delaySeconds field constraints not in existing inventory
  8. CronCreate Durability Workaround: Skills reference nexus.cron-state-tsv for recovery-marker pattern due to durable:true flag being silently ignored; TSV schema not in inventory

Per-section version assumptions

  • Claude Code — CLI flags & invocation — VERSION PINNING: package.json declares @anthropic-ai/claude-code 2.1.173 as a maintainer-managed vetted FLOOR for fresh installs (not a per-release pin); package-lock.json locks node_modules to 2.1.161; a successful gated cc-update advances the operator-local pin (monitor/.state/cc-version-local) without editing either file. install-claude-local.sh implements idempotent fast-path: if node_modules/.bin/claude exists and reports version == pinned_version, skip reinstall; else npm ci (reproducible with lock) or npm install (first-time setup). Requires Node.js >= 18.0.0. ENVIRONMENT SUMMARY: Three main env vars exported into Claude processes: NEXUS_ROOT (repo path), NEXUS_IS_ORCHESTRATOR (0 or 1, orchestrator only), NEXUS_WORKER_WINDOW (tmux window name, worker only). Plus CLAUDE_CODE_RESUME_THRESHOLD_MINUTES and CLAUDE_CODE_RESUME_TOKEN_THRESHOLD (respawn paths only, to suppress resume dialog). SETTINGS FILES: Two JSON files passed via --settings: orchestrator-settings.json (orchestrator only, includes session-pin hook) and worker-settings.json (worker only, includes heartbeat/notification/decision-emit hooks). Both set skipDangerousModePermissionPrompt: true and an env block with DISABLE_AUTOUPDATER=1 (the built-in npm auto-updater never applies to the project-local pin; disabling it removes the spurious "Auto-update failed" banner). HOOK SCHEMA: Hooks are keyed by event (PreToolUse, PostToolUse, Notification, UserPromptSubmit, PermissionRequest, Stop, StopFailure) and optional matcher (AskUserQuestion, Bash, Write, Edit, NotebookEdit). Each hook runs a shell command that typically redirects jq output into state files under $NEXUS_ROOT/monitor/.state/. SESSION RESUMPTION: The watcher owns it via the orchestrator session-id pin: _respawn_choose_resume_mode resolves a valid pin to --resume and degrades to a deterministic cold fresh spawn otherwise (never bare --continue — issue 200). entry.sh only reconciles operator intent onto the pin (default archives it; --continue keeps it). claude-loop.sh's --continue path sets CLAUDE_CODE_RESUME_THRESHOLD_MINUTES=999999999 to suppress the resume dialog. The pin file is written at spawn and re-affirmed on every UserPromptSubmit via orchestrator-session-pin.sh hook. BINARY RESOLUTION: _claude-bin.sh is sourced by all spawn paths. Resolves CLAUDE_BIN in order: env override, project-local $NEXUS_ROOT/node_modules/.bin/claude, system claude on PATH. Path is baked into launcher heredocs at write time so spawned windows use absolute path, not PATH lookup. TEST FILES: test-.sh files in monitor/watcher/ test spawn-worker.sh, spawn-fresh-orchestrator.sh, and respawn logic. Tests capture launcher body and assert on presence of --dangerously-skip-permissions, --continue (default), --resume (when pin valid), --settings (when file exists). Not included in the entries above but should be scanned for breaking regressions if Claude Code CLI changes. GAPS / ASSUMPTIONS: (1) Hook payload schema is inferred from jq filters in orchestrator-session-pin.sh and worker hooks; no formal schema doc found. (2) session_id field name assumed from orchestrator-session-pin.sh jq '.session_id'; if Claude Code renames it, the hook silently fails. (3) Project slug encoding (char → dash) is documented in respawn.sh but not verified against Claude Code's actual implementation. (4) Resume dialog suppression assumes CLAUDE_CODE_RESUME env vars still exist in Claude Code; if removed or renamed, the respawn paths will fail silently and the picker will block.
  • Claude Code — env vars & settings/config JSON — Version assumptions: Claude Code v1.x CLI surface as of early 2026 (--dangerously-skip-permissions, --settings, --continue, --resume, hook events: PreToolUse, PostToolUse, Notification, UserPromptSubmit, PermissionRequest, Stop, StopFailure; hook payload JSON schema with .tool_name, .tool_input, .hook_event_name, .notification_type, .message, .session_id, .error_type). Hook payload stdin piping and settings JSON structure are the load-bearing interfaces. Ambiguities: the legacy .notification.type and .notification.message nesting was preserved in nexus-code for resilience but may not be in current Claude Code (PR #132 captured empirical payload shape); future Claude Code schema changes (e.g., field renames, event name capitalization, or hook event addition/removal) would break nexus-code's decision-emit, heartbeat, and session-pin mechanisms unless the scripts update their jq filters and event matchers. Test files (monitor/watcher/test-.sh) exercise all these interfaces and would fail immediately on CLI/settings schema changes. The only DISABLE_ env var referenced is DISABLE_AUTOUPDATER (set to "1" via the settings env block to silence the built-in npm auto-updater, which never applies to the project-local pin); no telemetry or apiKey-helper env vars are referenced in nexus-code itself (those are user-side Claude Code config); NEXUS_* vars are nexus-specific, not Claude Code's.
  • Claude Code — TUI / pane-rendering string parsing — Version assumptions: assumes Claude Code renders panes via tmux with CSI escape sequences (ANSI 256-colour, reverse-video, faint/dim). Heartbeat JSON format and field names are contingent on worker-settings.json hooks. Rate-limit reset_at field path in StopFailure payload is empirically unknown (issue #129 q2); code probes multiple plausible paths. NBSP anchor (❯ vs ❯) is critical for distinguishing input row from menu rows. The complete colour palette (38;5;231, 38;5;246, 38;5;244 etc) is hardcoded in rendering patterns and would require code changes if Claude Code switches to different 256-colour indices or adopts 24-bit true colour. Monitor handle count parsing accepts both spinner-row form ('N monitor still running') and status-line form ('· N monitor ·') with automatic selection. Over-limit detection is anchored to bottom 15 rows to avoid false-match in scrollback; this constraint is load-bearing. All state classifications depend on input-row chevron anchor (❯); loss of that anchor breaks the entire classifier. Gaps: no explicit dependency on specific tmux version; no constraint on terminal emulator; no constraint on system locale affecting date parsing in over-limit reset_at extraction.
  • Claude Code — session model & jsonl session log — VERSION ASSUMPTIONS: This inventory targets Claude Code's current session model as of 2025-02-cutoff, assuming: (1) JSONL session logs at ~/.claude/projects//.jsonl; (2) session_id as 8-4-4-4-12 UUID in hook payloads and current_session_id file; (3) project-slug encoding as all-non-alphanumeric→hyphen (including '/', '_', '.') matching CC's internal path normalization; (4) hook events (UserPromptSubmit, Stop, PostToolUse, Notification) fire with JSON payloads; (5) --resume and --continue as valid command-line flags. DEPRECATION NOTICES: _orchestrator_liveness.sh header (lines 32-36) marks jsonl-mtime-only signal as deprecated fallback slated for removal after "the deprecation window" — earliest version where heartbeat/paste-received hooks became mandatory unknown, but new liveness machine (issue #164) is designed to outlive it. GAPS & AMBIGUITIES: (1) No inventory of monitor/orchestrator-settings.json hook schema itself — changes to hook event names, payload field names, or the hooks section structure are breaking but live in JSON, not covered here; (2) CLAUDE_PROJECT_DIR path and current_session_id location inside it are not catalogued — changes to CC's internal session bookkeeping location would break ng's preferred source; (3) Hook asynchronous-background execution (backgrounding touches via (... &) >/dev/null 2>&1) is not listed as a dependency but would break if CC changes hook execution model; (4) Session-log write frequency is assumed empirically (frequent-enough for mtime granularity) but no formal contract exists; (5) timestamp-precision granularity in session-id or mtime comparisons is not specified — potential brittle-ness in "mtime > last_paste_ts" comparisons if CC switches to sub-second or drifts write cadence. ENTRY COMPLETENESS: Searched all .sh files under monitor/, monitor/watcher/, monitor/hooks/, plus monitor/ng and related JSON settings. Focused on non-test references for "where"; test-only usages (test-.sh, test-integration/) are tagged "(test)" in purpose field when inclusion felt critical to understanding the contract. Some entries compress 8+ locations into representative paths + "+N more" when patterns were obvious (e.g., every test-.sh that uses slug encoding).
  • tmux — Version assumptions: Code targets tmux 2.6+ (based on test notes about CSI 3J in stub-claude.sh line 70, tmux <= 2.6 caveats). The repo explicitly acknowledges tmux 2.x vs 3.x differences in display-message behavior (pane-state.sh lines 755-757). Format tokens (#{...}) are stable across 2.6-3.x releases tested in GitHub Actions. Key load-bearing interfaces: (1) remain-on-exit on + capture-pane for wrap-up detection, (2) display-message + format tokens for window metadata extraction, (3) load-buffer + paste-buffer + send-keys for prompt injection, (4) window renaming for lifecycle stability, (5) list-windows for worker discovery. Test-only usages (marked "(test)") include socket isolation (-L), config override (-f), and harness teardown (kill-server). The -W flag for list-windows is not used; sorting/filtering is done client-side via awk/grep. Session/pane targeting syntax: "session:window" or "session:window.pane" or "%pane_id" (pane IDs like %42 from TMUX_PANE). Buffer names are arbitrary strings (nexus-respawn-$$-epoch pattern). No hard dependency on base-index or pane-base-index defaults (always use explicit indices/names). The send-keys "Enter" must be capitalized (not "enter"); "Escape" and "BSpace" are special key names. No documented dependency on $session_name, $pane_current_command extraction (though pane_pid liveness check uses ps to inspect /proc//comm matching "claude"). Status-bar or color configuration is not invoked (tests use -f /dev/null to suppress user tmux.conf). No direct mouse mode, copy-mode, or binding modification. The orchestrator-session-pin hook is gated by NEXUS_IS_ORCHESTRATOR env var to avoid side effects on non-orchestrator sessions.
  • gh CLI + GitHub REST/GraphQL API — Version Assumptions and Compatibility Notes: 1. GitHub REST API Version (2022-11-28): Hardcoded in curl calls and gh CLI invocations. If GitHub retires this version, header X-GitHub-Api-Version must be updated across monitor/mint-token.sh, monitor/watcher/_deliveries.sh, and monitor/user-pat.sh. The gh CLI may auto-negotiate a newer version; curl calls will fail outright if the header is no longer accepted. 2. GraphQL Schema Stability: GraphQL queries in _github.sh and _mentions.sh rely on exact field names and structure. Breaking changes include: (a) removing databaseId (deprecated in favor of id on many objects), (b) changing comments(last: N) pagination pagination, © renaming reactions.nodes[].content enum values (EYES/ROCKET), (d) removing .repository.nameWithOwner in favor of .repo or similar, (e) removing reviewThreads collection on PR objects. The per-surface backoff and gate mechanisms (_graphql_backoff_active, _graphql_polling_gate, _watcher_handle_graphql_failure) are resilient to some transient failures but will fail outright on schema changes. 3. Installation Token Lifecycle: POST /app/installations//accessokens is the JWT→token exchange. Bakage scenarios: (a) endpoint path changes, (b) JWT format rejected (currently RS256 with iss/iat/exp), © response .token/.expirest fields missing, (d) expiry format compatible with date -d (currently ISO 8601). The 55-minute cache window (300 s safety margin) assumes expirest is reliably 1 hour in theuture; if GitHub changes the TTL, cache hits/misses will become stale. 4. Webhook Delivery Pload Structure: _deliveries.sh pars .event and .action enums (e.g., issueomment, pull_requesreviewomment, created, submitted,pened) to gate emission. If GitHub changes these enum values, the conditional logic on line 313 onward (case "$event" ... elif ...) will silently drop events and surface nothing, even though the API call succeeds. Similarly, field paths like .request.payload.issue.number and .comment.user.login are hardcoded; reorganization (e.g., .issue → .issue_data) breaks silently. 5. Reaction Content um (EYES, ROCKET): Hardcoded ng verb bodies and GraphQL jq filters. If GitHub renames these enums (e.g., "EYES" → "EYE", "ROCKET" → "ROCKET_EMOJI") or removes them, reactions will silently POST invalid content (4xx error) or fail to filter incoming reactions (no matching .content value). The _functional_check helpers that test reactions assume the enum values are stable. 6. Rate Limit Bucket Structure: _graphql_polling_gate expects .resources.graphql.remaining from /rate_limit. If GitHub flattens the structure (e.g., .graphql.remaining instead of .resources.graphql.remaining), the jq extraction fails silently and the gate assumes bucket is healthy, potentially drowning in rate-limit errors. The probe also assumes the free REST core bucket (used for /rate_limit itself) is separate from the GraphQL bucket; convergence or deprecation of one will degrade the gate's utility. 7. Cross-Repo Context: snapshot_mentions and deliveries (mention= shape) rely on .repository.nameWithOwner to disambiguate cross-repo events. _filter_cross_repo_surface makes routing decisions based on whether the emit header contains mention= or cross_repo= to distinguish App-installed repos (mention, can comment) from search-only hits (cross_repo, cannot comment). If GitHub changes how these ntexts are signaled webhooks or search, the downstream routing and capability model breaks. 8. No GraphQL Mutation The watcher is read-only othe GraphQL side. All writes (reactions, comments, issue/PR updates) go through REST. If GitHub moves reactions to GraphQL-only or deprecates the REST endpoints, substantial refactoring is needed. 9. Test Infrastructu: Many test les (test-emit-gate.sh, test-graphql-gate.sh, etc.) mock gh by shadowing it on PATH with a shell stub. These mocks hardcode response shapes (e.g., {"graphql":{"remaining":5000,...}}). Tests will fail immediately if response shapes change, but production code may not fail until that code path is actually executed (e.g., a new edge case in the snapshot logic). 10. Pagination Cursor Stability: monitor/.state files (last-mention-cursor.txt, last-delivery-cursor.txt, processed-comments.txt) store comment databaseIds, delivery GUIDs, and issue numbers. If GitHub changes how these identifiers are minted, assigned, or persisted (e.g., databaseId format changes from 32-bit to 64-bit int), the cursor-based dedup logic may skip comments or re-emit duplicates. 11. HTTP Status Codes: Code assumes 200 = success, 404 = not found (webhook not configured), 401 = auth failure. If GitHub changes status semantics (e.g., 404 → 410 Gone for /app/hook/deliveries when webhook is unconfigured), the handling may silently degrade or error differently. 12. Completeness: This inventory covers every gh api, gh auth, curl -H Authorization: Bearer, GraphQL query field, and REST endpoint used in production watcher and ng code. Test-only usages are tagged (test). The inventory is per-file-location; if code is refactored across files or files are deleted, the paths will need updating but the interface dependencies remain the same.
  • git — Version assumptions: (1) main branch is default branch assumption (hardcoded in upload-asset.sh, test-upload-asset-gitignore.sh, tests). (2) Porcelain v1 format for status --porcelain (used in main.sh to detect dirty state via emptiness check). (3) git remote get-url origin resolves to HTTPS or SSH URL with github.com pattern, regex expects github.com[:/]owner/repo form. (4) Authentication via https:// with x-access-token:TOKEN@ form (GitHub HTTPS token auth); SSH keys not explicitly handled in nexus-code itself, though test repos use git@github.com: URLs. (5) All paths use git -C to operate in specific directories rather than cd'ing. (6) Repo identity derived from remote origin URL parsing; git@github.com: vs https://github.com/ formats both supported via regex. (7) No git worktree add/remove actual invocations found in production code, only mentioned in comments/test names and documentation. (8) Gaps: no explicit error handling for git network failures beyond stderr redirection; no retry logic; no git config mutations beyond local user.name/user.email -c flags. (9) Test-only usage includes git init, symbolic-ref, and branch -M for seeding test repos; not production code paths.
  • Misc CLIs & shell (jq, python, node/npm, SLURM, coreutils, bash, agent-sandbox) — ## Version Assumptions - jq: Tested against jq 1.5+ (as noted in comments about IEEE 754 number parsing). No version check enforced; degradation is silent with grep fallback. - python3: >=3.6 (implicit from f-string and type hint usage in embedded scripts). Requires pyyaml for YAML parsing. - Node.js: >=18.0.0 (enforced in install-claude-local.sh line 52-54). npm required for package-lock.json handling. - bash: 4.0+ minimum for associative arrays and [[ ]]; 4.3+ for nameref (local -n). - GNU tools: sed -E, grep -P, stat -c, date -d, readlink -f are GNU-specific (no BSD fallback). System must be Linux or provide GNU coreutils. - SLURM: Assumes sbatch and srun output format is stable (Submitted batch job N, Submitted job N). Patterns in async-launch-patterns.conf are data-driven and can be updated without code changes. - agent-sandbox: Required for safe isolation. entry.sh and bootstrap-install.sh fail hard if SANDBOX_ACTIVE != 1. ## Gaps and Ambiguities - coreutils version pinning: No explicit minimum version for individual tools. Assumption is Linux with GNU coreutils from the last 5-10 years. - tmux: No version check; assumes tmux 2.0+ based on option syntax. Window name restrictions and ANSI handling may vary by version. - jq line-by-line reads: Some filters are applied to single-line JSON (heartbeat files); behavior depends on jq's handling of compact JSON. Multiline JSON in stdout/stdin is capped at 64KiB by design. - Python imports: Only yaml is imported in load.sh; json/sys imports are present in test files but implicit. - Regex flavor: bash [[ ]] uses POSIX ERE; grep -P uses PCRE (gnu-only). No attempt to support other flavors. - Numeric precision: jq comment at pane-state.sh line ~1025 notes IEEE 754 parsing; large epoch times may lose precision on some systems. - Date handling: Assumes TZ environment variable is respected; some date -d formats may not work across all locales. ## Files Skipped - test fixture JSON files (monitor/watcher/fixtures/.json) — contain test data, not functional dependencies. - .github/workflows/.yml — GitHub Actions CI definitions; do not affect runtime. - Documentation and comments in .md files — advisory only.

Maintaining this doc

When you touch a parsed string, CLI flag, env var, API field, or settings key, update the matching row here in the same change. When a dependency adds a new interface, add a row. The path:line locations drift as code moves — treat a stale location as a bug in this doc. Regenerate fragility judgments after any Claude Code major version bump.