aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ai/notes.org2
-rw-r--r--.ai/sessions/2026-08-19-15-08-agent-text-relay-fix-and-pager-outage.org294
-rw-r--r--inbox/lint-followups.org32
-rw-r--r--todo.org337
4 files changed, 648 insertions, 17 deletions
diff --git a/.ai/notes.org b/.ai/notes.org
index be6708c..734dd1c 100644
--- a/.ai/notes.org
+++ b/.ai/notes.org
@@ -85,6 +85,6 @@ Format:
Markers maintained by workflows to record when they last ran. Read by other workflows that gate their behavior on freshness.
:LAST_AUDIT: 2026-07-20 (open set current — this session's shipped work (working/temp, triage-source-activation, silent-until-signal, suspend detach) closed as it went; sentry cluster consolidated (merged the /schedule tasks, added cross-host-coordination); nothing shipped-but-open per git reconcile. Live finding: the Polyglot + Subprojects scouting tasks are SCHEDULED 2026-07-20 and due.)
-:LAST_INBOX_PROCESS: 2026-08-04 (ten handoffs, batch-approved: four implemented — signature paths, lint-org anchor scope, gmail pagination floor, telega 404 callback; two folded — work's link sweep as independent confirmation, KB orphans 42 → 113; four filed — [#B] teardown live-sibling gate, [#B] agent-scoped anchor default, [#C] install-lang track-mode ignores, [#C] hook message names a step not a command; all four senders replied to)
+:LAST_INBOX_PROCESS: 2026-08-19 (14 items, 4 real: ten byte-identical kb-hygiene reports deleted as script-sourced and the duplicate delivery filed [#C]; agent-text relay fix parked as a [#B] VERIFY with the prepared diff in working/agent-text-relay-fallback/ — archsetup's version taken over .emacs.d's competing one for its self-relay guard, bug confirmed live on velox; settings.json /model churn filed [#B]; ratio-only bats failures filed [#C] after the suite came back green here; inbox-send's inability to reach ~/.emacs.d found while replying and filed [#C]. Both senders replied to.)
Format: one =:MARKER: YYYY-MM-DD= line per workflow. Workflows overwrite their own marker on completion.
diff --git a/.ai/sessions/2026-08-19-15-08-agent-text-relay-fix-and-pager-outage.org b/.ai/sessions/2026-08-19-15-08-agent-text-relay-fix-and-pager-outage.org
new file mode 100644
index 0000000..0599d90
--- /dev/null
+++ b/.ai/sessions/2026-08-19-15-08-agent-text-relay-fix-and-pager-outage.org
@@ -0,0 +1,294 @@
+#+TITLE: Session Context — 2026-08-19
+#+AUTHOR: Craig Jennings
+
+* Summary
+
+** Active Goal
+
+Startup found fourteen inbox items and a blocked rulesets pull. The session
+became one long inbox pass plus the shared-asset change it surfaced: fix
+=agent-text=, whose hardcoded relay target had died with velox's registration.
+
+** Decisions
+
+- *Took archsetup's proposal over .emacs.d's* for the same bug. Both replaced
+ the hardcoded host with an ordered list; only archsetup's skips a candidate
+ matching =uname -n=. .emacs.d's works on velox by accident of listing ratio
+ first, so it repairs the instance and leaves the defect reachable.
+- *Measured rather than reasoned, three times, and it paid every time.*
+ .emacs.d's rc-0 worry, archsetup's escalation of it into a blocking =[#B]=,
+ and my own claim about =signal-receive.sh= no-opping cleanly all dissolved or
+ narrowed under one command.
+- *Fixed the runbook rather than deferring it.* Correcting =protocols.org= while
+ leaving the document it points at asserting the dead topology is worse than
+ not starting, because the contradiction reads as sources disagreeing.
+- *Removed ratio's stale signal-cli symlink* rather than pinning the unit's
+ path, so the packaged 0.14.7 wins for every caller instead of just the timer.
+ Left the 107M install tree — reclaiming it is Craig's call.
+- *Committed the =/model= pin as-is* on Craig's ruling. It records a deliberate
+ choice (Opus 5 with the 1M-context variant, replacing a floating alias) and
+ clears the blocked pull, but does not fix the write-target problem.
+
+** Data Collected / Findings
+
+*The away channel was broken in two independent ways, and both reported success.*
+=agent-text= relayed velox to velox after the reinstall. Separately, ratio's
+=signal-receive= timer had been exiting =status=0/SUCCESS= while receiving
+nothing, because =~/.local/bin/signal-cli= (0.14.5, manual, 2026-06-12) shadowed
+the packaged 0.14.7 and refused on an upgraded database. Removing the symlink
+fixed it; the service then drained real queued envelopes including a receipt
+from Craig dated 2026-08-16.
+
+*I mis-attributed a number in my own task and corrected it.* I wrote that the
+pager account had not received in 17 days. That staleness belonged to the
+*personal* account =+15103169357=, proven by draining it and watching the
+warning clear. =listAccounts= prints the warning above the account list without
+naming which account it concerns.
+
+*The isolated reviewer earned the gate three rounds running.* It found that my
+self-skip guard compared a domain-stripped candidate against an unstripped
+=uname -n=, so an FQDN nodename silently restores the original bug — the same
+"fixes the instance, not the defect" flaw I had written into the decision record
+as my reason for rejecting the other proposal. My tests could not catch it
+either: the =uname= stub only fed short names, so I had tested the direction
+that already worked. It then caught my SUPERSEDED banner vouching for four
+screens of runbook I had not read. Both are errors no self-review reaches.
+
+*It also found the live outage above*, while verifying a claim I had made in
+prose, and reported its own isolation-run artifact rather than only the
+corrected conclusion.
+
+*One failure shape, four instances.* A check that cannot distinguish "nothing to
+do" from "could not do it" reports success either way:
+=signal-receive.sh='s guard, =triage-intake='s missing ACTIONS-UNAVAILABLE
+outcome, the reviewer's bats variant going green at exit 127, and the
+=gh auth status= precondition passing on a work credential.
+
+*Confirmed home's two triage defects rather than trusting them*, and defect 1 is
+worse here: velox's only configured GitHub host *is* =deepsat.ghe.com=, so a
+personal sweep would return entirely work PRs.
+
+** Files Modified
+
+- =62a7fbf= =fix(agent-text): relay through a host that holds the account= — the
+ script, its suite (5 tests → 11, mutation-proven), =protocols.org= + mirror,
+ the pager runbook, =signal-receive.sh='s header, and a decision record.
+- =.claude/settings.json= — the =/model= pin, committed separately.
+- =todo.org= — nine tasks filed, two closed.
+- ratio (no commit): removed the stale =~/.local/bin/signal-cli= symlink.
+
+KB: promoted 2 / consulted yes
+
+** Next Steps
+
+1. *=[#A]= github-prs returns work PRs into personal triage.* Pin both the
+ precondition (=--hostname github.com=) and the scan (=GH_HOST=). Safe
+ overnight only because it fires on a triage sweep — don't run one first.
+2. *Fix the guard class, not three guards.* =signal-receive.sh= =[#B]= and
+ =triage-intake= =[#B]= are the same bug, and a sweep of =scripts/= for
+ =2>/dev/null= in front of a decision would find the rest. This is the highest
+ -value item on the list.
+3. *=[#B]= the settings.json write target* — today's commit cleared the tree
+ without fixing why it dirties. Both options are in the task, unchosen.
+4. Craig's two standing reminders are untouched and both need his decisions: the
+ context-engineering rightsizing and the sentry spec read, now five weeks old.
+5. Nine unstamped =[#D]= tasks want a real task-review pass, not a sweep — the
+ staleness count is 40.
+
+* Session Log
+
+** 2026-08-19 Wed @ 12:59 — Startup
+
+Ran the startup workflow. Phase A.0 pulled nothing: the rulesets pull was
+blocked by an unstaged =.claude/settings.json= (=model= flipped from =opus= to
+=claude-opus-5[1m]=), which is the eleventh instance of the churn .emacs.d
+handed off on 2026-08-14. =make install= had nothing new to link; the project
+repo was already current.
+
+Phase A found 14 inbox items, 40 top-level tasks unreviewed past a week, 6 roam
+inbox items (none rulesets'), and a clean host-identity/spec-sort probe. No
+crashed-session anchor — the last session wrapped cleanly.
+
+Verified during Phase C prep, on velox (=uname -n=):
+
+- velox's =signal-cli listAccounts= is empty, so the =agent-text= relay bug the
+ two senders reported is live on this machine right now — a "text me" from
+ this session would relay to velox itself and fail.
+- =bats= IS present here (=/usr/bin/bats=), so .emacs.d's 2026-08-14 "bats
+ absent on velox" finding is stale.
+- The ten =kb-hygiene-report.org= inbox files are byte-identical (same md5), so
+ that is duplicate delivery, not ten distinct reports.
+
+** 2026-08-19 Wed @ 13:05 — Inbox processed to zero
+
+The Stop hook caught me presenting dispositions instead of applying them, which
+was the right catch: startup Phase C says inbox processing is mandatory, not a
+thing to ask about. Ran it properly.
+
+Ten kb-hygiene reports deleted as script-sourced, per the disposition ladder.
+They were byte-identical, and their content (142 agent nodes, ~114 orphans) is
+already carried by the =[#C]= KB orphan-node task from 2026-08-04. The
+duplication itself is the finding, so it got its own =[#C]=: =kb-hygiene.sh=
+names each report by timestamp, so nothing dedups, and whatever drives it ran
+it ten times in four hours.
+
+The two agent-text handoffs are one item from two senders. archsetup's finished
+script beats .emacs.d's diff on one point, and it is the point: the self-relay
+guard. .emacs.d replaced the hardcoded host with an ordered list but never
+skipped self, so their fix works on velox only because ratio happens to lead
+the list. Parked as a =[#B]= VERIFY rather than applied, because agent-text is
+a synced shared asset — prepared diff, both source proposals, and the written
+skeptical review are in =working/agent-text-relay-fallback/=. The proposed file
+is =bash -n= and =shellcheck -S warning= clean. Left as an apply condition: the
+reachable-but-unregistered relay case that neither sender tested, where a
+zero exit from =signal-cli send= would make the loop report success for a
+message that went nowhere.
+
+Filed =[#B]= for the settings.json =/model= churn (eleventh flip, nothing
+tracked before now, both of .emacs.d's options recorded unchosen since it is
+Craig's call). Filed =[#C]= for the ratio bats failures after running the suite
+here and getting 36 green — so it is ratio-local, not a code defect, and
+.emacs.d's fleet-wide framing was too broad.
+
+Found a new bug while replying: =inbox-send= discovers only =~/projects= and
+=~/code=, so =~/.emacs.d= is unreachable by name despite the dot-stripped
+resolution existing in the script. =.emacs.d= can send to rulesets but rulesets
+cannot answer it. Worked around with =INBOX_SEND_ROOTS= to get the reply out,
+and filed =[#C]=.
+
+Both senders replied to. Inbox at zero.
+
+** 2026-08-19 Wed @ 14:35 — Applied the agent-text fix; refuted archsetup's follow-up
+
+Craig approved the parked fix. Applied archsetup's script to the canonical,
+=bash -n= and =shellcheck= clean.
+
+Answered the open question first, because it gated the apply: on velox — which
+is itself the reachable-but-unregistered case — =signal-cli send= exits 1 with
+"User +15045173983 is not registered." So the loop advances rather than
+reporting a phantom delivery.
+
+Rewrote the bats suite, 5 tests → 10. The important change is that it now stubs
+=uname=, so the self-skip is exercised deterministically instead of depending on
+which machine runs the suite; the old test 2 asserted on velox by name and would
+have passed or failed by host. Mutation-proved the suite rather than assuming
+it: deleting the self-relay guard reds tests 3, 4 and 9; an unconditional
+=break= reds 5; seeding =rc=0= reds 9. Three mutations, three distinct catches.
+
+Rewrote protocols.org "Reaching Craig". It asserted the account is "registered
+on velox (primary) and ratio (linked device)" — the exact fact velox's rebuild
+destroyed. Replaced the snapshot with the derivation (=signal-cli listAccounts=)
+per the host-identity rule, since a fresh snapshot would rot at the next
+rebuild. Its =signal-mcp= claim was false too: velox's =~/.claude.json= now
+holds only linear and slack-deepsat.
+
+Green baseline was not green — =ai-launcher-runtime.bats= test 8 fails here
+because it asserts on =codex= being installed and codex did not survive the
+reinstall. Triaged and filed =[#C]= rather than fixed, so the bar for this work
+is "only that one remains". It is the same class as the ratio failures: a
+characterization test reading the host instead of a fixture.
+
+*Refuted archsetup's follow-up.* They replied asserting the rc-0 case is a real
+defect in what they sent — that an unregistered relay exits zero, so the loop
+breaks and reports success. That is inference and it is wrong. Measured it two
+ways: ssh propagates a remote exit code faithfully (=exit 7= came back as 7),
+and =signal-cli= on ratio for an account it does not hold exits 1 with "User
+... is not registered". Used a bogus number for the remote probe so nothing
+could reach Craig by accident. Their =:blocked:= tag rests on a premise that
+does not hold, so the reply has to carry the measurement, not an opinion.
+
+** 2026-08-19 Wed @ 14:36 — archsetup confirmed the refutation
+
+archsetup reproduced the measurement independently rather than taking my word
+for it, then closed their =[#B]=, dropped the =:blocked:= tag, and named the
+process failure themselves: my handoff had stated .emacs.d's concern as a
+conditional ("if =signal-cli send= exits zero against an empty account store"),
+and they converted that "if" into a graded defect with a blocking tag on
+another project without running the one command that settles it — on the
+machine that was the exact case.
+
+Worth keeping as the general lesson, since this is twice in one session: a
+defect filed from reading code is a hypothesis. The measurement was one command
+away on the machine in front of both of us. Nothing is owed in either
+direction; the tag is gone and the dependency is clear.
+
+** 2026-08-19 Wed @ 15:05 — Review rounds 2 and 3
+
+Round 1 returned Request Changes with four Important findings. All four were
+right and I took every one without pushback.
+
+The sharpest was the self-skip guard — the thing I chose archsetup's proposal
+*for*. It compared a domain-stripped candidate against an unstripped
+=uname -n=, so an FQDN nodename silently disables it and restores the
+2026-08-13 bug on the away channel. That is the same "fixes the instance, not
+the defect" flaw I had written into the decision record as my reason for
+rejecting .emacs.d's version, one section above my own instance-dependent fix.
+My test suite could not catch it either: the =uname= stub I added only ever fed
+short names, so I had tested the direction that already worked.
+
+Also fixed: the failure message named the relay list on both branches (so a
+local failure on the only working sender would send a debugger chasing the
+tailnet); the header's 2026-08-16 end-to-end claim was archsetup's, not mine,
+and is now attributed on-report; and =protocols.org= pointed readers at a
+runbook still asserting the retired topology, which is worse than not starting.
+
+Round 2 closed all four and found one more, and it is the one worth recording.
+My SUPERSEDED banner on the runbook said "the operational recipes further down
+still hold — only the topology section rotted." I had read the first 32 lines
+of that file. Four sites below contradicted the vouch, including both
+reply-drain recipes and a copy-pasteable =ssh velox= relay sitting under "reach
+for the raw form when debugging". A wrong vouch is worse than no vouch: it
+converts a reader's suspicion into confidence.
+
+Round 3 takes the stronger fix — the clause is gone and five dated notes sit
+inline at each stale site, so nobody has to carry the banner four screens.
+
+The reviewer also ran a mutation I had not asked for, on a hazard my own fix
+introduced: swapping =rc=$?= and =why== in the direct branch so =rc= captures
+the assignment. Test 10 catches it, but incidentally — via the =why= text
+rather than any assertion about =rc=. I asked whether that deserves a test that
+fails for the right reason instead of relying on the incidental catch.
+
+Three times today a confident claim dissolved on inspection: .emacs.d's rc-0
+worry, archsetup's escalation of it, and my own guard. I measured the first two.
+The third I would have shipped.
+
+*Loop bound:* this is round 3 of the three the publish flow allows. Another
+Request Changes stops the loop and goes to Craig rather than spending a fourth.
+
+** 2026-08-19 Wed @ 15:20 — Review approved; residuals closed
+
+Round 3 came back *Approve*. The reviewer also settled my test-10 question by
+isolation rather than by reading: it split the test into two variants, one
+keeping only the exit-code assertion and one keeping only the text assertions,
+and ran each against the mutated script. Both red independently, so
+=[ "$status" -ne 0 ]= is a first-class direct-branch exit-propagation assertion
+and not an incidental catch. No extra test needed — it would restate one that
+already exists.
+
+The valuable part is that its *first* isolation run gave the opposite answer,
+and it reported that rather than just the corrected conclusion. It had written
+the variant bats files into a scratch directory, so =setup()='s =REPO_ROOT=
+walked up from the wrong place and =$PAGE= pointed at a file that does not
+exist. bash exited 127, and 127 satisfies =-ne 0=, so the variant went green
+while never executing the script under test.
+
+*KB promotion candidate (update, not a new node).* That is the
+already-recorded "A check that cannot fail proves nothing" class
+(=20260808040531=), but it sharpens the node's own remedy. The node says
+asserting =status= alongside output is what makes absence register — today a
+=status= assertion was itself satisfied by absence, because 127 clears
+=-ne 0=. The refinement: a *negative* assertion needs a positive control, or
+infrastructure failure reads as the behavior under test. Append to that node at
+wrap rather than writing a duplicate.
+
+Closed the four residuals the approval left open rather than filing them, since
+I was already in the file: the "Linking a device" recipe is now marked as
+currently impossible (the primary's keys are gone, a linked device cannot
+authorize another) with the re-registration tradeoff and a pointer to the
+decision record; plus the three wording minors. Sent back as a confirm-only
+pass, because the approval predates those edits and I would rather not commit a
+diff the reviewer has not seen. Flagged two of my own claims in it for checking
+— that =signal-receive.sh= no-ops cleanly without the account, and that no
+machine can currently run =addDevice= — since both are reasoned rather than
+run, which is the error this session keeps making.
diff --git a/inbox/lint-followups.org b/inbox/lint-followups.org
index b140944..9ec6c04 100644
--- a/inbox/lint-followups.org
+++ b/inbox/lint-followups.org
@@ -1,18 +1,18 @@
* 2026-07-20 Mon — Task-review health: 1 top-level [#A]/[#B]/[#C] tasks unreviewed for >30 days (daily review may have slipped)
-* lint-org follow-ups — todo.org (2026-08-05)
-** TODO misplaced-heading — Possibly misplaced heading line (line 2593)
-** TODO link-to-local-file — Link to non-existent local file "working/hook-fail-open/validate-el.diff" (line 2569)
-** TODO misplaced-planning-info — Misplaced planning info line (line 2558)
-** TODO link-to-local-file — Link to non-existent local file "working/hook-fail-open/pre-commit.diff" (line 2553)
-** TODO misplaced-planning-info — Misplaced planning info line (line 2538)
-** TODO org-table-standard — table violates the org-table standard: no closing rule; missing rule between rows — wrap-org-table.el reflows it (line 607)
-** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 786)
-** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 789)
-** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 796)
-** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 804)
-** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 807)
-** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 816)
-** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 930)
-** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 1089)
-** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 1098)
+* lint-org follow-ups — todo.org (2026-08-19)
+** TODO misplaced-heading — Possibly misplaced heading line (line 2926)
+** TODO link-to-local-file — Link to non-existent local file "working/hook-fail-open/validate-el.diff" (line 2902)
+** TODO misplaced-planning-info — Misplaced planning info line (line 2891)
+** TODO link-to-local-file — Link to non-existent local file "working/hook-fail-open/pre-commit.diff" (line 2886)
+** TODO misplaced-planning-info — Misplaced planning info line (line 2871)
+** TODO org-table-standard — table violates the org-table standard: no closing rule; missing rule between rows — wrap-org-table.el reflows it (line 940)
+** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 1119)
+** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 1122)
+** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 1129)
+** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 1137)
+** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 1140)
+** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 1149)
+** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 1263)
+** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 1422)
+** TODO task-missing-last-reviewed — task has no :LAST_REVIEWED: — stamp it at creation with today's date (a task you just wrote and graded is reviewed); otherwise it enters the next staleness batch as never-reviewed (line 1431)
diff --git a/todo.org b/todo.org
index 3213a6f..07e2ad6 100644
--- a/todo.org
+++ b/todo.org
@@ -39,6 +39,343 @@ Tags are assigned and refreshed by =task-audit=; =task-review= keeps them honest
* Rulesets Open Work
+** TODO [#A] github-prs plugin returns work PRs into personal triage :bug:
+SCHEDULED: <2026-08-19 Wed>
+:PROPERTIES:
+:LAST_REVIEWED: 2026-08-19
+:END:
+=triage-intake.github-prs.org:15= gates on =command -v gh && gh auth status=.
+=gh auth status= exits 0 when *any* configured host authenticates, including a
+GitHub Enterprise host, so the precondition can pass on the strength of the work
+credential alone. The scan then runs =gh search prs --author=@me=, which resolves
+against the enterprise host and returns work PRs into a personal project's
+triage. home hit this today and discarded a =deepsat/sbir-pleo-d2p2-submission=
+PR by hand.
+
+Reproduced on velox:
+
+: gh auth status rc 0
+: gh auth status --hostname github.com rc 1
+: gh auth status --hostname deepsat.ghe.com rc 0
+
+So the precondition passes with no *usable* github.com credential, and every
+result would be work rather than merely some.
+
+home corrected one detail I had wrong. I first read this as github.com not being
+configured at all; the entry does exist, its token is simply invalid
+(=GH_HOST=github.com gh auth status= prints "The token in default is invalid").
+Same functional state and the same fix, but the entry's existence is what makes
+the failure quiet — gh knows the host, so nothing in the config looks absent.
+
+Fix needs both halves. Pin the precondition
+(=gh auth status --hostname github.com=) and pin the scan, because =gh search
+prs= has no =--hostname= flag — =GH_HOST=github.com= is the lever. The
+precondition alone only caught today's case because github.com's token happens
+to be broken; with two valid hosts it would pass and the scan would still be
+ambiguous.
+
+The plugin's own header says a work project on GHE declares its own plugin and
+does not edit this one, so github.com-only is the stated design. The defect is
+that nothing enforced it.
+
+*Generalize before closing:* an =:ENABLED:= test proving *a* credential exists is
+not the same as proving the *right* one does. Every plugin whose tool can address
+more than one account has this shape — audit the others rather than fixing this
+one in isolation.
+
+Grading: severity-alone carve-out. This is a confidentiality boundary violation —
+work material surfacing in a personal project, in a system whose boundary
+discipline exists to keep them apart — and it presents identically to a healthy
+scan. One occurrence with the wrong PR in it is a showstopper regardless of
+frequency, so Critical = P1 = =[#A]=. Source: home handoff 2026-08-19.
+
+** TODO [#B] triage-intake reports a clean close over an unactionable backlog :bug:
+:PROPERTIES:
+:LAST_REVIEWED: 2026-08-19
+:END:
+Two levels, and the engine half is the one that matters.
+
+*Plugin:* =triage-intake.personal-gmail.org:11= gates on the
+=google-docs-personal= MCP, and all three hygiene verbs (lines 73-75: trash,
+mark-read, star+read) are MCP-only. When that MCP is absent the source goes
+inactive and the sweep announces a skip — going blind on the highest-volume
+personal channel, which held 873 unread when home looked. The other Gmail MCP on
+offer is work-bound (there is a recorded 2026-07-23 incident where a pass used it
+expecting personal and got 201 unread work messages), so it is not a substitute.
+Confirmed absent from this velox session too.
+
+home worked around the *read* half by scanning the maildir through =mu=, which
+their notes already name as the reliable path when an MCP binding is in doubt —
+but the plugin does not sanction it, so that was their substitution rather than
+the workflow's. Fix: declare the mu/maildir fallback as a real scan tier and give
+it action verbs. =cmail-action.py= is the working model (local script over the
+maildir, with mark-read/star/trash); a =gmail-action.py= sibling closes it, and
+mbsync already syncs the maildir.
+
+*Engine, and this is the durable half:* =triage-intake.org= Phase D says hygiene
+runs on every scanned account. It has no concept of a source that was *scanned*
+but whose action verbs are unavailable, so the sweep classifies everything and
+acts on nothing while reporting a clean close. Today that gap surfaced only
+because home wrote a prose line about it. It should be a first-class close
+outcome alongside =SCAN FAILED= — e.g. =ACTIONS UNAVAILABLE: <source> — scanned
+via <fallback>, hygiene not run, N items left unprocessed=.
+
+*This is the third instance today of one failure shape:* a mechanism that cannot
+distinguish "nothing to do" from "could not do it" reports success either way.
+The others are the pager receive timer (=[#A]= above) and the reviewer's own
+bats variant that went green at exit 127. Worth fixing as a class, not three
+times.
+
+Grading: Major severity (the sweep reads but cannot act, and says nothing, so the
+backlog only grows and every later sweep re-reads it) x most sweeps, frequently =
+P2 = =[#B]=. Source: home handoff 2026-08-19.
+
+** DONE [#A] Pager receive timer was dead on ratio and reported success :bug:
+CLOSED: [2026-08-19 Wed]
+:PROPERTIES:
+:LAST_REVIEWED: 2026-08-19
+:END:
+Fixed and verified. =signal-receive.service= on ratio had been exiting
+=status=0/SUCCESS= every 15 minutes while receiving nothing.
+
+Root cause was a PATH version skew. ratio carried two signal-cli installs:
+=~/.local/bin/signal-cli= symlinked to a manual 0.14.5 tree (2026-06-12) and
+=/usr/bin/signal-cli= at 0.14.7 from pacman. The systemd user manager puts
+=~/.local/bin= first, so the timer ran 0.14.5 against a database 0.14.7 had
+already upgraded and signal-cli refused with "Database has been updated by a
+newer signal-cli version". An interactive shell resolved =/usr/bin=, which is
+why it worked by hand and failed under the unit.
+
+Fix: removed the stale =~/.local/bin/signal-cli= symlink, so the pacman-managed
+0.14.7 wins for every caller rather than just the timer. Nothing else referenced
+the 0.14.5 tree. Left the 107M install directory in place — reclaiming it is a
+separate call.
+
+Verified after: the service drained real queued envelopes, including a receipt
+from Craig timestamped 2026-08-16 that had been sitting undelivered, and exited
+=Result=success=. Timer still armed on its 15-minute cadence.
+
+*Correction to this task's original claim.* I first wrote that the pager account
+had not received in 17 days. That number was wrong — the 17-day staleness
+warning belonged to the *personal* account =+15103169357=, proven by draining it
+and watching the warning clear. The pager's failure was real and separately
+established (the database-version error under the unit, plus the 2026-08-16
+envelope still queued), but the 17-day figure was never its. I had attributed
+one account's number to the other because =listAccounts= emits the warning once,
+above the account list, without naming which account it is about.
+
+*Still open, and pre-existing:* nothing keeps the personal account warm.
+=signal-receive.sh= hardcodes the pager account and takes no others, so
+=+15103169357= goes stale until drained by hand — already filed as part of the
+2026-07-23 signal findings, and unchanged by today's fix.
+
+*The durable half is not done and is filed separately below:* the guard in
+=signal-receive.sh= still cannot tell "the account is not here" from
+"listAccounts failed", which is the only reason this ran undetected. Fixing the
+skew without fixing the guard leaves the next failure just as silent.
+
+** TODO [#B] signal-receive guard cannot tell absence from failure :bug:
+:PROPERTIES:
+:LAST_REVIEWED: 2026-08-19
+:END:
+=scripts/signal-receive.sh= guards with:
+
+: if ! signal-cli listAccounts 2>/dev/null | grep -q "$account"; then
+
+The redirect discards any error and =grep= finds nothing either way, so a hard
+failure is indistinguishable from a genuine absence. Both produce the benign
+"not registered on this machine — nothing to do" and a clean exit 0.
+
+That is what let the ratio PATH skew (=[#A]= above) run 17 days undetected while
+reporting success every 15 minutes. The skew is fixed; this is not, so the next
+failure of any kind on that path is equally silent.
+
+Fix: capture =listAccounts= output and its exit code separately. A non-zero exit
+is a failure and should exit non-zero with the error surfaced, never the
+nothing-to-do path. Only a clean run that genuinely lacks the account takes the
+no-op branch.
+
+*Same shape as two other things found today*, which is the argument for treating
+it as a class rather than one script's bug: =triage-intake= reports a clean close
+over a backlog it could not act on (=[#B]= above), and an isolated reviewer's own
+test variant went green because the path under test did not exist and exit 127
+satisfies a =-ne 0= check. A check that cannot distinguish "nothing to do" from
+"could not do it" reports success either way. Worth a sweep for the pattern
+across =scripts/= rather than three separate fixes.
+
+Grading: Major severity (it masks arbitrary failures on a channel whose job is
+reaching Craig when he is away, and masking is worse than failing) x every
+occurrence on that path = P2 = =[#B]=. Not =[#A]= because the live outage it hid
+is now closed.
+
+** TODO [#C] ai-launcher runtime test depends on codex being installed :bug:
+:PROPERTIES:
+:LAST_REVIEWED: 2026-08-19
+:END:
+=scripts/tests/ai-launcher-runtime.bats= test 8 ("--print-runtimes lists claude,
+codex, and one line per ollama model") fails on velox, so =make test= exits 1
+here. Found as the green-baseline run before the agent-text work, and triaged
+rather than fixed so that work could proceed against a known bar.
+
+The test stubs =ollama= but not =codex=, so it asserts on whatever the host
+really has. codex is absent on velox after the 2026-08-13 reinstall, and the
+assertion =[[ "$output" == *"codex"* ]]= fails. The launcher is behaving
+correctly — it lists the runtimes that exist.
+
+This is a test-isolation defect, the same shape as the three ratio-local
+launcher failures filed alongside it: a characterization test that reads the
+host instead of a fixture will pass or fail on where it runs rather than on
+what the code does. Fix by stubbing =codex= on PATH the way =ollama= already
+is, so the test asserts on the launcher's logic.
+
+Two things it is worth not confusing. codex being missing on velox is a real
+reinstall casualty and may be worth reinstalling on its own merits, but that
+would only paper over the test. And the ratio failures are a separate task —
+different tests, different machine, likely different cause.
+
+Grading: Major severity (a red suite makes the green baseline unreadable on the
+affected machine, so every later regression check there is guesswork) x some
+machines sometimes = P3 = =[#C]=.
+
+** TODO [#C] inbox-send cannot reach ~/.emacs.d or ~/.dotfiles :bug:
+:PROPERTIES:
+:LAST_REVIEWED: 2026-08-19
+:END:
+=inbox-send= discovers projects under =~/projects= and =~/code= only
+(=DEFAULT_ROOTS=), so a project sitting directly in =$HOME= is invisible to it.
+=.emacs.d= and =.dotfiles= are both such projects and both carry =.ai/= and
+=inbox/=. So =inbox-send emacsd= fails with "unknown target" even though the
+dot-stripped name resolution it would need is already implemented and working
+(=find_target=, lines 154-175).
+
+Hit for real today: =.emacs.d= sent two handoffs, and I could not reply through
+the normal channel. Worked around with
+=INBOX_SEND_ROOTS="$HOME/projects:$HOME/code:$HOME/.emacs.d"=.
+
+The asymmetry is the defect. =.emacs.d= can send to any project, but no project
+can answer it, which breaks the reply-to-sender discipline in the inbox
+engine's core §4 — and that section's own rationale is that silent
+non-response trains a sender to escalate around the channel. The failure is at
+least loud rather than silent, and an agent that does not know the override
+just drops the reply.
+
+Fix direction: add =$HOME= itself as a scanned root for dotted project
+directories, or add =~/.emacs.d= and =~/.dotfiles= to =DEFAULT_ROOTS=
+explicitly. The docstring already claims "if the root itself is a project, it's
+included", so the intent exists; the roots list is what is short. Note
+=cross-project.md= and =triggers.md= both document =emacsd= as an addressable
+name, so the docs already promise this works.
+
+Grading: Major severity (a protocol obligation is dropped, with a workaround
+only if you know it) x some projects sometimes = P3 = =[#C]=.
+
+** DONE [#B] agent-text relay fallback :bug:
+CLOSED: [2026-08-19 Wed]
+:PROPERTIES:
+:LAST_REVIEWED: 2026-08-19
+:END:
+Approved and applied. =agent-text= hardcoded =VELOX_HOST= as its only ssh relay
+target, and velox's 2026-08-13 reinstall wiped that machine's signal-cli
+registration — so the relay branch pointed at the one machine guaranteed to
+fail, and every machine without a local account relayed into the same dead end.
+
+Took archsetup's proposal over .emacs.d's competing one for the same bug. Both
+replaced the constant with an ordered list; only archsetup's skips a candidate
+whose short name matches =uname -n=, which is the actual defect. .emacs.d's
+works on velox by accident of listing ratio first, so it repairs the instance
+and leaves the defect reachable.
+
+Answered the open question neither sender had tested — a relay host reachable
+but unregistered — by running it on velox, which is that case: =signal-cli
+send= exits 1 with "User +15045173983 is not registered", so the loop advances
+instead of reporting a delivery that never happened. Recorded in protocols.org.
+
+Also corrected =protocols.org= "Reaching Craig", which asserted the same dead
+fact (account "registered on velox (primary) and ratio (linked device)"). Now
+states how to derive it rather than a snapshot, per the host-identity rule —
+the snapshot is what rotted, and a fresh one would rot at the next rebuild. Its
+=signal-mcp= claim was false too: velox's =~/.claude.json= holds only =linear=
+and =slack-deepsat= after the reinstall.
+
+Tests 5 → 10, and the suite now stubs =uname= so the self-skip is
+deterministic rather than dependent on the machine running it. Mutation-proven:
+removing the guard reds 3 tests, an unconditional =break= reds 1, seeding
+=rc=0= reds 1. Decision record:
+[[file:docs/design/2026-08-19-agent-text-relay-fallback-decision.org]].
+
+** TODO [#B] /model writes into the tracked settings.json and blocks the pull :bug:
+:PROPERTIES:
+:LAST_REVIEWED: 2026-08-19
+:END:
+=~/.claude/settings.json= is a symlink into this checkout, and Claude Code
+saves a =/model= or effort change by writing user settings. So every runtime
+model change writes straight into a tracked file, dirties the tree, and blocks
+=git pull --ff-only= at every machine's session start. It blocked this
+session's rulesets pull.
+
+Not a one-off: =git log= on =.claude/settings.json= shows ten prior commits
+flipping the =model= line (=c6bd31f=, =5feaa94=, =e3d3c1c=, =c976f5b=,
+=73835a2=, =d5bc9b3=, =e91073d=, =bd76d98=, =3a5292c=, =ada0e6d=), and
+=effortLevel= has begun doing the same. Treat it as a property of the file, not
+of one key.
+
+Two options, from .emacs.d's handoff (2026-08-14), neither yet chosen:
+1. Stop installing the tracked file as user settings — rulesets holds a
+ canonical template, install copies or merges it, runtime writes land on the
+ machine's own file. Cost: canonical updates stop propagating automatically.
+2. =git update-index --assume-unchanged= per machine. Cheap, but it hides
+ genuine canonical edits too and must be reapplied on every fresh clone,
+ which is exactly the manual step that silently drifts.
+
+Grading: Major severity (recurring startup blocker, no clean workaround) x most
+machines frequently = P2 = =[#B]=.
+
+** TODO [#C] kb-hygiene ships the same report on every run :bug:
+:PROPERTIES:
+:LAST_REVIEWED: 2026-08-19
+:END:
+Ten =kb-hygiene-report.org= files arrived in =inbox/= between 09:15 and 12:56
+on 2026-08-19, all byte-identical (one md5 across all ten). So whatever drives
+=scripts/kb-hygiene.sh= re-ran it ten times in four hours and delivered the
+same output each time. =kb-hygiene.sh= names its report by timestamp
+(=$(date +%Y-%m-%d-%H%M)-kb-hygiene-report.org=), so every run is a new file
+and nothing dedups.
+
+The content was also stale news — 142 agent nodes and ~114 orphans, which the
+=[#C]= KB orphan-node review task already records from 2026-08-04. Deleted all
+ten as script-sourced.
+
+Fix direction: dedup on content before delivering (skip when the new report
+matches the last one), or have the caller deliver on change only. Find the
+caller first — the repeat cadence is the actual bug and it is not in
+=kb-hygiene.sh=.
+
+Grading: Minor severity (inbox noise, wasted processing) x most runs frequently
+= P3 = =[#C]=.
+
+** TODO [#C] Three launcher characterization tests fail on ratio only :bug:
+:PROPERTIES:
+:LAST_REVIEWED: 2026-08-19
+:END:
+.emacs.d reported (2026-08-14) that =scripts/tests/ai-launcher-characterization.bats=
+fails three functional tests on ratio, so =make test= exits 1 there:
+=find_window_id=, =sort_windows=, =attach_mode=. They confirmed the failures
+are pre-existing rather than caused by their two commits, by stashing and
+re-running against a pristine tree.
+
+I re-ran the same suite on velox today: 36 tests, all green, including exactly
+those three. So this is environment-dependent and ratio-local, not a code
+defect that reproduces fleet-wide. All three are the tmux-driven functional
+tests, which points at ratio's tmux state or version rather than the launcher.
+
+Diagnose on ratio (reachable over tailscale). It matters more than the [#C]
+suggests in one respect: a red suite on ratio poisons the green-baseline
+discipline, so every later "did I break this?" check there is unreadable until
+it is fixed.
+
+Grading: Major severity (unreadable baseline on the affected machine) x rare
+edge case (one machine, and velox is clean) = P3 = =[#C]=.
+
** TODO [#B] Voice pattern #48 — corrective antithesis :feature:
:PROPERTIES:
:LAST_REVIEWED: 2026-07-31