Skip to content

fix(watch): fail loudly on shifted args instead of running with zero subscriptions - #477

Merged
fujibee merged 2 commits into
fujibee:mainfrom
Masashi-Ono0611:fix/watch-arg-shift-guard
Jul 26, 2026
Merged

fix(watch): fail loudly on shifted args instead of running with zero subscriptions#477
fujibee merged 2 commits into
fujibee:mainfrom
Masashi-Ono0611:fix/watch-arg-shift-guard

Conversation

@Masashi-Ono0611

Copy link
Copy Markdown
Contributor

Related to #475 (and the launch path introduced in #236, refined by #245).

Problem

A grok-build monitor watcher launched from the rule template can run with zero subscriptions, silently. The template bakes watch.sh "$GROK_SESSION_ID" '<project>' grok-build into the command line; grok's monitor tool re-evaluates that line in a shell where GROK_SESSION_ID is unset, and the quoted-but-empty expansion is dropped as a word — every later argument shifts one slot left. watch.sh then parses the project path as the session id, the type as the project, and the agent name as the type; identities.sh resolves no pairs, and the watcher polls forever delivering nothing. Observed in production (macOS zsh, v1.1.10-1-g5f8d0b7); full evidence in #475.

The existing empty-first-arg tolerance (#236) assumes the empty argument stays positionally present. It does not survive a caller shell that deletes the word entirely.

Fix (two minimal layers)

  1. Caller side — the grok-build templates (_delivery.sh monitor rule, template.md x3) now pass "${GROK_SESSION_ID:--}". When unset, this yields the literal sentinel -, which survives re-evaluation as a real argument. watch.sh folds - into the existing empty-arg resolution path, so feat(grok-build): add opt-in explicit-launch monitor delivery #236 behavior (self-generated fallback id) is preserved bit-for-bit.
  2. watch.sh defense — validate AGENT_TYPE against the registered types (discovered dynamically, no hardcoded list) and fail loudly when it is unknown, with a message that names the shifted-args cause and the sentinel remedy. Follows the Windows/Git Bash: native sqlite3.exe can't open MSYS /c/ DB path (inbox/send/watch fail) + 4 related findings #197 healthcheck pattern: one line on stdout (the monitor event stream), then exit 1. This guards every caller, not just grok. Cost design: a built-in type is confirmed by a single type.conf stat (watch.sh launches are a hot path — actas/drop/SessionStart relaunch it routinely); only a non-builtin name loads the type registry (agmsg_type_dir, so trusted external plugin types still validate), and the full type enumeration runs only inside the error message. An earlier draft used agmsg_is_known_type unconditionally and measurably tightened the 1s timing window of the excludes pairs held by another live session test; the stat fast path restored it to green 8/8.

Auto-detecting the shift (path-looking $1 + known-type $2) and re-shifting was considered and rejected: it stacks guessing on an already subtle launch path, and a loud error already surfaces the bug immediately with a precise remedy.

Tests

Review follow-ups folded in (multi-model review, 2 rounds): a shifted three-argument launch (no active_name) leaves only two args and the old ${3:?} guard died on stderr — invisible to a stdout-consuming monitor tool — so required-arg validation now fails explicitly on stdout; and a path-like value in the type slot (../types/claude-code) is rejected outright instead of reaching the registry where path concatenation could resolve it.

tests/test_watch.bats:

  • shifted arguments (agent name in the type slot) exit non-zero with the unknown-agent-type error and never arm a pidfile;
  • shifted three-arg launch (missing agent_type) fails loudly on stdout;
  • path-like agent type is rejected outright;
  • sentinel - resolves exactly like an empty session id (fallback agmsg-* pidfile appears, no --keyed run files, no error).

tests/test_delivery.bats:

  • the grok monitor rule bakes watch.sh "${GROK_SESSION_ID:--}" (regression guard for the caller-side layer).

Ran locally: the two new watch tests and all 7 grok-build delivery tests pass; pre-existing environment-specific failures in test_watch.bats (1, 2, 3, 5 — watcher-delivery timing under a live agent ancestor) fail identically on a clean upstream/main checkout with this branch's changes stashed, i.e. unrelated to this change.

🤖 Generated with Claude Code

Masashi-Ono0611 and others added 2 commits July 23, 2026 06:53
…subscriptions

A grok-build monitor watcher launched from the rule template could run
with zero subscriptions, silently: the template baked
watch.sh "$GROK_SESSION_ID" into the command line, and grok's monitor
tool re-evaluates that line in a shell where the variable is unset — the
quoted-but-empty expansion is dropped as a word, shifting every later
argument one slot left. watch.sh then parsed the project path as the
session id and an agent name as the type, identities.sh resolved no
pairs, and the watcher polled forever delivering nothing.

Two minimal layers:

- Caller side: the grok-build templates now pass "${GROK_SESSION_ID:--}".
  When unset this yields the sentinel '-', which survives re-evaluation
  as a real argument; watch.sh folds '-' into the existing empty-arg
  resolution path (fujibee#236 behavior preserved).
- watch.sh defense: validate agent_type against the registered types and
  fail loudly (one line on stdout, fujibee#197 pattern) when unknown, naming
  the shifted-args cause. A built-in type is confirmed by a single
  type.conf stat to keep the launch hot path free; non-builtin names
  fall back to the type registry so trusted plugin types still validate.

Related to fujibee#475.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review follow-ups: a shifted three-argument launch leaves only two args,
and the old ${3:?} guard died on stderr — invisible to a monitor tool
consuming stdout. Replace it with an explicit check that fails on stdout
and names the shifted-args cause. Also reject '/' and '..' in the type
slot outright instead of letting the registry fallback concatenate a
path-like value into a manifest path.

Related to fujibee#475.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@fujibee
fujibee merged commit ed7f160 into fujibee:main Jul 26, 2026
7 checks passed
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.

2 participants