feat(routines): git-tracked Claude Desktop routines + autonomous-loop registration#3034
Conversation
…omous-loop registration Aaron asked for a power-user / scripting / version-control path for Claude Desktop scheduled tasks (the "Routines" sidebar panel), composed with the existing CLI <<autonomous-loop>> cron substrate. This introduces tools/routines/ as canonical-source-of-truth for Desktop routines and registers the first one (autonomous-loop) as a Desktop-side every-2-hour fallback for the CLI every-minute tick. Two-layer architecture: tools/routines/<id>/ — git-tracked canonical (SKILL.md + schedule.json) ~/.claude/scheduled-tasks/ — runtime location, generated from canonical tools/routines/install.ts — idempotent Bun TS installer (rule-0 compliant) The scheduled-tasks MCP server is cross-host — same server wired into both CLI Claude Code and Claude Desktop. Both surfaces read/write the same disk store. This PR makes the prompt-body authoring path git-canonical so routines are diffable, PR-reviewable, and shareable across maintainer machines. Three-layer catch-43 defence now in place: 1. CLI session cron — every minute, cheap re-prompt, dies on exit 2. Desktop routine — every 2 hours, persistent cold-boot, survives restart 3. tools/routines/ repo — git canonical, survives runtime corruption Composes with: - .claude/rules/tick-must-never-stop.md (catch-43 substrate) - .claude/rules/rule-0-no-sh-files.md (TS installer, not bash) - .claude/rules/holding-without-named-dependency-is-standing-by-failure.md (PR #3029) - .claude/rules/dv2-data-split-discipline-activated.md (canonical vs runtime change-rate split) - docs/AUTONOMOUS-LOOP.md (canonical tick procedure) Tick shard: docs/hygiene-history/ticks/2026/05/13/2125Z.md Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 01fcf40a7e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Adds a git-tracked “canonical routines” substrate under tools/routines/ for Claude Desktop scheduled tasks, plus an initial autonomous-loop routine and a Bun/TS installer to sync the canonical SKILL.md into the Desktop runtime directory (~/.claude/scheduled-tasks/). This complements the existing CLI session-scoped cron tick by providing a durable Desktop-side cadence.
Changes:
- Introduces
tools/routines/documentation and a first routine (autonomous-loop) withSKILL.md+schedule.json. - Adds
tools/routines/install.tsto sync canonical routine prompts into the Desktop runtime directory. - Records the work as a hygiene-history tick shard.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/routines/README.md | Documents the canonical-vs-runtime routines architecture and authoring workflow. |
| tools/routines/install.ts | Bun installer that syncs canonical SKILL.md files into ~/.claude/scheduled-tasks/<id>/SKILL.md. |
| tools/routines/autonomous-loop/SKILL.md | Adds the Desktop routine prompt body for the autonomous loop. |
| tools/routines/autonomous-loop/schedule.json | Adds a 2-hour cron schedule metadata file for the routine. |
| docs/hygiene-history/ticks/2026/05/13/2125Z.md | Tick shard capturing the work, verification trace, and rationale. |
Comments suppressed due to low confidence (2)
tools/routines/install.ts:61
readSchedulecatches JSON parse failures but then returns{ missing: false }, which hides an invalidschedule.jsonand produces ambiguous output (no cron printed and no “missing schedule” note). Consider surfacing parse errors explicitly (flag + warning) and/or failing the run so broken schedules can’t silently slip through.
try {
const parsed = JSON.parse(readFileSync(path, "utf8")) as { cronExpression?: string };
return { cronExpression: parsed.cronExpression, missing: false };
} catch {
return { missing: false };
}
tools/routines/install.ts:124
- Schedule changes won’t be detected:
needsRegistrationonly includes routines when SKILL.md was created/updated, so editingschedule.jsonalone won’t prompt re-registration and can lead to silent schedule drift. Consider tracking schedule state too (marker/hash) or always emitting registration instructions whencronExpressionis present.
const needsRegistration = results.filter(
(r) => r.cronExpression && (r.action === "created" || r.action === "updated"),
);
if (needsRegistration.length > 0) {
… + testability Bundles all 7 review findings from PR #3034 into one commit: install.ts - Fix tsc exactOptionalPropertyTypes violations (was the BLOCKING required check failure) - Eliminate TOCTOU races: replace existsSync+readFileSync pairs with try/catch around readFileSync via readFileOrUndefined helper (addresses CodeQL alert) - Surface invalid schedule.json instead of silently swallowing parse failures: new scheduleParseError field + console.error + parseErrors summary count (Codex P2) - Export listRoutines/readSchedule/syncRoutine/main with directory parameters so tests can drive deterministically without touching real homedir() or import.meta.dir (Copilot test-coverage suggestion; actual test suite is a follow-up) - Gate main() under if (import.meta.main) — aligns with other tools/** scripts that use the importable/testable pattern (Copilot) autonomous-loop/SKILL.md - Remove hardcoded /Users/acehack/Documents/src/repos/Zeta path from routine prompt (Codex P1, Copilot duplicate). Now portable across maintainer machines via "typically ~/Documents/src/repos/Zeta" guidance + project-metadata fallback. Installer will sync the new prompt to the live runtime SKILL.md on next run; the next routine fire (22:07Z) picks up the portable version. README.md - Fix Rule 0 wording: was "only `.sh` allowed there" (implied tools/setup/ is .sh-only); now ".sh files are restricted to under tools/setup/; other formats also live there" (matches .claude/rules/rule-0-no-sh-files.md) (Copilot) docs/hygiene-history/ticks/2026/05/13/2125Z.md - Update pr: TBD → pr: 3034 (Copilot — placeholder breaks consumers that parse frontmatter) Verify trace: 1. npx tsc --noEmit on routines files: clean ✓ 2. bun tools/routines/install.ts: [updated] autonomous-loop, parseErrors=0 ✓ 3. Runtime SKILL.md at ~/.claude/scheduled-tasks/autonomous-loop/SKILL.md now portable Follow-up (not in this commit): add bun:test test suite for install.ts (Copilot suggestion; refactor for testability landed here so the suite can land cleanly). Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 79c00b9df4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- SKILL.md:16 — wrap cron expression in backtick span to avoid MD037 (asterisks in "* * * * *" were parsed as emphasis markers) - 2125Z.md:43 — add blank line before list to satisfy MD032 (markdownlint requires blank lines surrounding lists) Co-Authored-By: Claude <noreply@anthropic.com>
Real-time observation: Otto-CLI hijacked the primary worktree branch context while Otto-Desktop was working there, in the SAME session that Otto-CLI authored PR #3032's claim-acquire-before-worktree rule. The rule was speculative when proposed; this observation is its first empirical validation. Files: - memory/feedback_split_brain_real_time_otto_cli_otto_desktop_primary_worktree_branch_hijack_pr_3032_claim_acquire_rule_validation_2026_05_13.md - docs/hygiene-history/ticks/2026/05/13/2140Z.md The memory extends PR #3032's rule operationally — adds 3 clauses beyond "claim acquire before worktree work": 1. Each Otto gets ONE dedicated worktree (never share primary) 2. Never git checkout on a worktree another Otto is using 3. Bus claim envelope should include 'worktree' field Composes with PR #3032 (validates), substrate-or-it-didn't-happen (rules in flight don't apply to behavior in flight), glass-halo-bidirectional (observation enabled diagnosis). Co-Authored-By: Claude <noreply@anthropic.com>
The non-required CI check "check memory file frontmatter completeness" flagged the file. Added YAML frontmatter (name/description/type) matching the convention used by other memory/feedback_*.md files. Co-Authored-By: Claude <noreply@anthropic.com>
…ion hint
P1: registration hint was gated on action=created|updated, so a
schedule-only change (SKILL.md unchanged) silently skipped the
create_scheduled_task reminder. Remove the action emitfilter
the hint whenever cronExpression is present, regardless of action.
P2: readSchedule returned { missing: false } with no parseError
when schedule.json parsed but lacked cronExpression, producing a
silent misconfiguration. Now surfaces a descriptive parseError so
the caller prints the malformed-schedule warning.
Addresses chatgpt-codex-connector review threads on PR #3034.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…n optionality Two Copilot review threads on PR #3034: install.ts (Copilot — BP "No name attribution in code"): - Doc comment said "ask Otto (or call directly) to run create_scheduled_task" - Console output said "(in a Claude session, ask Otto to run create_scheduled_task for each)" - Both replaced with persona-agnostic phrasing: "invoke create_scheduled_task from an interactive Claude session (or via direct MCP API call)" README.md (Copilot — internal consistency): - Top-level routine description said each routine has SKILL.md + schedule.json without noting schedule.json is optional - But installer explicitly supports missing schedule.json (ad-hoc routines) - Now: SKILL.md marked **required**, schedule.json marked **optional** with the ad-hoc clarification both in the bullet list and in the Authoring section Verify trace: - bun tools/routines/install.ts → [skipped-unchanged] (idempotent confirmed) - npx tsc --noEmit → clean on routines files Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fbdc1fabca
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…errors (Codex P2 x2) Two P2 findings from Codex on PR #3034: 1. Reject non-string cronExpression values (PRRT_kwDOSF9kNM6B5oEP): readSchedule now narrows parsed.cronExpression via typeof === "string" before accepting it. Non-string values (number, null, object) return a parseError with the offending type. Defends against schedule.json files that happen to round-trip through JSON but have semantically wrong types. 2. Fail installer when schedule parsing reports errors (PRRT_kwDOSF9kNM6B5oEU): main() now returns an exit code (0 on success, 1 if parseErrors > 0). The if (import.meta.main) entrypoint calls process.exit(main()) so CI catches malformed schedule.json files instead of silently counting them in the summary and exiting 0. Verify trace: - bun tools/routines/install.ts (clean run): exits 0, parseErrors=0 ✓ - npx tsc --noEmit: clean on routines files ✓ Co-Authored-By: Claude <noreply@anthropic.com>
…g correction Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f0652b8e14
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ntmatter The memory-index-integrity CI gate requires all four frontmatter fields: name, description, type, created. The prior fix commit (459a511) added the other three fields but omitted created. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…fence layer (#3039) Aaron 2026-05-13 authorized this row ("yes that sounds good about the backlog too") after the same-session research surfaced Cloud Routines as a 4th catch-43 layer Anthropic ships: - Scheduled triggers (hourly/daily/nightly/weekdays/weekly) - API triggers (HTTP POST + bearer token per routine) - GitHub event triggers (pull_request.opened, push, issues, releases, check_runs) Usage caps: Pro 5/day, Max 15/day, Team/Enterprise 25/day. Builds on PR #3034's tools/routines/ substrate (the canonical-source-of-truth two-layer architecture established earlier today for Desktop routines). Cloud Routines extend the same canonical pattern with a sibling cloud-schedule.json file. Killer use case identified: GitHub event triggers unlock per-PR review ticks without any local cron infrastructure — cheap (uses GitHub event quota only, not per-day cron quota) and zero local-process risk. Decomposition hint included (B-0448.1 through B-0448.7) for the implementing slice. Co-authored-by: Claude <noreply@anthropic.com>
…ng, empty cron rejection Four threads addressed (3 P1 + 2 P2 from Codex/Copilot on PR #3034): install.ts: - readFileOrUndefined now distinguishes ENOENT (returns undefined → "missing file") from other errors (re-throws → installer fails loudly). Permission errors, IO failures no longer silently treated as missing. (Codex P2, Copilot P1) - readSchedule now rejects empty cronExpression strings with parseError "cronExpression must be a non-empty string". (Codex P2) autonomous-loop/SKILL.md + README.md: - Remove remaining "Otto" persona attribution per BP "No name attribution in code" on current-state surfaces under tools/. SKILL.md description + first paragraph now use "Autonomous-loop tick" without persona claim. README.md "Ask Otto" rephrased to direct MCP invocation; "Otto bootstream" rephrased to "canonical bootstream" with note that the literal filename is historical-surface substrate. (Copilot P1) Identity-stays-unified substrate (PR #3036) is preserved — the fresh-session that fires this routine identifies as Otto via the bootstream's Part 1, not via the routine prompt itself. Routine prompt is functional description; identity is established at cold-boot. Deferred to follow-up row: bun:test coverage for install.ts (Copilot P1 thread 5). Refactor for testability already shipped (exported pure functions accept directory params); actual test suite is its own task — will file as a separate backlog row. Verify trace: - bun tools/routines/install.ts: exits 0, parseErrors=0 ✓ - npx tsc --noEmit: clean on routines files ✓ Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6702abb4fd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…x P1 + P2) Same silent-failure-prevention pattern as readFileOrUndefined: 1. listRoutines now distinguishes ENOENT (returns empty array — the no
| { | ||
| "taskId": "autonomous-loop", | ||
| "cronExpression": "0 */2 * * *", | ||
| "description": "Otto autonomous-loop tick — fresh-session cold-boot, fires every 2 hours on Desktop", |
| const routines = listRoutines(repoRoutinesDir).filter( | ||
| (id) => id !== "install.ts" && !id.endsWith(".md"), | ||
| ); |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d79784ef53
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| import { homedir } from "node:os"; | ||
|
|
||
| export const DEFAULT_REPO_ROUTINES_DIR = resolve(import.meta.dir); | ||
| export const DEFAULT_RUNTIME_TASKS_DIR = join(homedir(), ".claude", "scheduled-tasks"); |
There was a problem hiding this comment.
Honor CLAUDE_CONFIG_DIR for runtime task location
The installer hardcodes ~/.claude/scheduled-tasks as the runtime path, but Desktop scheduled-task prompts can live under CLAUDE_CONFIG_DIR when that env var is set. In that environment this script writes to the wrong directory, reports success, and leaves the actual scheduled tasks unsynced, so routine updates/deletes in tools/routines will not take effect where Desktop reads them.
Useful? React with 👍 / 👎.
| return 0; | ||
| } | ||
|
|
||
| const results = routines.map((id) => syncRoutine(id, repoRoutinesDir, runtimeTasksDir)); |
There was a problem hiding this comment.
Prune runtime routines removed from canonical source
This flow only iterates routines present in tools/routines and never reconciles extra task directories already present in the runtime tree. If a routine directory is deleted from the canonical source, its old ~/.claude/scheduled-tasks/<id> prompt is left behind and can keep running, which violates the documented canonical→runtime source-of-truth model and creates stale autonomous behavior.
Useful? React with 👍 / 👎.
…rd + memory (#3041) * docs(rules): Otto inter-surface communication channels — reference card + memory Aaron 2026-05-13 asked Otto on both surfaces independently "do yall have a good way of communicating you should make sure and save it for future versions to remember." Two complementary observers landed independent partial lists; this PR synthesizes them as 8 channels in 2 classes (ambient vs explicit) and lands the substrate as both auto-loaded rule + detailed memory. Files: - .claude/rules/otto-channels-reference-card.md (auto-loaded; reference card) - memory/feedback_otto_inter_surface_communication_channels_8_channels_ambient_vs_explicit_aaron_2026_05_13.md (substantive empirical evidence) Ambient channels (state-of-the-world; both Ottos read continuously): Git, .claude/rules/ auto-load, Bootstream, Tick shards, Memory files, PR review threads Explicit channels (active signaling; meant to be observed by peer): Bus envelopes, Claim coordinator, Routines schedule, Aaron as ferry Per Otto on CLI 2026-05-13: "the bus is the explicit channel; git is the ambient one." Empirically validated: today's session exercised all 8 channels — 6 commits on PR #3034 across both processes (zero conflicts via rebase-on-pull), 3 memory files landed, PR #3032 rule auto-loaded for future, 9 bus envelopes scanned, multiple Aaron-as-ferry paste-relays, cross-lane PR thread resolution. Composes with PR #3032 (claim-acquire), PR #3036 (identity-stays-unified), PR #3037 (SENDER_IDS schema), B-0444 (bus envelope worktree field), and the existing wake-time-substrate / glass-halo-bidirectional / substrate-or-it-didn't-happen rules. Co-Authored-By: Claude <noreply@anthropic.com> * fix(rules): correct channel count (8→10) + commit count (6→9) consistency Codex P2 + Copilot P1 findings on PR #3041: - Carved sentence + 'all 8 channels' references inconsistent with enumerated list (actually 10: 6 ambient + 4 explicit) - 'Empirical evidence' section claimed 6 commits but commit table lists 9 Both factual inaccuracies. Aligned references throughout: - '8 channels' → '10 channels' in carved sentence + empirical-evidence header + memory description - '6 commits' → '9 commits (6 by Otto on Desktop + 3 by Otto on CLI surface)' in empirical evidence - 'all 8 channels exercised' → 'all 10 channels exercised' The 'Otto on CLI surfaced 6' / 'Otto on Desktop surfaced 8' counts in the inter-observer comparison are correct as-is — those refer to standalone observation counts per surface, not the combined synthesis (= 10). Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
Summary
tools/routines/as canonical-source-of-truth for Claude Desktop scheduled tasks (the "Routines" sidebar panel — same substrate as thescheduled-tasksMCP server)autonomous-loop— Desktop-side every-2-hour cold-boot tick, complementary to the CLI every-minute<<autonomous-loop>>cron sentineltools/routines/install.ts, Bun, rule-0 compliant) syncs canonical → runtime locationWhy
Aaron 2026-05-13 (this conversation arc, preserved in tick shard):
The
scheduled-tasksMCP server is cross-host — same server wired into both CLI Claude Code and Claude Desktop, both reading/writing~/.claude/scheduled-tasks/. UI Routines panel, MCP tools, raw-file edits, and now git-tracked canonical sources all route into the same disk substrate. Aaron's framing: "looks like there are multiple ways in here."Architecture
tools/routines/<id>/~/.claude/scheduled-tasks/<id>/tools/routines/install.tsEach routine:
SKILL.md(prompt body + YAML frontmatter) +schedule.json(cron + metadata).Three-layer catch-43 defence
* * * * *0 */2 * * *tools/routines/repoEach layer is a different distance from the operative tick — closer = faster recovery, farther = more durable across substrate failure modes.
Composes with
Test plan
bun tools/routines/install.tsfirst run:[updated](whitespace normalization vs MCP-generated file)bun tools/routines/install.tssecond run:[skipped-unchanged](idempotent verify)mcp__scheduled-tasks__list_scheduled_tasks→ autonomous-loop present,enabled: true,nextRunAt 2026-05-13T22:07:13Zbun test tools/routines/install.test.tsonce test suite landsTick shard
docs/hygiene-history/ticks/2026/05/13/2125Z.md
🤖 Generated with Claude Code