Four defects that all reduce to the same thing: the engine trusted a name it had no business trusting. _broadcast is now a reserved namespace, not a repo. coord-send guarded retraction with a sender check, but that guard only covered the door it was nailed to: `coord-done --repo _broadcast <file>` walked in the side entrance and archived a broadcast out of the queue - a full unauthenticated retract of an announcement for every repo that had not read it yet. The rule reserves the whole `_` prefix rather than one literal, so a later `_seen` or `_config` cannot reopen the hole, and it is enforced in every CLI: a rule held in three of four places is not a rule. The pwd fallback is gone. It existed so the CLI would work anywhere, but "anywhere" includes every global surface: a session in ~/repos is not a repo, and basename(pwd) silently handed it the identity "repos". That is not hypothetical - mail was delivered under exactly that name. git toplevel or an explicit --from/--repo are now the only two sources. The write paths refuse and say so; the read path declines silently, because the hook runs it at every session start and must never fail a session. Two checkouts with the same directory name still share one mailbox - re-keying identity would break every existing mailbox and the readable `--to <repo>` addressing. Instead the first git-derived read records the claiming path in <repo>/.origin, and a read from elsewhere is warned about in the injection. A warning, not a refusal: the same repo moved or re-cloned is the ordinary case. The warning goes in the injection because the hook discards stderr, and a warning nobody can see is not a warning. The collision is live in this tree: claude-code-100x is nested inside a repo of the same name. Broadcast delivery is recorded only after the injection is written. Marking inside the read loop left a window where the seen set said "delivered" while the operator saw nothing, and the hook runs under `timeout: 10`, so the window was reachable. A lost broadcast is unrecoverable by design - the seen set is delivery history and retraction deliberately leaves it alone - so the failure mode has to be redelivery, never loss. The hook stops resolving identity altogether. It was the fourth copy of the rule and the only one that runs in production, so passing --repo bypassed the engine's guards exactly where they mattered and suppressed the collision check along with them. It is now the pure wrapper the boundary rule always claimed it was, pinned by two behavioral tests rather than by reading the source. Selftest 93 -> 116; three node tests cover the hook. BREAKING CHANGE: coord-send and coord-done exit 2 outside a git repo instead of naming themselves after the working directory. Pass --from/--repo to choose an identity explicitly. Repo names beginning with _ are refused everywhere. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U6EixQo6hpoRCVtiAXdnFs
12 KiB
| type | created | question | scope |
|---|---|---|---|
| feature-survey | 2026-07-25 | What features do tools in active use ship so a human does not have to actively monitor a queue? | complements 2026-07-25-cross-repo-delivery-research.md (which covered Unix/email prior art) |
Features that remove the need to watch
Companion to 2026-07-25-cross-repo-delivery-research.md. That brief surveyed
Unix and email prior art (maildir, biff, RFC 3834, cron/launchd). It explicitly
reported the "what do comparable projects actually ship" question as not
answered. This closes that gap.
The reframe that makes everything tractable
With no daemon, no scheduler and no network, nothing in this system can fire on its own — the only moment code runs is when the developer opens some repository. So the discriminating question per feature is not "does it need a daemon?" but "can it be computed at read time from timestamps and files already on disk?"
Every aging and escalation tool surveyed (actions/stale, PagerDuty,
probot/no-response) depends on an external clock. Their mechanics transfer;
their triggering does not. A state machine evaluated at read time needs no
clock of its own — only a comparison against now.
Candidate features
VERIFIED = the cited page was fetched and says this. Defaults are quoted only where documented.
| Feature | Tool | Mechanism | Fit |
|---|---|---|---|
Dependency Dashboard — one issue summarizing all pending work, body rewritten every run via ensureIssue({title, reuseTitle, body}), sectioned into Pending Approval / Awaiting Schedule / Rate-Limited / Errored / Open / … |
Renovate | one always-current artifact instead of N notices | direct |
| Dashboard as input device — "click on a checkbox below" turns the summary into the control surface | Renovate | read and act in the same object | direct |
| Severity bypasses the caps — "Security update pull requests are not subject to this limit and do not count toward it" | Dependabot | a priority field exempts an item from batching | direct |
One continuously-updated comment — found by a hidden header marker, not by search; modes append/recreate/delete/hide/only_create |
sticky-pull-request-comment | identity-by-marker | direct |
| Living release PR — one PR updated as new changesets land | changesets/action | same pattern, different substrate | direct |
Read and done are orthogonal — is:read "doesn't include notifications marked as Done"; PATCH marks read, DELETE marks done. Two verbs, two axes |
GitHub | seen ≠ resolved | direct |
Unread-only by default — all defaults to false |
GitHub REST | the primitive behind any pending count | direct |
Typed reason on every item — 14 values (mention, review_requested, state_change, …) |
GitHub | triage keys off why, not just what | direct |
One command, all repos — gh status prints assigned issues, PRs, review requests, mentions and activity across every subscribed repo in one view |
gh CLI | the closest analogue to the actual complaint | direct |
Auto-de-escalation — remove-stale-when-updated defaults to true; any activity clears the mark |
actions/stale | mtime change recomputes at read time | direct |
| Gap detection instead of guaranteed delivery — monotonic event IDs; missed events "can be detected by noting a discontinuity in the event IDs" | Syncthing | proves at read time that nothing was silently dropped | direct |
| Snooze that resurfaces itself — 1/4/8/24h or custom, max 168h; only on acknowledged incidents; on expiry the incident "returns to a triggered state and notifies you again" | PagerDuty | store a resurface timestamp, compare at read time | adapts |
Grouping — "All updates sharing the same groupName will be placed into the same branch/PR"; Dependabot groups: first matching rule wins |
Renovate, Dependabot | N items become 1 | adapts |
Schedule gates the action, not the run — schedule restricts "times… during which Renovate may create or update branches and PRs" |
Renovate | a gate evaluated inside a run survives having no scheduler | adapts |
Concurrency cap — prConcurrentLimit default 10; Dependabot raises max 5 and "no further pull requests are raised until some… are merged or closed" |
Renovate, Dependabot | cap what is displayed, not what is stored | adapts |
Age gate before auto-action — minimumReleaseAge (default null); maintainers recommend "14 days" before automerging third-party deps |
Renovate | age-gated automatic disposal | adapts |
| Persisted cursor replayed on restart — triggers "are saved and re-established across a Watchman process restart", re-evaluated from the last captured clock | Watchman | needs the daemon; the cursor idea does not | needs daemon |
Aging → stale → close — days-before-stale: 60, days-before-close: 7 |
actions/stale | state machine fits, triggering does not | needs daemon |
Hourly rate caps; operations-per-run: 30 |
Renovate, actions/stale | need a clock / a rate limit that does not exist locally | no fit |
Failure modes — the findings that change the design
The living-summary pattern breaks on identity, not on content. Renovate
discussion #12131: a repository got a new Dependency Dashboard roughly once an
hour. ensureIssue locates the existing dashboard by searching for issues
authored by the bot account; custom auth meant issues were created under a
different account, the lookup returned zero, and every run created a fresh
dashboard. Design constraint: the summary artifact must live at a fixed,
deterministic path and must never be located by searching on a mutable
attribute.
Its second failure mode is latency, and it reads as breakage. Discussion #9905: users reported dashboard checkboxes "doing nothing". The checkbox is a request queued for the next run, not an immediate action. An input surface on a passively-regenerated artifact is inherently deferred — and it must say so, or deferral is read as failure.
Never auto-close on age. This is the single most-criticized behavior in the survey. The signal argument (fvsch.com/stale-bots, Feb 2023) is that auto-closing destroys the queue's meaning: "Closed" stops distinguishing fixed from rejected from arbitrarily hidden, and closed items drop out of default search, so users "will not find that identical issue which was auto-closed" and file duplicates. Sentiment is genuinely split — maintainers defend it on capacity grounds — but capacity is not the constraint for a single operator. Escalate presentation only.
A silent cap makes a queue look shorter than it is. actions/stale's own
README warns that with operations-per-run: 30 "you might end up with unprocessed
issues or pull requests after a stale action run". Any cap must be stated in the
summary, never silent.
Maintainers' own defaults are not the shipped defaults. Renovate recommends
config:best-practices over config:recommended, and the Dependency Dashboard
option itself defaults to false while the onboarding preset turns it on.
Nobody has solved this for agents. avivsinai/agent-message-queue (~76
stars) is a genuine close analogue — Maildir tmp→new→cur semantics, atomic
rename, no daemon by default — and its awareness story is still "poll, or run a
monitor, or use an experimental terminal-injection wake hack". It does not
solve the monitoring problem either. Claude Code's own agent messaging is
in-session, not cross-session. LangChain's Agent Inbox is server-backed. No
surveyed project ships a daemon-free mechanism that makes an unopened
repository's backlog visible from elsewhere. The strongest transferable idea
came from outside the space entirely: gh status.
Ranked shortlist — cheapest first
Each passes the test: computable at read time, from what is already on disk, with no daemon and no clock of its own.
- Surface every mailbox at every session start, not just the current
repository's. (
gh status.) The only item that attacks the stated complaint head-on — an unopened repository is invisible precisely because injection is repo-scoped. The enumerate-all logic already exists; what changes is where its output appears. Must use a plain file count, never the consuming read path, and must exclude_broadcast. - One living summary at a fixed path, regenerated on every read. Three independent tools converge on this. Address it by deterministic path, never by search.
- Seen and done as two independent states. Without it the pending count becomes a lie the moment something is read without being acted on — and the count is the whole basis of item 1. Default view shows un-acted-on only.
- Explicit defer-until-timestamp that resurfaces itself. The self-returning version of "leaving one pending". Copy two constraints: cap the deferral, and permit it only on items already acknowledged — which is why item 3 lands first.
- Age computed at read time, escalating presentation, automatic de-escalation. An adaptation — no surveyed tool ships this daemon-free. Take the state machine and both guardrails (any touch clears the escalation; exemptions never age). Do not take auto-close.
Explicitly not recommended: hourly rate caps, scheduled digest windows, and auto-close — the first two need a clock this system does not have, the third is the most-criticized behavior in the survey.
Sources
Renovate: configuration options, schema, dependency-dashboard.ts, presets, best practices, #12131, #9905 · Dependabot: options reference, pull requests, auto-triage · GitHub: managing notifications, REST notifications, gh status · actions/stale · fvsch: don't use stale bots · PagerDuty snooze · Watchman trigger · Syncthing events · sticky-pull-request-comment · changesets/action · agent-message-queue
Not verified — do not build on
prHourlyLimit's default (two extractions of one page said 10, no second
source); the vulnerabilityAlerts object's default; what Dependabot does when a
"dismiss until a patch is available" patch actually lands (absent from both pages
checked); whether a GitHub notification marked Done returns to the inbox on new
thread activity — two pages checked, neither states it, so do not assume
resurface-on-activity. probot/no-response is archived (2021-07-13) and
deprecated by its owner. langchain-ai/agent-inbox was search-level only, not
fetched.