Live tracker
Every open defect in Smith, in one place, so they can be closed instead of rediscovered. Three sources feed it: the 16-day review sweep (2026-08-11 → 08-26), the Temporal / session audit from the decision-page-markup-review session, and the fleet items nobody had picked up. Status is derived from each row's own PR link — never typed.
Complete as of 2026-09-07 — 40 rows, 39 done, 1 withdrawn on a false premise. “Done” here is the older standard: merged, not proven in prod. Its successor, Smith fail markers, picks up at 2026-08-27 and gates shipped on prod evidence.
bin/refresh-tracker agent-smith-issue-tracker reads each row's own PR and decision links live and regenerates it. Last refreshed 2026-09-16 02:58Z.The original banner here said the deploy clone ~/code/PropFlow/agent-smith was stranded on smith/url-label-keep-path at 93c2eae, blocking every auto-deploy. That is fixed (D1): verified 2026-08-28 04:20Z, the clone is on main at 76c614a, 0 ahead / 0 behind origin/main, and both daemons are loaded from it with last-exit 0.
The same class of problem is still live one layer down. The daemons declare that clone as WorkingDirectory, so its code is current — but each daemon's launchd plist is a separate file installed into ~/Library/LaunchAgents, and nothing has ever compared those to deploy/. co.propflow.smith-slack-socket is running a plist installed 2026-07-17 that predates AGENT_OWNED_CHANNELS entirely, so for ~6 weeks Smith has answered every channel it receives — logging channel-gate OFF each time. 3 of 5 loaded daemons are stale; SMITH_STATE_DIR is missing from all three, which puts them on a hardcoded state dir instead of the per-seat one.
launchctl called every one of them healthy throughout, and was right to: a daemon is loaded or it is not, and launchd has no opinion on whether its plist is current. See D5. The detector now ships in smith doctor (#339) and found more than the hand audit did; the reload restarts the live Slack socket and Temporal worker and repoints three daemons at a different state directory, so it is a decision (b87888824), not a sweep.
Five surfaces. That is not five projects; the fleet keeps its tools in separate repos by design (a hook has to live in ~/.claude, a CLI in ~/.local/bin), so the count is architecture, not sprawl. What matters is that each change is small, each has a test, and nothing is half-applied:
| Surface | Changed | Guarded by |
|---|---|---|
agent-smith repo | 6 src modules · 1 plist · testscapture · doctor · slack · slack_socket · worker · git_hygiene | PR review + full pre-push suite, every finding falsified by mutation |
~/.local/bin | 2 files — watcher-doctor, start-operator | tests/watcher-doctor-cloud-grading.py (new) |
~/.claude/hooks | 1 file — guard-bash.sh rule 14 | 16 cases in the existing matrix · 87/87 |
propflow-docs | 1 artifact — this page | status DERIVED from live PR + decision state |
| runtime (no new files) | 3 launchd plists re-rendered from the repo · 1 Temporal schedule enabled | smith doctor deploy section · schedule describe |
Nothing is left half-done for later. Every row below is either closed on evidence, or open with the reason it is not mine to close stated in the row. The two remaining rows are both parked on decisions — S4 on b88034047, H5 on b88033792 — raised 2026-08-29 rather than left sitting. Both moved on 2026-08-29 without closing: H5's decision b87721567 is now answered ("Drive all four to green now") but the jobs themselves cannot be shown green — their local logs stopped writing in July, and a silent instrument is not a passing one. S4 was re-verified against main (18ac962) rather than assumed to have drifted shut: the door is still unbuilt, and the live start_operator_task() in src is a Temporal workflow reachable only from the CLI, not the reply path.
Nothing else on this page can be verified in production until the deploy path works.
| Status | Item | Kind | Evidence |
|---|---|---|---|
| done | Put the deploy clone back on main — and save 93c2eae firstThe clone is on smith/url-label-keep-path, 5 behind / 1 ahead of main. The one commit there is unpushed with no PR, so a naive reset --hard is the one move that can lose it. Order matters: push the branch and open a PR first, then checkout main + merge --ff-only origin/main, then restart both daemons and confirm the deployed SHA. --clobber-branch exists but preserves the COMMIT, never the branch name — it is not the right tool while the work is still unpushed. | fix | ✅ Done 26 Aug 23:55Z. Gera’s 93c2eae pushed + PR #314 FIRST, then clone → main. Guard now exit 0 (was 4). Daemons restarted on 553aac0 — worker 59770, socket 59965. Ledger verified intact across the bounce (5690 rows, horizon unchanged). |
| done | Make the deploy clone unusable as a workspaceThis has now happened at least twice (the guard's own comment cites 2026-08-17). The existing guard is downstream: smith-sync-from-main.sh refuses to sync (exit 4) once the clone is off main, and then refuses forever — it catches the problem after an agent has already built there. Nothing stopped an agent choosing the clone as its cwd in the first place.Fixed 2026-08-28 as guard-bash rule 14 ( 6732031, pushed): a refusal at the moment the clone would BECOME a workspace. The allow-cases were the hard part. git -C <clone> worktree add is the CORRECT way to start work and is how every worktree on this machine is made — a rule that blocked it would have broken the standard workflow fleet-wide. So it denies only a branch SWITCH of the clone itself, and deliberately leaves alone: worktree add, checkout -- path restore (rule 7 owns that hazard), worktrees under the clone, and same-prefix siblings. Targeting resolves explicit -C → a cd in the same command → payload cwd, since a bare git checkout -b is only dangerous depending on where you are.16 cases added to hooks/tests/guard-bash-matrix.py; 85/85 pass. The matrix caught two things I had wrong: a patched variable name that did not exist, so cwd was silently never passed and the cwd-dependent cases would have passed for the wrong reason; and my expecting git restore in the clone to be an "allow" when it correctly warns via rule 7 — that case now asserts rule 7's marker, which is what proves rule 14 stayed out of a path restore. | new | guard-bash rule 14 · 6732031refuses at branch-creation, not at deploy 85/85 matrix |
| done | Retire the 09:00 ccswitch-pool-check LaunchAgentGated on the 07:00 queue posting a real 11/11 first — it did, with claude-account-pool verified as one of the eleven, so the 09:00 job was genuinely redundant rather than merely noisy. | audit | verified: launchctl list → 0 matchesplist parked in ~/code/tmp/retired-launchagents/ (moved, not deleted) |
| done | The attach guard has no test — ⚠️ THE CLAIM IS FALSEThe audit said neutering the guard "leaves all 77 green". It does not. I neutered _classify_claude_argv in a candidate copy (via the tool's own CCSWITCH_BIN seam, live file untouched) and 4 assertions red, including the behavioural one — "attach pane with a PERFECT statusline stays id-less". The guard carries 13 cases, among them the edge where a viewer's target cannot be parsed and must STILL classify as a viewer. It was flagged and then FIXED; the audit recorded only the flag. | audit | tests/ccswitch-safety.py — 13 classify casesneutered → FAIL ×4; baseline → all assertions passedaudit correction #19 |
| done | Nothing ever compared the installed plist to deploy/co.propflow.smith-slack-socket has been running a plist installed 2026-07-17 that predates AGENT_OWNED_CHANNELS entirely. The repo has the channel gate; production never got it — so for ~6 weeks Smith has answered every channel it receives, announcing it each time in a WARNING nobody reads: channel-gate OFF — AGENT_OWNED_CHANNELS unset … answers EVERY channel. launchctl reported the daemon healthy throughout, correctly: a daemon is loaded or not, and launchd has no opinion on whether its plist is current. 3 of 5 loaded daemons are stale; SMITH_STATE_DIR is missing from all three, which its own plist comment says matters because state_dir() falls back to a HARDCODED ~/.claude/smith-state. ⚠️ Found by hand, then by the detector — which found more than the hand audit did. This is the check the deploy-freshness canary (b87853466, ~2,160 expected fires / 0 actual) was supposed to be. | fix | PR #339 reload is live-infra → b87888824 falsified ×6 |
| done | A queued review run publishes no check — the PR reads green AND unreviewed#310 sat 2 days looking mergeable: mergeStateStatus=CLEAN, zero failing checks, zero comments. It had never been reviewed. Its Claude Code Review run was created at open and stayed queued for ~36h, never picked up by a runner — and a queued run contributes no check-run at all, so the rollup showed only lint-test=SUCCESS. No red, no pending, nothing outstanding: the PR looked better than a correctly-reviewed one. The tell is an absent review entry, never a failing one. Exactly one bad run in the last 60, so it is a starvation blip — but the invisibility is structural. The fleet had already given up on it: ⏱ PR-drive · #310 — EXPIRED: still open after 36h — nothing is watching it now. Cure is the draft→ready toggle; the workflow fires only on opened/ready_for_review, so a push does not re-trigger it. | fix | PR #310 toggled → 🟡 reviewed trap recorded in memory |
| done | Two lanes built the same feature four days apart — the SECOND recurrence#293 (8/22) and #310 (8/26) both implement Gera\'s "reply by default in #alerts like you do in #agent-smith". Unlike #278/#290 these are not byte-duplicates — each got a different half right. #293 correctly removes the now-dead is_alert_channel clause from watches_typed_alerts; #310 has the not is_home guard that #293 lacks, without which a seat pointing home and alerts at the same channel would silently override its own SLACK_HOME_MENTION_ONLY. Resolved by folding #293\'s half into #310 (plus a test pinning the home==alerts path) so the survivor is strictly the union, and closing #293 with the reasoning on the PR rather than silently. ⚠️ The standing gap is upstream: nothing stops a second lane from starting work a first lane already has open. This is the second occurrence, not the first. H2 was #290 duplicating #278 (byte-identical, closed). The pattern recurred four days later with #293/#310 — and this time the two were NOT identical, which is worse: closing either one on sight would have thrown away a correct half. The upstream gap is untouched — nothing stops a second lane starting work a first lane already has open, so expect a third. | fix | #310 MERGED (union) #293 closed w/ reasoning falsified ×2 |
| done | The three stale daemons were reloaded — and one key nearly died doing itGera answered b87888824 "reload all three" (overriding my socket-only recommendation, correctly — my reason was that SMITH_STATE_DIR would repoint the daemons, but the repo sets it to __HOME__/.claude/smith-state, exactly the hardcoded fallback already in use. Identical value, no migration. I had not checked the premise).All 5 loaded daemons now read current, and the six-week bug is fixed live: 00:36:54 channel-gate OFF — answers EVERY channel → 08:57:30 channel-gate WARN — owns [26 channels].⚠️ A bidirectional audit before reloading caught what the detector cannot see. The installed worker plist carried SMITH_FANOUT_WORKER_MODEL=claude-opus-5; the template did not. A reload RE-RENDERS the template, so it would have dropped the key and task_fanout would have fallen back to claude-fable-5 — fan-out workers silently downgraded, no check red anywhere, because D5's detector compares repo→prod only. The class is still open: a re-render is a full REPLACE, so any prod-only key is destroyed by it. | fix | PR #340 MERGED decision b87888824 · recorded EXECUTEDbidirectional audit ×3 daemons |
| done | watcher-doctor graded 14 lanes supervised with nothing minding themacceptance.json is written when a lane is armed and nothing removes it when the workflow finishes, so presence alone graded a lane "watched on Temporal" forever. Re-measured before fixing: 16 non-terminal lanes carried the marker, exactly 2 op-task workflows were RUNNING. The tool whose job is catching unsupervised lanes was the thing hiding them.It now asks Temporal once per run — the old comment defended the file check on cost ("an RPC every 180s"), which is sound for a per-lane query and not for one list call, so the constraint is respected rather than overridden. Fail-open is the load-bearing part, not the query. --repair acts on DARK lanes, so an outage that mass-darkened the fleet would arm a second looper beside every running workflow and both would wake the same session. Verified across all four outcomes: real query 2 on Temporal · query fails 16 (old behavior) · garbage 16 · empty list 0. "Could not ask" must never render as "nothing is running". | fix | 2ac1d42 local-bin (pushed)decision b87861849 · recorded EXECUTEDcontrols + falsified |
| done | The deploy-freshness canary is runningBuilt, wired, and never switched on — ~2,160 expected fires, 0 actual. Gera answered b87853466 to enable it once the alert fan-out fixes merged; they have.Preconditions checked before enabling rather than after: the restarted worker registers SmithDeployFreshnessWorkflow (worker.py:627), and the fan-out cap that was Gera\'s stated concern (_MAX_FAN_OUT = 4, from #322/#327) is in place. Then one run fired manually and watched to completion before turning on a 20-minute cadence.Live: paused=false, and the 14:02 / 16:00 / 16:20 runs all COMPLETED. | fix | schedule live · */20 America/Chicago decision b87853466 · recorded EXECUTEDone run proven before enabling |
| done | ⚠️ Both capture instruments were blind in the same wayThe two things anyone would read to confirm a flow ran were both reporting only RUNNING workflows, for the same reason: an unqualified list_workflows() is NOT newest-first — this namespace returns every RUNNING execution first (start desc), then the closed ones.smith capture RAN broke on the first row older than the window, so one long-running workflow ended the scan on iteration one and it printed "read cleanly · nothing in this window" — a POSITIVE claim of emptiness. Measured: 0 rows where 24 existed.smith doctor Temporal took the first n, so "recent n" was really "the n most-recent RUNNING runs". Measured: 8 of 8 RUNNING, zero COMPLETED — a flow that starts and finishes between two reads never appeared at all.Both now let visibility bound the set and sort locally. Found only because a "triggered" message was distrusted and the receipt checked instead. Both halves merged. Verified afterwards that these were the ONLY two readers of the assumption: every other list_workflows() caller in src/ already passes a query — the one unqualified match left is a comment describing the old behaviour. | fix | #341 capture · #342 doctor both MERGED · 0→24 rows, 8/8→6C/2R only 2 readers existed; both fixed |
:claude-fail: flags · 08-11 → 08-26Every row here is grounded in a message Gera flagged by hand. C1 is first because it is the instrument — it was hiding the rest.
| Status | Item | Kind | Evidence |
|---|---|---|---|
| done | --flagged shows the placeholder, not what Gera flaggedThe review tool has been lying for weeks. It renders the ledger's post row — the spinner placeholder the turn opened with — while the message a human actually reacted to is the latest update, edited in place. So 20 of 30 flags displayed as looking into it… :claude-dancing: and read as "dead promises". They are not: 17 of 24 were answered in place. One sample's real content was a runtime abort; another's was nothing to add there. This manufactured a whole phantom defect class and hid the real one. | instrument | PR #316 verified on the real ledger falsified: 2 of 3 tests red |
| done | nothing to add there. — 21 emitted, 10 flagged (48%)The single most-flagged thing Smith says. reply.py:1381 collapses three different causes into one dismissive line: a graceful wall-kill, a redundant idle-window follow-up, and a genuinely empty brain reply. Measured: 10 of 21 had a real answer already delivered in-thread 2–14 min earlier — pure noise that should RETRACT (the ABSTAIN path at slack_retract.py already does exactly this). The other 11 had no prior answer, and there the text is a lie about the cause — the run produced nothing, which is not the same as having nothing to add. ⚠️ A binding wisdom rule banning this exact phrase was written on 2026-08-26 14:43Z — and Smith said it again at 15:19Z, 36 minutes later. A second, non-binding rule against "content-free filler" has carried evidence: 81 since 08-24 and changed nothing. | fix | PR #317 patch-gated · phrase banned silence when nothing promised |
| done | Busy-ack fires while Smith is visibly answering122 in 16 days; 30 threads got more than one; one thread got 13. The dedupe (#218) is working exactly as designed — a promise stops being outstanding once Smith delivers, so in a fast thread each new message legitimately re-arms it. The defect is at a different altitude: at 17:31 on 08-26 a busy-ack landed 1 second before the spinner placeholder for the very next turn, and the answer followed 32s later. Promising a future circle-back while answering every 30 seconds is noise. Missing predicate: a real delivery in this thread within the last ~2 minutes. | fix | PR #318 120s window measured (p75) mute-guard test pinned |
| done | Genuinely dead acks the watchdog still missesMeasured against the live ledger, not reasoned about: 8 findings before, 15 after (STALLED 3→10). Every recovered ack was suppressed by an update on a different ack's ts. The receipt: two acks were muted by a post reading "Two 'looking into it…' acks above never delivered", while their mirrors in a quieter thread WERE detected. | audit | #325 MERGED 94742a8 · review 🟢operational number corrected DOWN in the PR body: 8→14, not 8→15 |
| done | 10 runtime aborts — re-measured 8/28: stopped, and 9 of 10 were misreadRe-measured across all 1,322 activity logs. The row said "worth confirming rather than assuming" — both halves of it turned out to need correcting. 1 · It stopped, and the denominator is real. Last abort of any kind is 2026-08-18 (not 08-16 — there were three more: 08-17, 08-18 ×2). Since 08-19 there are 351 activity logs and zero aborts, with daily volume up (33–63/day vs 13–27 before). So this is a genuine stop, not an empty instrument. 2 · "Each one is a turn that died holding a human's request" is wrong for 9 of the 10. Those nine carry an earlier result with subtype=success, is_error=false, followed by the abort. Verified by opening one (smith-reply-C0BDW7G1Z62-1787083571: result[0] success in 63ms, result[1] error_during_execution/aborted_streaming, one synthetic interrupt row). That is the documented death rattle — the runner's own post-result force-close provoking a dying CLI — and _final_result already judges by the earlier result (#218). The turns SUCCEEDED.3 · What is actually left: 1 genuine cancellation (08-17, smith-alert, no prior success) and 8 api_exhausted clustered on 08-10, which #209 had already reclassified from "cancellation" to "resource exhaustion".⚠️ Cause of the stop NOT established. Nothing in claude_runner.py between 08-18 and 08-21 accounts for it (#218 landed 08-17 and introduced the force-close; #277 on 08-21 is spinner verbs). Closing this as not-a-live-defect on the evidence above, not on a cause story — if aborts return, the shape to look for is a terminal result with NO prior success. | investigate | 1,322 logs scanned · 351 since 08-19, 0 aborts 9/10 had a prior success — teardown noisecause of stop: not established |
These are capacity and lifetime bugs — the kind that stay invisible until the day they take the whole surface down.
| Status | Item | Kind | Evidence |
|---|---|---|---|
| done | slack_send idempotency key at the transportA sync activity whose start_to_close expiry is never delivered — it heartbeats once at entry, so cancel never arrives. The timed-out attempt keeps posting while its retry posts too: 4 identical deploy confirmations in 104s, two after the workflow had given up. Keyed on (workflow_id, run_id, activity_id), never a text hash. ⚠️ Merged but NOT deployed — it is one of the five commits D1 is holding. | merged | PR #308 merged 08-26 21:22Z undeployed — see D1 |
| done | Cap max_concurrent_activities on both uncapped workersInteractive and batch passed NO cap, so Temporal used its 100-slot default against pools of 20 and 8. Correction to the audit: it said the SDK "already warns at boot" — it does not. The guard compares max_workers < (concurrent_activities or 0), i.e. 20 < 0, so it is blind to precisely the uncapped case. Measured: 0 warnings while 143 activities timed out on 08-20. Also 2 of 4 workers, not all. | audit | #324 MERGED 6d2cc7c · follow-up #326 MERGED |
| done | Cap the alert-remediation fan-out; fix _brain_queue routingTwo halves, and only ONE was the outage. The fan-out cap landed (#322) and was then corrected for fairness (#327). The _brain_queue half — the routing that put alert deciders on the shared interactive pool, where eleven ran at once — is #328, also merged. (I wrote "still gating" here and the status deriver corrected me — I had confused #328 with the doctor/capture branches gating now.) | audit | #322 MERGED · #327 MERGED · #328 MERGED #328 proven END-TO-END on real traffic: same alerts channel, 17:11Z brain→ propflow-smith, 21:24Z brain→propflow-smith-alert |
| done | approval.py's unbounded while True_gate_and_merge's poll loop was the one loop with no bound — a forever-recoverable gate polled every 15s until the history limit killed the run silently. 3h workflow.now() ceiling behind a patch gate. Correction: 2 of the 3 loops the audit flagged were already bounded. | audit | #323 MERGED 8887a93 · review 🟢ships a REAL pre-ceiling replay specimen (860 events) — and says plainly what it does NOT prove |
Gera's two rulings from 08-25: "yea we can attach, not resume", and "we should have one ongoing session per thread" with a double-message queueing into the live turn the way Claude Code does it.
| Status | Item | Kind | Evidence |
|---|---|---|---|
| done | The Operator still revives with --resumeoperator_task.py is explicit about it — "THE NAME IS THE IDENTITY. claude --resume <sid> --bg revives a dead…" — and the revive path is built around resume semantics throughout (lines 18, 24, 81, 205–231). Gera ruled for attach on 08-25. Until this moves, the ruling is a decision nothing implements. | fix | PR #315 found stranded, rescued 27 tests · falsified |
| done | Own the resume-miss instead of silently starting freshThe real defect behind the whole session model. The session store is claude's local --resume database — unreplicated, evictable, and not modelled by the workflow at all. The code detects a miss and silently starts a fresh session, so a thread can lose its entire history with no signal to anyone. This is very likely the mechanism behind Gera's long-standing complaint that Smith answers as if it has never seen the thread. | fix | PR #315 attach keeps id + transcript |
| done | One session per thread + queue the second messageGera: "whenever I double message, I would want that second message to be like queued into the message… and I don't want to close that conversation." Today a second message races the live turn rather than joining it. The thread root ts is the natural session key — it already names the Temporal workflow id (smith-reply-<channel>-<ts>), so the identity exists; nothing binds the session to it. | new | PR #315 uuid5 per thread, permanent |
| done | The "substantive ask → durable work" door — it EXISTS, and this row had it wrongCorrection, 2026-08-29. This row said the door does not exist and that a substantive ask from a reply turn "dies leaving no record". That is false on main, and it was false when the row was written. workflows/reply.py:1739 is the FOURTH canonical post-turn hook: it calls start_task_goal_if_requested and arms SmithTaskWorkflow, the durable container that holds a goal across phases. reply.py:1499/1654 similarly arm SmithPrDriveWorkflow for any PR the turn opens.What misled the earlier check was grepping for the string start-operator. That script genuinely is not called from src — but the door was never built on it. The durable path is a Temporal child workflow, so an instrument looking for the shell script correctly reported the script's absence and was read as reporting the door's absence. Same shape as H7 on this page: an instrument answering a nearby question.The gate is an explicit human marker, and that is a decision already made with reasons written down — not an omission. The container arms on a human sentence ( goal: … done when: …) plus three more gates: a Temporal workflow.patched id (a pre-existing recorded message carrying those markers would otherwise schedule a start-child command absent from its history — a non-determinism failure), scrubbed.primary (arming a day-long container off a message Smith DECLINED to answer would be the loudest contradiction of that silence), and human turns only.And the classifier alternative is closed, deliberately. The pre-model intent gate was REMOVED 2026-07-10 ( _FRONT_DOOR_PATCH_ID, the marketing-brain inversion) because it short-circuited terse real asks into one-liners; activities/intent_classify.py survives only for deterministic replay of pre-patch histories. Reviving it as a trigger would re-introduce the exact miss that ruling deleted.Residual, stated rather than tracked: an UNMARKED substantive ask does still run inline and end with the turn. That is the accepted trade — a false container costs real turns — so it is a known cost, not an open defect. The doc-rot fix ( f6b408d) that said "the door is NOT built" is now itself too strong and is the next thing to correct at the source. | verified | reply.py:1739 arms SmithTaskWorkflow · marker gate is deliberate |
Finished work that never landed, and the checks that quietly stopped meaning anything.
| Status | Item | Kind | Evidence |
|---|---|---|---|
| done | Open-PR backlog — 16 → 7 → 0 (re-measured 8/29 15:45Z)Started this review at 16 open. Re-measured 8/28 16:30Z at 7. Now 0 — gh pr list --repo PropFlow-Technologies/agent-smith --state open returns an empty set.Every one closed on a verdict, not swept — which was this row's whole standard, so it is the thing I checked rather than the count. The last seven resolved between 8/28 16:55Z and 8/29 04:17Z: five merged after review (#341–#346), #310 merged once unstarved (see H7), and #260 closed on a reasoned finding rather than age — its four files were already on main and larger in every case (1161/359/897/334 lines vs 915/265/685/259), landed 08-23 against a branch head of 08-17, and its one unique line was a bug main had deliberately fixed. That reasoning is recorded on the PR. The rebase trap this row documented never had to be paid: nothing was force-rebased, so git merge origin/main-before-push stands as guidance for the next stale branch rather than as work done here. | audit | 0 open · #260 closed on a finding, not on age |
| done | Smith opened the SAME auto-fix PR twice#290 was a byte-identical duplicate of #278 — same md5 on the source diff, which is what made closing it an objective call rather than a judgement. The work survives in #278. ⚠️ The failure mode RECURRED and is still open — see h6. | audit | #278 MERGED — the surviving PR the duplicate #290 was closed against it |
| done | Flag scan cries wolf on a channel that no longer existsC0BLA553PFZ returns channel_not_found. It was live for exactly two days (08-18 → 08-20, 83 posts) and is now gone. The sweep cannot tell "archived" from "broken token", so it prints channel scan FAILED AND falls through to per-message reaction reads that each fail the same way — 61 of them on 08-18, 16 on 08-20 — then stamps the day's results INCOMPLETE. Two loud false alarms from one dead channel, on the instrument whose whole job is to be trustworthy about incompleteness. | fix | PR #319 gone ≠ broken · both directions falsified |
| done | Three delete call sites; the audit knows about oneThe review's standing delete audit expects only slack_socket:alert-episode (87 rows). There are now two more: abstain-retract (6) and worker:deploy-announce (10). Both are legitimate — own-authored, ledger-recorded, folding noise rather than erasing evidence — but "sanctioned" is a property of the call site, not the capability, and an audit list that does not know about a path cannot vouch for it. Update the list, and state how each is gated. | audit | PR #338 AST call-graph guard falsified ×2 |
| withdrawn | The four nightly jobs are not failing — nothing runs themSharpened 2026-08-30, and it changes what this row means. This row tracked them as chronically failing, and my own last pass reported only the symptom — that their logs stopped writing in July. The cause is simpler and worse: they have no scheduler at all. Verified two ways just now — no plist in ~/Library/LaunchAgents and nothing loaded in launchctl list for any of turnover-eval-daily, maintenance-eval-daily, morpheus-nightly-daily or git-hygiene-main-drift; and the user crontab is empty. The July logs are not an instrument that broke, they are the last time these jobs ever ran.Why that inverts the work. A failing job needs a fix; an unscheduled one needs a decision about whether it should exist. The failure streaks this row counted are historical, and no amount of driving them "to green" reaches a job that is never invoked — so decision b87721567 ("Drive all four to green now") was answered against a premise that does not hold. That is now its own open block, b88033792.⚠️ The delivery defect this row found stands and is independent: notify.notify omits alert=True, so no smith_alert marker is written and these banners never reach the dedup or episode gates. It matters the moment any of them is scheduled again — and it is exactly why the silence read as calm rather than as absence.Closed 2026-09-07 — the premise was false. All four run nightly as steps of NightlyQueueWorkflow in Smith’s tools-prod Temporal namespace (source of truth: propflowai-smith/config/automations.toml; morpheus’s standalone 05:00 LaunchAgent was retired 2026-07-12 when it joined the queue, and git-hygiene-main-drift is a detector on morning-queue step 0, not a job). launchd, the plist folder, the crontab and the default-namespace schedule list all read them as absent, which is what this row measured. The queue exists to serialise them: reinstalling plists would double-run them against the appfolio-45 bench. Decision b88033792 is no longer open. The delivery defect is fixed on main: notify() takes alert_source, which stamps the smith_alert marker. | withdrawn | nightly_queue.py · morning_queue.py step 0 · notify.py alert_source — verified 09-07 |
| done | 🔴 HIGH — the fan-out cap sliced a STABLE orderMy own #322. MERGED (#327). The ledger really is alphabetical — the #LATEST pointer's sort key IS the job id (automation_run.py:262). But the report's proof was fabricated: fanOutCap is null on all 30 real scans; the cap merged tonight and had never run. Real measurement: replaying the 30 actual scans with deferrals carried forward, touch-ledger-reconcile waits 9 days, propflow-appfolio-sync-prod-purchase-orders 7. Nothing starves forever — a multi-day delay, not permanent silence. Ordering stalest-first takes the longest wait to 0. | red-team | #327 merged aefeeca⚠️ a mutation showed the test that NAMED the regression stayed green when the bug returned — it drove the pure fn, not the wiring. Wiring test added review found a §1 trap: eligibleSources holds the FIRED set, not the eligible one — now pinned by a round-trip test |
| done | MEDIUM — #322 capped the smaller producerConfirmed and quantified. Across all 335 smith-alert-* executions in 30 days: ELEVEN concurrent deciders on 8/10 against a 20-thread pool, busiest 10 min started SEVEN, longest ran 9.5 hours — so #322's "~90 minute brain" is not the tail. The scan path was capped at 4 while the uncapped socket path reached 11. Fixed with a dedicated propflow-smith-alert queue (cap 4), the pattern already proven for reply brains. | red-team | #328 · review 🟢 on the fix ⚠️ correction: I claimed the patch gate was needed for determinism. Falsification disproved it — Temporal does not compare an activity's taskQueue. Gate kept for §2/defence and the comments now say so (the sibling reply-brain comment carried the same false claim since it was written) |
| done | MEDIUM — #318 turned a 120s look-back into an unbounded muteConfirmed and MERGED (#325). Measured against the live ledger rather than reasoned about: 8 findings before, 15 after (STALLED 3→10). Every recovered ack was suppressed by one row shape — an update on a different ack's ts, i.e. another turn answering its own question. Gaps of 9 to 163 minutes. The receipt that settles it: two acks were muted by a post that literally read "Two 'looking into it…' acks above never delivered", while their mirrors in a quieter thread WERE detected — same acks, same minute, opposite verdicts, decided only by thread traffic. Fix is per-class: STALLED ignores acks owned by another ack; FAILED and BUSY still read everything, because a retry IS another turn answering its own ack. | red-team | #325 merged 94742a8⚠️ a mutation initially SURVIVED — every historical suppression happened to be an update-on-an-ack, so a blanket rule was observationally identical. 192 of 824 real update rows edit a non-ack; test added operational number corrected DOWN in the PR body: 8→14, not 8→15 (the daily run reads 2 days, not history) |
| done | MEDIUM — #317 left sibling handoffs with zero outputConfirmed at the ack gate. #317 keyed "say nothing on an empty run" off the ABSENCE of an ack — but the ack gate suppresses the ack for THREE kinds of turn (is_cron, a webhook wake, and sibling_hop != 0) and only the first two are self-driven. A sibling handoff is another agent asking, and waiting: after #317 it gets no ack, no answer and no note, and cannot tell "produced nothing" from "still working". That is the dangling-ack shape one layer up, introduced by the fix for dangling acks. #317's own test docstring listed "a sibling hop" beside the cron drop — that line is where the case got lost, and it is corrected in the same commit. | red-team | #333reply.py ack gate self._sibling_hop == 0new _SIBLING_GETS_AN_ANSWER_PATCH_ID — this one does move a command (unlike a taskQueue change)falsified BOTH ways: reverting reds, widening it to all ack-less turns reds the cron case |
| done | MEDIUM — a gone channel was re-asked once per postConfirmed, and the reported "61" is exact. gone was write-only — added inside the except, never consulted on the way in. Seeded with the receipt's own 61 messages and driven through main(), the merged code issues 61 reactions.get calls. Teeth: Tier 3 is ~50/min, so one archived channel could 429 the day's LIVE posts and stamp the review INCOMPLETE. Correction to the report: the sweep half was never affected — this lived entirely in the ledger join. | red-team | #331 merged d7b69d5 |
| done | MEDIUM — a kicked-from private channel was called deletedConfirmed (§4). Slack returns channel_not_found for a deleted channel AND for a private channel the bot was removed from, and cannot distinguish them — but both printed "archived or deleted", asserting a cause the error does not support and hiding the remedy that works (re-invite). is_gone_channel replaced by gone_channel_reason, which says only what Slack said. | red-team | #331 merged |
| done | MEDIUM — an answer edited in after UTC midnight read as an unanswered ackConfirmed by reproduction. record() is idempotent on (channel,msg_ts,kind) and posted_at is written ONCE, so an update row carries the moment of its FIRST edit. An ack posted 23:58 whose answer is delivered by editing at 00:03 leaves the post row in day D and the update row in D+1 — and #316 built its "what it says now" map from posts_on(day) alone, so the day-D review rendered the PLACEHOLDER. Direction matters: this MANUFACTURES the never-delivered-ack signal the review exists to find, on a turn that answered fine. | red-team | #332 seeded ledger: REVIEW of 2026-08-20 shows: ['looking into it…']new outbound_ledger.updates_for(keys) — keyed by MESSAGE, bounded by the day's keys, chunked under SQLITE_MAX_VARIABLE_NUMBER⚠️ caught my own vacuous test: the first fail-soft test wrapped its own try/except around a raising stub — it proved only that the test could catch an exception |
| done | MEDIUM — #314 shipped literal backslashes to the readerConfirmed by reproduction. _demote_markdown_blocks takes a body already GFM-escaped and re-renders through to_slack_mrkdwn, which folds the label verbatim: <…|github.com/…/src/\_\_init\_\_.py>. The href is fine; what breaks is "which file is this?" — the thing putting the path in the label was FOR. Fixed with _unescape_gfm_label at the fold. | red-team | #330 ⚠️ 2 corrections: my one-pass justification ( \\_) was disproved by its own mutant — the real divergence is escapes outside the marker set. And review caught the docstring still carrying the disproved claim: a git checkout -- restore had reverted my uncommitted fix, and the amend shipped it. Commit before mutating. |
| done | LOW — an empty channel id matched every limit stringReal in the instrument, NOT reachable from today's writers — and reported that way rather than inflated. any(cid in lim …) is a substring test, so "" in lim is True for every string. The ledger column admits '' and record() does not validate, but slack.post_message never produces one. Fixed anyway: the substring test was the wrong instrument regardless of input. | red-team | #331 merged |
| done | The decision-block treadmill — 296 re-arms over a 16-day-old answerRoot cause of H6’s volume. 40 of 55 running workflows were SmithDecisionBlockWorkflow; three blocks were 69% of it. Each had been answered on 2026-08-10 and adopted in three sessions — but a fourth ledger copy in another session’s directory still said open, and open_block_records keys on the block id alone, so the sweep re-armed a driver over a decision made two weeks earlier. Closed the stale copies FIRST, then stopped the drivers — that order, or the sweep undoes the stop within ten minutes. | closed | ✅ 26 Aug. b86386853 + b86388306 stale rows → adopted; both drivers signalled stop. Verified answer.source=="decisions" on each before closing — never matchedOption alone. Originals kept in ~/code/tmp/. |
| done | The decision sweep fork-bombs the docs KV pathObserved live 2026-08-26 17:22Z: 249 concurrent wrangler kv key get processes, 203 of them orphaned to launchd, load average 7.2 — and it was still climbing. Traced end to end: agent_smith.worker (pid 784) shells blocked check (worker.py:276); blocked resolves answers by looping every slug serially (blocked:1506, for s in slugs) and shelling bin/docs answers <slug> --json per slug; each of those spawns its own wrangler node against Cloudflare KV. One check therefore costs N subprocesses, and under contention a call outlives the poll interval — so the calls stack and the pile is self-reinforcing. It starved a docs publish for 15+ minutes. ⚠️ This is the exact failure bin/docs warns about in its own pinning comment: degradation on the answers path "presents as NOBODY IS ANSWERING, fleet-wide" — so a decision Gera HAS answered can read as unanswered. Cap the fan-out, batch the read (one kv key list beats N gets), and reap orphans. Drained by hand to 2 on 08-26 17:40Z; the spawner is unchanged. | fix | ✅ Fixed at the source 27 Aug 00:0xZ. read_store now reads KV over REST instead of spawning docs answers per slug. blocked check <id>: minutes → 0.53s, zero node. Also fixes ABSENT-vs-UNREADABLE (404 is now a real empty, not a failure). Peak was 939 + 642 procs at load 13. local-bin 789efbd. |
Not tasks — things that stay true until someone changes them, and any "done" above should be read against them.
nothing to add there. was written 2026-08-26 14:43Z; the phrase shipped again 36 minutes later, and 21 times across the window in total. Wisdom records intent; only code refuses. Treat any "we wrote a rule about it" as an open item, not a closed one.--flagged rendering the wrong text (C1) and the flag scan's dead-channel false alarm (H3). Both were reporting confidently while blind.~/code/PropFlow/agent-smith as its cwd and branches there silently becomes production on the next worker restart.Sibling page: Session tracker — decision-page-markup-review, which carries the Yale lane and the go-live gates. Decisions belong on the Decisions tab, never on this page.