fix(watch): fail loudly on shifted args instead of running with zero subscriptions - #477
Merged
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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-buildinto the command line; grok's monitor tool re-evaluates that line in a shell whereGROK_SESSION_IDis unset, and the quoted-but-empty expansion is dropped as a word — every later argument shifts one slot left.watch.shthen parses the project path as the session id, the type as the project, and the agent name as the type;identities.shresolves 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)
_delivery.shmonitor rule,template.mdx3) now pass"${GROK_SESSION_ID:--}". When unset, this yields the literal sentinel-, which survives re-evaluation as a real argument.watch.shfolds-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.AGENT_TYPEagainst 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 singletype.confstat (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 usedagmsg_is_known_typeunconditionally and measurably tightened the 1s timing window of theexcludes pairs held by another live sessiontest; 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:-resolves exactly like an empty session id (fallbackagmsg-*pidfile appears, no--keyed run files, no error).tests/test_delivery.bats: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