A continuation controller that works, ten defects found by testing it, and a queue it finished on its own standing between it and running unattended. 26 October 2026.
Status of every numbered item in the brief, with the evidence that backs it. An item with no evidence beside it is marked unfinished rather than done.
| # | Ask | Status | Evidence |
|---|---|---|---|
| 1 | Five-minute automatic continuation | Running unattended | 20 launchd runs, last exit code 0. It sent 3 nudges, closed 5 queue steps and marked the work complete with no one at the keyboard. |
| 2 | Revisit the X.com conversation against reality | Done | All 8 recommendations checked against this machine, 4 of 5 free actions were never implemented |
| 3 | Verify and improve, not box ticking | Done | 10 defects found in my own work and fixed, 1 improvement reverted after testing proved it a regression |
| 4 | Lyrics without AI reproducing text | Built and loaded with real data | 33 songs from 3 source types, feature export verified to leak 0 verbatim lines |
| 5 | Complete change report | This page | Every claim below carries a command output, file path or live URL |
| 6 | Second optimization pass | Done, reported separately | Section 7, which found 4 items the first pass missed, 2 of them real bugs |
| 7 | Durable scheduled process | Verified live | Ticks at 14:03, 14:08, 14:13, 14:18, 14:23, 14:28 and 14:33 on a closed app. The 3 failed ticks sit inside the consent window and every tick after 13:08 succeeded. |
Claude Code holds the queue and decides what to send. The browser sessions do the work. That is the difference between a feedback loop and a prompt loop.
The word continue appears nowhere in anything it sends.
~/.claude/skills/continuation-controller/ · state and logs: ~/.claude/continuation/Your MTG deck conversation had stalled at 12:29 PM on the word
continue, with ChatGPT's answer cut off mid-sentence at
"After twenty" and the page showing
Connection interrupted.
The controller classified it as stalled, quoted the exact point of
interruption back, and asked for the one outstanding step. ChatGPT worked for 2m 43s
and produced Revision 21: all five decks structurally legal, none demonstrably
optimized, each verified at 60 main deck and 15 sideboard with no four-copy
violations, plus named gaps on Delney's redundant legendary draws and Gardenize
needing black sources that green-only acceleration cannot supply.
sent {state: stalled, submitted: button, chars: 1626} then progress {gptTurns: 5 -> 6}That conversation then hit You have reached the maximum length for this conversation. Prompting cannot fix that, so the controller marked it blocked with the true reason instead of nudging a corpse.
ctl.py migrate opened a fresh chat, seeded it with the verified
checkpoint and the four surviving steps, carried the finished step across with its
evidence, and re-pointed the target automatically. ChatGPT's first reply in the new
conversation: "I will locate the existing five-deck artifacts,
correct only the Mono-White Delney ranking and filename references. I will leave the
decklists and other outstanding tests untouched."
It picked up at step 2 and declined to redo step 1.
chatgpt.com/c/6adf9587-074c-83e8-bf23-d26611f3dcfd · seed verified by composer emptying from 1934 chars to 1The first scheduled run raised the macOS dialog "python3.13 wants access to control Google Chrome", and every Apple Event blocked until it was answered. Three ticks failed inside that window, at 12:57, 12:58 and 13:03. Once consent was given, every tick since has succeeded.
What it then did with nobody at the keyboard. It sent three context-aware
nudges at 13:08, 13:18 and 13:33, closed five queue steps on evidence ChatGPT
reported itself, and marked the target complete at 13:38 when the answered turns
caught up with the asked ones. It has idled correctly ever since, logging
skipped: complete every five minutes rather than prodding finished work.
launchd runs 20 last exit code 0 ticks logged 24 tick errors 3 (all inside the consent window) nudges sent 3 queue steps closed 5
The spent-evidence guard also fired in production. At 13:18 one report closed two steps, the guard reopened the second, and the conversation earned it again with fresh evidence at 13:33.
Nothing was assumed finished because it had been written down. Each recommendation was checked against local state.
| # | Recommendation | Claimed | Found today | Now |
|---|---|---|---|---|
| 1 | Obsidian 1.12.7 to 1.14.5 | 5 min | Still 1.12.7 | Done, 1.14.5 installed |
| 2 | Claude Duet waitlist | 1 min | Needs your account | Yours to join |
| 3 | Bash sandbox | 10 min | No sandbox key at all | Configured, left off, see section 7 |
| 4 | mcp-obsidian write access | 15 min | mcpServers was empty | Wired and proven |
| 5 | financial-analysis plugin | 10 min | Not installed | Installed, 18 skills including xlsx-author and audit-xls |
| 6 | Claude Dashboards trial | 20 min | Needs your account | Yours to try |
| 7 | Game-Studios hook reading | 20 min | Not read | Read, 18 hooks; the rigor dial is below |
| 8 | Obsidian Sync decision | $48/yr | Costs money | Out of scope, free only |
Four of the five free actions had never been implemented. The page was correct about what to do and nothing had been done with it.
The bridge was half built and sitting idle. obsidian-local-rest-api
was enabled in the vault and answering on both ports, and nothing was configured
against it.
Now registered at user scope with mcp-obsidian 1.30.0, exposing 15
tools. The two that matter are obsidian_patch_content and
obsidian_append_content, because the previous retrieval path could read
the vault and could not write to it. Session notes, artifact rows and decision logs
can now be written straight into Brandon-Vault.
obsidian_delete_file is denied in permissions.deny, so no delete call can reach the vault through it.
serverInfo: mcp-obsidian 1.30.0; no mcp 2.0 incompatibilityForty launchd agents already run on this Mac and not one of them did this.
1,103 lines across the bridge, the store, the tick and the operator CLI, plus a
skill file so it is invoked as /continuation-controller rather than by
raw path.
Covered in section 5. 33 songs loaded from material already on your disk.
~/Projects/_outputs/lyrics-db/lyrics.sqliteSection 7 explains why. Applying it broke two of your standing gates, so it is written, tuned to your real write paths, and switched off with the reason recorded in the file.
settings.json now carries sandbox._why_offThe lyrics problem is a transport problem wearing a copyright costume. Asking a model to retype protected text is both the thing it should refuse and the slowest possible courier. The two jobs are now separate.
Transport. Embedded lyric frames in files you own, .lrc
sidecars, JSON packs already on disk, and your own clipboard through
capture_clip.py. Byte for byte, with no model in the path. Every row
carries source, source_ref and rights_note, so
the Joni pack's own "personal, non-commercial study only" note survived import rather
than being dropped.
Intelligence. features --out emits hashes, counts, structure and
quality flags with no lyric text in it. That is enough for identification, matching,
indexing, deduplication and quality checking.
Loaded today from material already on your disk: 33 songs, 22 rows from JSON
packs, 10 from .lrc sidecars, 1 from an embedded tag.
lyrics_db.py qcNumbers taken from the machine on both sides of the work. Nothing here is an estimate.
| What | Before | After |
|---|---|---|
| Continuing a browser AI session | You type continue by hand | 3 nudges sent and 5 queue steps closed with nobody at the keyboard |
| Your MTG conversation | Stalled 12:29, dead on a cut-off sentence | Complete 13:38, all five steps evidenced |
| launchd jobs doing continuation | 0 of 40 | 1, with 20 runs at exit code 0 |
| Obsidian | 1.12.7 | 1.14.5, 14 community plugins carried over, old bundle archived |
| Claude Code access to the vault | Read only, mcpServers empty | 15 tools including patch and append, delete denied |
| Lyrics on disk | 3 formats, no index, no quality check | 33 songs in one database, 4 real defects surfaced |
| Lyric text reaching a model | The blocker that stopped the work | 0 verbatim lines in the AI-facing export, tested |
| Truncation flag on the same corpus | 17 of 33 rows, 52% | 0 of 33 |
| Reading of the stalled conversation | mainLen 7130, userTurns 8, contaminated | mainLen 5366, userTurns 6, true |
| Turn counting on claude.ai | 0 and 0, silently wrong | 1 and 1, correct |
The transferable piece from that repository, which is why it was on the 23 October page at all.
The repository carries 18 hooks. The part worth taking is modes.rigor,
a single setting whose whole job is to expand into six knobs:
modes.review_mode, modes.workflow, docs.density,
qa.level, modes.story_granularity and team.size.
Its own config file warns that seeding any of the six pins that value and shadows the
expansion, so the dial stops changing anything.
Your honesty gate has no such dial. Six hooks fire at full strength on every turn,
whether the turn is a one-line edit or a shipped chapter. A rigor setting
that expanded into which gates run would cost nothing and is the one idea in that
repository worth having.
Each of these would have made the automation lie to you. Every one was caught by running the thing rather than by reading it.
| # | Defect | How it would have lied | Fix |
|---|---|---|---|
| 1 | Insert verified by a 24-character prefix | A stale composer passed verification because two prompts shared an opening clause. The wrong text was submitted and reported as success. | Exact whitespace-normalised equality, proven with a nonce the model echoed back |
| 2 | A regex backslash in the JS payload | AppleScript string literals escape backslashes, so the bridge died with a syntax error | Whitespace normalised by char code, and run_js now refuses backslashes and double quotes outright |
| 3 | Enter fallback reported success and submitted nothing | 1,764 characters sat unsent in the composer while the log said sent. Synthetic key events carry isTrusted: false and ChatGPT ignores them. | Insert and submit split into two calls with a render gap, and submission confirmed by the composer emptying |
| 4 | The composer lives inside main | Unsent text counted as conversation progress. Readings of mainLen 7130, userTurns 8 were really 5366 and 6. | The probe subtracts the composer's own text before measuring anything |
| 5 | The prompt quoted the strings the probe counts | One unanswered nudge inflated the turn counters from 6 to 8 and 5 to 6, so outstanding work would have looked permanent | Turn markers neutralised in any quoted tail |
| 6 | Truncation flagged anything ending on a letter | 17 of 33 rows, a 52% truncation rate that was the measure reporting its own bias. Lyrics almost never end in punctuation. | Only unambiguous mid-sentence breaks count. Same corpus now yields 0. |
| 7 | A patch that printed success and changed nothing | I replaced a code block without asserting the match, saw my own confirmation message, and the old rule was still running | Every subsequent patch asserts the replacement landed before writing |
| 8 | The step-closing marker could match my own instruction | A line beginning EVIDENCE: would have closed a queue step and recorded my own request as its evidence | Echoes of the controller's own phrasing are rejected |
| 10 | The skill file was wrong about claude.ai | It claimed Claude renders Claude said:. The real string is Claude responded:, .font-claude-message does not exist, and claude.ai repeats the user's text in innerText so text counting double counts. Claude turn counting would have read zero forever. | User turns take the larger of the text count and the data-testid=user-message DOM count, which is correct on both sites. Claude now reads 1 and 1 where it read 0 and 0. |
| 9 | One evidence line closed two queue steps | An EVIDENCE: report stays visible in the tail on later ticks, so it closed step 4 as well as step 3 on one Azorius deck report. Caught by reading the queue output, where steps 3 and 4 carried an identical evidence string. | Evidence already spent cannot close another step. Step 4 was reopened. |
An independent re-examination, looking for what the first pass missed and challenging what it had already concluded. Four findings, two of them real defects that were still live.
The 23 October page scored the Bash sandbox 9 out of 10, and the reasoning was sound: it replaces bypass mode with a real boundary. I enabled it with your actual write paths.
It took effect immediately and turned off Apple Events. osascript
to Chrome began failing with -600 with Chrome demonstrably running,
which breaks the continuation controller's in-session bridge and your
auto-open-deliverables gate, since open -R and launching Firefox both go
through Launch Services.
A change that breaks two standing gates
fails its own test. It is configured, tuned
and switched off, with the reason written into the settings file. The launchd
controller is unaffected because it runs outside this sandbox. For a single hardened
session: claude --settings '{"sandbox":{"enabled":true}}'
-600 with sandbox on, 1 window with it offIt reported Automation permission: GRANTED because it inherits the
grant of whatever shell invokes it. The launchd job runs as a different responsible
process and holds no such grant, which is the entire reason the scheduled run hung.
A health check that passes while the thing it checks is broken is worse than no check.
It now states that caveat and prints the launchd job's own last tick outcome.
Defect 8 in the table above surfaced on this pass. The first
pass had concluded it was safe because the controller's prompt is a single line that
starts with a bracket. That is luck rather than a guard, and a direct test proved a
line beginning EVIDENCE: would close a step with my own instruction text
as its evidence.
please try again is in the stall marker list and could appear in
ordinary model prose. The consequence is mild, since both stalled and
idle lead to the same action, and it is recorded here rather than
quietly left.
Named rather than dropped. Nothing here is being reported as done.
| Item | Why it is not finished | What it needs |
|---|---|---|
| Claude Duet waitlist | Account action on your login, which I cannot take for you | Join from claude.com |
| Claude Dashboards trial | Account action on an existing route | Yours to try |
| A second live target | The claude.ai path is verified end to end, including send and turn counting, and no Claude conversation currently carries outstanding work worth registering | ctl.py add --id claude-main --url claude.ai/chat/<id> --kind claude |
| Obsidian Sync | Costs money, outside a free-only brief | $48 a year, your call |
One unrelated thing the screen showed while I was working: your keyboard battery is very low and is asking to be connected to USB.