Skip to content

fix(afk): refuse unverifiable away-mode delivery - #2217

Closed
coreldh wants to merge 2 commits into
kunchenguid:mainfrom
coreldh:fm/c0806n-fm-1506a
Closed

coreldh wants to merge 2 commits into
kunchenguid:mainfrom
coreldh:fm/c0806n-fm-1506a

Conversation

@coreldh

@coreldh coreldh commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Intent

Publish the captain-approved local fix for defect A of #1506. The change must stop absent TMUX_PANE from becoming the fabricated firstmate:0 target, identify the real non-tmux Ghostty operator session from stable existing session-lock process and controlling-TTY evidence, preserve strict composer identity by refusing a plain TTY as an injectable endpoint, report blind discovery as explicit UNVERIFIED rather than confidently absent, and make refused/unavailable delivery observable through a durable marker, stderr, and an independent configured alarm path with firing and negative controls. The pull request body must reference #1506 and clearly say it fixes defect A: fabricated operator-session identity silently disabling away-mode escalation delivery. Push only to the configured coreldh/firstmate fork, open exactly one PR against kunchenguid/firstmate:main, and do not merge, self-approve, force-push, comment on the issue, or contact the maintainer.

What Changed

  • Fixes defect A: fabricated operator-session identity silently disabling away-mode escalation delivery (#1506).
  • Refuse unverified supervisor delivery targets before arming away mode, while retaining Ghostty operator identity from the session lock and controlling TTY.
  • Record and alarm on unavailable delivery, with updated lifecycle coverage and operator documentation.

Risk Assessment

✅ Low: Captain, the change cleanly replaces the fabricated fallback with fail-closed discovery and covers the required refusal, marker, and alarm paths.

Testing

Reviewed the target diff, ran the focused daemon and away-launch tests, and captured a direct blind-discovery CLI refusal artifact; the required fail-closed delivery path works end-to-end, while the optional Herdr topology E2E is not verifiable in this shared environment because its lab-session tripwire failed.

Evidence: Blind-delivery refusal CLI transcript
command_exit=1
--- CLI output ---
error: away mode NOT ARMED: escalation delivery unavailable; operator_session=UNVERIFIED(no-regular-session-lock); backend=UNVERIFIED(no-supported-delivery-backend); target=UNVERIFIED(no-supported-delivery-target); alarm marker=/var/folders/cq/xf4qcb9j0qzc2dbh173mflbm0000gn/T/no-mistakes-evidence/01KZSWNJR03XFHAYZZX3V4JWRF/manual-command-alarm.qVMVgm/.subsuper-delivery-unavailable
--- durable marker ---
away mode NOT ARMED: escalation delivery unavailable; operator_session=UNVERIFIED(no-regular-session-lock); backend=UNVERIFIED(no-supported-delivery-backend); target=UNVERIFIED(no-supported-delivery-target)
Firstmate refused to guess an operator delivery endpoint. Configure FM_SUPERVISOR_BACKEND and FM_SUPERVISOR_TARGET for a supported endpoint.
--- configured alarm output ---
away mode NOT ARMED: escalation delivery unavailable; operator_session=UNVERIFIED(no-regular-session-lock); backend=UNVERIFIED(no-supported-delivery-backend); target=UNVERIFIED(no-supported-delivery-target)
away_flag=ABSENT
Evidence: Focused daemon test log
ok - fm-afk-start.sh fails before daemon startup when the afk flag cannot be written
ok - fm-afk-start.sh ignores stale pidfile-only live pids and reaches delivery validation
ok - fm-afk-start.sh reclaims stale daemon locks whose live pid identity no longer matches
ok - fm-afk-start.sh blind delivery refuses and alarms before away state
ok - supervise daemon state root is scoped by FM_HOME
ok - routine signal self-handles
ok - captain-relevant status verbs escalate
ok - check + unknown escalate; heartbeat self-handles
ok - transient stale self-handles and records a persistence marker
ok - enriched stale wedges bypass status absorption without disturbing busy workers
ok - stale + terminal status escalates immediately
ok - paused reasons with captain phrases remain pause-classified
ok - handle_wake on a paused stale records a pause marker, drops the wedge marker, and does not escalate
ok - handle_wake records a declared pause from a routine signal for long-cadence rechecks
ok - a terminal signal clears pause and stale tracking across both supervisors
ok - housekeeping migrates a normal-watcher's declared pause into daemon tracking
ok - housekeeping clears an already-resumed watcher pause across both supervisors
ok - housekeeping seeds pause tracking from status without a watcher marker
ok - persistent stale escalates after threshold and clears its marker
ok - resumed (busy) stale clears its marker without escalating
ok - housekeeping re-surfaces a stale declared pause on the long cadence and resets its window
ok - housekeeping clears a paused marker whose pane became busy again, without escalating
ok - housekeeping clears a paused marker once the crew is no longer declaring the pause
ok - housekeeping moves an existing stale marker to pause before wedge escalation
ok - housekeeping clears tracking when a crew leaves pause
ok - persistent herdr stale resolves the target from metadata and escalates
ok - herdr idle busy-footer stale clears through capture corroboration
ok - resumed herdr stale clears through backend-aware busy state
ok - persistent Orca stale resolves the terminal from metadata
ok - multiple escalations flush as a single batched digest
ok - batch flush measures max-delay from the first append, not the last
ok - catch-all scan escalates a missed terminal once, not twice
ok - handle_wake routes routine->self and captain->escalate
ok - INJECT_SKIP forces self-handle, bypassing captain-relevant classification
ok - is_wake_reason distinguishes watcher wake reasons from singleton-status stdout
ok - terminal-stale escalate removes its marker so housekeeping does not re-escalate
ok - captain signal escalate marks seen so the catch-all scan does not re-fire
ok - _collapse_newlines replaces newlines with literal separator
ok - afk flag absent: daemon does not inject, buffer preserved
ok - busy-guard defers injection when supervisor pane is busy
ok - marker detection: marker -> stay afk, no marker -> exit afk
ok - /afk invocation is exempt from afk exit (no self-cancel)
ok - should_exit_afk returns false when afk is not active
ok - strip_injection_marker removes the sentinel marker cleanly
ok - pane_input_pending detects partial input on the cursor line
ok - pane_input_pending: a blank unidentified cursor row defers (strict container-proof rule)
ok - pane_input_pending: only proven empty agent prompts pass
ok - fm_tmux_composer_state: a bare shell prompt ($/%/#/>) reads unknown, never empty (dead-shell injection safety)
ok - fm_tmux_composer_state: a bordered composer box and bare agent glyphs (❯/›) still read empty
ok - fm_tmux_composer_state: only matching edge borders form a composer box
ok - pane_input_pending preserves bright placeholder-like drafts in styled captures
ok - classify_signal dedupes against the catch-all scan seen marker
ok - classify_stale dedupes against the signal path seen marker
ok - AFK nonterminal working:+merged keeps wedge aging and re-escalates at bound
ok - genuine done: and merge-check events still escalate
ok - pane_input_pending: an idle bordered composer is NOT pending (afk-invx-i5)
ok - pane_input_pending: text inside a bordered composer is still pending
ok - submit-ACK confirms a submit when the composer returns to a bordered-empty box
ok - submit-ACK reports pending on a persistently swallowed Enter (type-once)
ok - max-defer on an empty stuck pane types once, alarms, and preserves the buffer
ok - max-defer flushes and clears the buffer on an empty bordered pane
ok - max-defer on a pending composer alarms without typing
ok - normal flush clears a stale wedge marker
ok - below MAX_DEFER: no inject, no alarm, buffer preserved
ok - max-defer does not flush or alarm while afk is inactive
ok - library mode: sourcing the daemon defaults FM_WEDGE_ALARM_EXEC to discard (no test can fire a real notification)
ok - wake helpers replace inherited notifier overrides with the safe recorder
ok - the discard seam suppresses every notifier, including command: (fires nothing)
ok - direct notifier helpers honor the discard seam, including command:
ok - osascript channel routes through the notifier seam with the summary (never a real notification)
ok - herdr channel routes through the notifier seam with the summary (never a real notification)
ok - command channel runs the captain command with the summary on $1 and on stdin
ok - command channel failures redact configured commands while logging their exit status
ok - unknown channel directives are redacted while the alarm keeps running
ok - off disables every active alert regardless of directive position (marker and tmux flash are unaffected)
ok - auto resolves to the macOS osascript notifier on Darwin (default-on)
ok - auto on a non-macOS platform selects no built-in OS channel (the marker or a configured command carries it)
ok - config/wedge-alarm selects every configured channel and skips comment and blank lines
ok - a failing channel logs and falls back to the next channel, never crashing the alarm
ok - a hung notifier is bounded, logged, and falls through to the next channel
ok - a backgrounded command notifier remains bounded until its process group is reaped
ok - a hung notifier override is bounded, logged, and proceeds to the next channel
/Users/admin/.no-mistakes/worktrees/aa652f3359ba/01KZSWNJR03XFHAYZZX3V4JWRF/bin/fm-supervise-daemon.sh: line 752: 39373 Terminated: 15          sh -c 'sleep 30 & printf "%s" "$!" > "$1"; wait' sh "$child_file"
ok - daemon shutdown stops and reaps the active notifier process group
ok - inject_wedge_alarm writes the marker AND emits the active alert even with no tmux status-line (herdr backend)
ok - in-process wedge throttle prevents alert spam when the marker cannot persist
ok - fm-send exits non-zero on a confirmed swallow, zero on a clean submit
ok - fm-send exits non-zero when initial text send fails
ok - fm-send exits non-zero unless delivery is proven empty
ok - discover_supervisor_backend: override > TMUX_PANE > HERDR_ENV+HERDR_PANE_ID > explicit UNVERIFIED
ok - discover_supervisor_target: override > TMUX_PANE > herdr '<session>:<pane-id>' composition > explicit UNVERIFIED
ok - operator identity: Ghostty primary resolves from the live session lock and controlling TTY without tmux
ok - operator identity: a blind path reports UNVERIFIED rather than confidently absent
ok - delivery refusal: exact Ghostty-shaped firing input writes a marker and fires the independent alert
ok - pane_is_busy: herdr native busy_state='busy' short-circuits without a capture fallback
ok - primary busy guard isolates rendered signatures by detected harness
ok - pane_is_busy: omitted backend defaults to tmux for Grok's isolated fallback
ok - pane_input_pending: dispatches through fm_backend_composer_state for backend=herdr
ok - inject_msg: herdr busy-guard defers before ever attempting a submit
ok - inject_msg: herdr composer-guard defers before ever attempting a submit
ok - inject_msg: herdr pane-gone check defers before any busy/composer/submit call
ok - inject_msg: dispatches busy-guard/composer-guard/submit through the herdr backend and succeeds on a confirmed empty composer
ok - inject_msg: defers on a dead-shell/unreadable composer (unknown), never typing the escalation into a shell
ok - inject_msg: unrecognized composer states defer by default
Evidence: Focused launcher test log
ok - clear-stale: removes escalations buffer, sidecar, and wedge marker
ok - clear-stale: leaves the durable wake-queue intact (no pending work dropped)
ok - launcher paths: relative home and state ignore CDPATH before daemon command construction
ok - launcher paths: absolute symlink spellings are preserved
ok - launcher paths: unresolved relative FM_HOME fails loudly
ok - launcher paths: unresolved relative FM_STATE_OVERRIDE fails loudly
ok - refresh: daemon already alive - stale artifacts preserved (current session's buffer kept)
ok - stop-ordering: daemon SIGTERM'd while .afk still present (flush is not a no-op)
ok - stop-ordering: .afk cleared last
ok - stop-ordering: daemon-terminal record removed
ok - stop identity: stale lock cannot signal an unrelated live process
ok - failed start: away flag and delivery artifacts roll back
ok - concurrent start: one serialized daemon terminal remains tracked
ok - launcher lock: incomplete publication receives initialization grace
ok - launcher signal: TERM exits and releases the lifecycle lock
fm-afk-launch: daemon launched in non-visible herdr workspace ws-partial (pane lab:pane-exact), supervising lab:captain
ok - herdr create: malformed response recovers durable exact ownership
fm-afk-launch: herdr create failed after returning exact ids; closing lab:pane-exact
fm-afk-launch: recorded terminal teardown is unconfirmed; preserving exact id
ok - herdr create error: unconfirmed exact id is persisted for reconciliation
fm-afk-launch: failed to run daemon in herdr pane lab:pane-exact; closing it
fm-afk-launch: recorded terminal teardown is unconfirmed; preserving exact id
ok - herdr run failure: unconfirmed exact id remains reconcilable
fm-afk-launch: failed to persist daemon terminal record; closing tmux:exact-session
ok - record failure: newly created terminal is closed by exact id
fm-afk-launch: daemon did not become ready; closing tmux:exact-session
ok - readiness failure: exact terminal and durable record roll back
fm-afk-launch: daemon did not become ready; closing tmux:exact-session
fm-afk-launch: recorded terminal teardown is unconfirmed; preserving exact id
ok - readiness failure: unconfirmed terminal retains its reconciliation id
ok - tmux absence: clean missing differs from transport probe failure
ok - native lifecycle: launcher owns state with no terminal
ok - native lifecycle: uniform stop clears state without closing a terminal
ok - native entry: launcher-prepared lifecycle state is not rewritten
error: away mode NOT ARMED: escalation delivery unavailable; operator_session=harness-pid=4242;tty=/dev/ttys042; backend=UNVERIFIED(no-supported-delivery-backend); target=UNVERIFIED(no-supported-delivery-target); alarm marker=/var/folders/cq/xf4qcb9j0qzc2dbh173mflbm0000gn/T//fm-afk-delivery-preflight.223Lmt/state/.subsuper-delivery-unavailable
ok - delivery preflight: Ghostty-shaped missing endpoint refuses before away state and fires the independent alarm
ok - delivery preflight: discovered endpoint is the negative control and fires no alarm
fm-afk-launch: reconciling leaked daemon terminal tmux:exact-session
fm-afk-launch: recorded terminal teardown is unconfirmed; preserving exact id
ok - teardown failure: exact terminal record is preserved
ok - record publication: failed atomic rename preserves the complete prior record
fm-afk-launch: daemon terminal record is malformed; refusing to act on it
ok - record read: malformed record fails closed without acting on a partial id
fm-afk-launch: daemon terminal record is malformed; refusing to act on it
fm-afk-launch: malformed daemon terminal record; refusing to stop away mode
ok - stop: malformed terminal record preserves away state and fails closed
fm-afk-launch: failed to create detached tmux daemon session 'fm-afk-daemon-4081440915-81743-24273-1786501997'
ok - tmux launch: planned exact target is recorded before creation and removed on failure
fm-afk-launch: failed to create detached tmux daemon session 'fm-afk-daemon-1523449970-81843-4168-1786501998'
ok - tmux launch: unique names eliminate collision teardown
ok - stop validation: malformed record causes no daemon or state side effects
ok - launcher lock: incomplete metadata fails acquisition and releases lock
fm-afk-launch: failed to clear away-mode flag
fm-afk-launch: away mode stopped; terminal teardown remains recorded for retry
ok - stop state: away-flag removal failure is surfaced
fm-afk-launch: away-mode daemon did not exit after SIGTERM; preserving lifecycle state
ok - stop liveness: captured live daemon preserves lifecycle state after lock release
fm-afk-launch: daemon terminal record is malformed; refusing to act on it
fm-afk-launch: daemon terminal record is malformed; refusing to act on it
ok - refresh record: malformed terminal identity fails closed
fm-afk-launch: failed to clear stale away-mode artifacts
ok - clear failure: native entry aborts and restores prior state
fm-afk-launch: reconciling leaked daemon terminal tmux:exact-session
fm-afk-launch: terminal close command failed, but exact absence was confirmed
ok - confirmed absence: cleanup succeeds and removes the stale record
fm-afk-launch: rollback restoration incomplete; backup retained at /var/folders/cq/xf4qcb9j0qzc2dbh173mflbm0000gn/T//fm-afk-restore-fail.mVdBEQ/state/.afk-launch-backup.3gkK1N
ok - rollback restore: incomplete restoration retains its recovery backup
ok - flag failure: lifecycle aborts without active state
fm-herdr-lab: fleet-state tripwire requires exactly one running default session
not ok - herdr e2e: could not prepare isolated lab session
ok - tmux e2e: captain window pane count unchanged after start (no split-window)
ok - tmux e2e: daemon launched in a separate detached session
ok - tmux e2e: captain window pane count unchanged after stop
ok - tmux e2e: daemon session killed by exact id on stop
ok - tmux e2e: record + .afk cleared on stop
- Outcome: ⚠️ 1 warning across 1 run (6m55s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

⚠️ **Test** - 1 warning
  • ⚠️ tests/fm-afk-launch.test.sh - The optional Herdr topology E2E could not prepare its isolated lab because the shared Herdr environment failed its required default-session tripwire. The tmux topology E2E and the targeted Ghostty-shaped delivery-preflight controls completed; this worktree-local test phase cannot safely alter shared Herdr state.
  • git diff --stat b5d430d6fdcd961ce9b681bf196f365c1825c284 cb8711e4bc5397a21b1a993e0c33e1af6f38f7f2 and targeted source/test inspection
  • bash tests/fm-daemon.test.sh (focused supervisor discovery, Ghostty lock/TTY identity, strict composer refusal, delivery-unavailable marker/alarm controls)
  • bash tests/fm-afk-launch.test.sh (focused launch preflight firing and negative controls; tmux topology E2E)
  • Direct end-user CLI check: bin/fm-afk-start.sh with TMUX/Herdr and supervisor overrides empty plus a configured command alarm; captured refusal, durable marker, alarm output, and absent away flag
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@coreldh

coreldh commented Sep 24, 2026

Copy link
Copy Markdown
Contributor Author

Closing in favor of #5591, a fresh and smaller fix for defect A of #1506 against current main.

This branch fell far behind main, and its rewrite of fm_afk_launch_start now conflicts with guards main added since. It also bundled a terminal-specific operator identity resolver, which the triage steered away from ("identity-or-refuse is simpler than a new discovery heuristic"). The new PR only refuses to arm pane escalation when no operator pane handle exists, and records that refusal durably.

@coreldh coreldh closed this Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant