Skip to content

Latest commit

 

History

History

claude

README.org

Claude Code configuration

Overview

Google Workspace helpers resolve Epoch OAuth credentials through op-automations cache read, so LaunchAgents that rebuild credentials every few minutes do not exhaust the personal 1Password account’s daily request quota; a grant or client Google rejects is forgotten from the cache and read fresh once, and update-gworkspace-refresh-token forgets the old token after storing a new one. The helpers call the broker by its path in this checkout, because the LaunchAgents that use them do not put the dotfiles bin on PATH. Calendar uses gcalcli-epoch, which validates the canonical grant and atomically refreshes gcalcli’s credential cache before each operation. This prevents a separately expired Calendar grant from blocking automation. The gmail.py --account personal whoami command verifies the authenticated mailbox through Gmail’s profile endpoint using the existing Gmail grant; it does not require a separate OAuth user-info scope. gmail.py --account ACCOUNT preflight reads no credential and calls no API: it checks that the broker command the helper would run resolves on the current PATH. LaunchAgents that send mail register it as their preflight for the launchd repo’s environment check.

This directory contains the tracked parts of my Claude Code setup. The shared Claude/Codex contract lives in the shared configuration guide; this file covers Claude-specific layout, activation, and settings.

Layout and activation

claude/
├── CLAUDE.md          global instructions
├── bin/               service and maintenance commands
├── context/           reference material loaded when needed
├── hooks/             hook implementations
├── skills/            tracked public global skills
├── private-skills/    encrypted skills exposed through local shims
└── README.org

The live ~/.claude/CLAUDE.md and ~/.claude/skills paths are symlinks to the tracked instruction file and skill tree. By contrast, ~/.claude/settings.json is a mutable regular file: Claude rewrites it when settings, permissions, hooks, or plugins change, so a symlink would be unsafe. ~/.claude/settings.local.json holds machine-local permissions and native skill-disable state.

The shared live settings set disableClaudeAiConnectors to true. Claude Code therefore does not load hosted claude.ai connectors or their tool schemas. Service access uses the canonical local CLIs and scripts documented in CLAUDE.md instead.

The shared live settings also set cleanupPeriodDays to 36500. Claude Code otherwise deletes session transcripts under ~/.claude/projects/ (plus tasks, shell snapshots and backups) older than 30 days at every startup. The setting has no “never” value: 0 is rejected or disables transcript writing, so a century stands in for “never”. Codex needs no counterpart: it never prunes ~/.codex/sessions/, and history.jsonl is trimmed only when [history] max_bytes is set, which codex/config.toml leaves unset. The service routing table maps paper downloads and book candidate inspection to paper-fetch; its book acquisition contract and shared bibliography policy keep the procedure and eligibility decisions in one place each.

Per-project configuration belongs in <project>/.claude/. Repo-root .claude/ directories are project-local and are never global symlink targets. Account-specific homes selected with CLAUDE_CONFIG_DIR isolate authentication, projects, history, plugins, and settings while continuing to use deliberately shared tracked artifacts.

Multi-account activation

Six OAuth accounts use separate configuration homes: ~/.claude-personal/, ~/.claude-personal2/, ~/.claude-tlon/, ~/.claude-epoch/, ~/.claude-forethought/, and ~/.claude-fias/. Each keeps its own OAuth identity, Keychain credentials, and .claude.json. Settings, skills, projects, history, and plugins are shared through symlinks to ~/.claude/. The account homes do not contain a second CLAUDE.md, because the user-level file is already loaded from ~/.claude/.

The selected account is stored in ~/.claude-current-account. Emacs reads that selection through agent-claude-start-or-switch and changes it with agent-claude-select-account; terminal aliases select the same account homes. Before launching a newly configured account, run agent-claude-init-account so its shared paths are symlinks from the start. Otherwise Claude may create divergent regular settings and history files on first login. The initializer heals untouched files but leaves non-virgin files for explicit migration.

The claude PATH shim removes ambient ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN because they take precedence over the selected OAuth account. CLAUDE_CODE_ALLOW_API_KEY_AUTH=1 is reserved for a deliberate API-key-authenticated Claude session.

Instructions and settings

The “Do the work yourself” rule in CLAUDE.md (mirrored in codex/AGENTS.md) gained a hand-off clause on 2026-10-10: when a step needs Pablo, the agent first does everything around it (opens the page in its own tab, fills permitted fields, puts secrets on the clipboard, places the cursor) and then gives numbered click-by-click instructions naming window, tab, field and button. Origin: during the Forethought meeting-debrief build Pablo repeatedly received instructions that began “open …” for steps the agent could have prepared itself.

The kill-ring rule in CLAUDE.md (mirrored in codex/AGENTS.md) gained an exception on 2026-10-27: draft replies to other people that come out of inbox-monitor work go in the project’s drafts file (~/.local/share/inbox-monitor/drafts/PROJECT.md, format in context/service-access.md), with the context needed to decide each one, never in the kill ring. Origin: a FIAS treasury session stacked several WhatsApp and Slack drafts in the kill ring, and Pablo could not tell which reply was which or what each one needed from him.

Two operating rules in CLAUDE.md (mirrored in codex/AGENTS.md) were added on 2026-10-27: an agent must name and get approval for any edit that reverses an agreed rule or decision in a governing document, and a rule that leaves a whole category unable to qualify is raised, not used to prune. Origin: a Codex session narrowing the noise-model work replaced the per-platform hotel and Airbnb noise thresholds with one 2% value in the accommodation profile and booking workflow without telling Pablo, and the next booking search pruned every hotel on that basis.

Unfamiliar code execution uses the shared bin/untrusted-run VM boundary; reviewed local workflows retain host execution. CLAUDE.md states only the routing rule; staging and trust details live in the runner contract. The guard engine’s untrusted-execution policy, run for every Bash call, denies recognizable untrusted launchers and execution from the designated staging directory. A variable executable or cd target assigned a literal earlier in the same command is classified as that literal; a quoted heredoc after cd dir; or read by git or gh is data, as on its own line. The installed /opt/homebrew/bin/trash may move literal absolute staging paths with optional -v or --; execution checks still apply, and other trash executables or modes remain outside this narrow cleanup allowance. The LaunchAgent registry guard also denies removing a live plist by hand, even with its override; ~/repos/launchd/bin/launchd-retire.py retires a job and its registry entry together, and launchd-reload.py reloads one and records its load time. See the hook contracts for coverage and the runner contract for available runtimes and prerequisites.

claude/CLAUDE.md is the user-level instruction source. A project may add a sibling CLAUDE.md and project-local settings. User and project settings are additive. Account selection changes which user-level native state is active; it does not introduce a third precedence layer. Effective behavior therefore depends on both tracked instructions and the selected live state.

The paired find-past-session skill directs agents looking for a past session to claude/bin/agent-log-find, which searches the rendered Markdown logs under ~/.claude/rendered/ (both backends, with summaries) instead of raw JSONL transcripts.

Operating rules in CLAUDE.md stay short and state the behavior directly. The file is a lean always-loaded instruction source, not a manual: detail belongs in skills and context files that load on demand. User-facing replies lead with the answer and stay plain and brief: short sentences, no preamble, and nothing that does not change what the user does or decides.

Agents solve classes, not instances: a recurring concern such as credentials or alerting follows one written principle across every project, and a fix in one project starts by checking how its siblings handle the same thing.

Any message an agent itself sends under Pablo’s name, to anyone, starts with [agent] =; drafts Pablo sends himself, such as text staged in the kill ring, and messages he approved in a review file (a =send comment in an inbox-monitor drafts file), which he has already reviewed, carry no prefix (2026-10-30). Peter proposed the prefix for messages to him on Slack on 2026-10-27 after an agent-written question reached him unmarked; Pablo extended it to every recipient on 2026-10-29, before an agent was to message Will MacAskill. The rule sits in the always-loaded file because such messages can be drafted from any project and go out on any channel. The send tools enforce it (claude/bin/agent_prefix.py, 2026-10-30): wa-send, slack.py send and gmail.py send|reply add the prefix unless given --reviewed FILE, and then refuse text that is not in that review file. This followed a chat-approved message that went out unmarked the same day.

Agents ask for a task’s decisions once: before acting, they list every decision and approval the task needs through to completion, with a recommendation each, and after approval carry the task to the end. They come back only for a step that needs the user in person or a decision they could not foresee, and then list every open decision again. This replaced a rule that restated the open list whenever any item came up, which drip-fed decisions across turns (2026-10-01). From approval until the task closes, every reply ends with one status line (done, done pending verification, waiting on the user, or in progress), because replies after approval often left unclear whether anything was still open (2026-10-07).

Main agents running inside Emacs publish concise current-state descriptions through bin/agent-status at task boundaries and meaningful phase changes. The global instructions keep only that trigger; the script’s --help carries the wording and failure rules. These descriptions appear beside the session summary in the agent menu; subagents must not overwrite their parent’s status. A failed publish usually means Emacs was briefly busy, so agents keep publishing afterwards.

The global instructions state the repository-root contract shared with the Codex side: every code repository lives outside Drive under ~/repos/ (Epoch repositories under ~/repos/epoch/). The dotfiles and notes workspaces such as Epoch/ and Forethought/ are the exception: their working trees stay in Drive and their git directories live in ~/git-dirs/<name>. Nothing under the Drive sync root may hold dependencies, builds, caches, virtual environments, or worktrees, and every linked worktree lives under ~/repos/.worktrees/<repo>/<name>. The 108 repositories that used to sit under ~/My Drive/repos/ were moved on 2026-08-03; the hooks that named a repository path were updated in the same change. Python entry scripts that import dotfiles modules set sys.dont_write_bytecode themselves, because launchd jobs and GUI-launched apps do not inherit PYTHONDONTWRITEBYTECODE from .zshenv.

The live global settings register hooks by absolute path. Stop forwarding to Emacs is a turn-end signal, not proof that the agent process exited. Reusable lesson capture belongs to the Emacs agent-before-exit flow and must not be silently reintroduced as a Stop hook. The permission mode is automatic: guards return allow or deny and never prompt as an approval mechanism.

Auto mode routes every non-read tool call through Anthropic’s server-side classifier, which reads only the user-level ~/.claude/settings.json (never project or local settings) and drops broad Bash allow rules. Its false positives are tuned with the autoMode block, whose source of truth is claude/auto-mode.json (git-crypt encrypted; unlock the checkout before using apply-auto-mode): environment entries describe the trust boundary (single-user laptop, the user’s repositories as project scope, 1Password via the brokers for Epoch, personal and Tlon secrets, the Epoch services the user administers, Emacs agent orchestration) and allow entries name the built-in soft-deny rules that misfire on that work. Both lists begin with "$defaults" so the built-in rules stay and keep updating. claude/bin/apply-auto-mode copies the block into the live settings with a backup, and --check reports drift; run it yourself, because the classifier’s Self-Modification rule refuses to let a session widen its own permissions. Add an entry only for a class of false positive that has actually recurred, and phrase it as the action and why it is inside the boundary. Change the tracked file, not the live block: an entry added only to the live settings is deleted by the next apply-auto-mode (the dotfiles-publication entry was recovered into the tracked file on 2026-10-01 for this reason).

The secret guard inspects replacement text and notebook cell source as well as whole-file writes. Security-hook bootstrap and unexpected runtime failures block with exit code 2; the hook contracts describe the covered guards. The regression fixtures check malformed input, missing dependencies, missing startup helpers, and benign or secret-shaped replacement text without executing the proposed tools. These guards run with the agent’s OS user and filesystem access.

Claude-specific browser, OAuth, hosted connector, and account state stay in Claude’s native stores. Shared MCP wiring is audited only when it is declared in ai-config-sync.json; placement and account guidance lives in the MCP reference, whose inventory lists each project-local server (including Notion for sessions under ~/repos/forethought).

Instruction files, skills, and hook reminders state current rules with their reasons and without incident history, migration-relative wording, shouted emphasis, or numeric output caps. The prompt audit recorded in the 2026-09-02 prompt-audit plan lists the removed patterns and the items left in place on purpose, including the deliberate model: aliases on the GitGuardian and publication skills. The proofread skill pins no model and runs on the session model.

Commands and context

The command index documents the tracked service clients, wrappers, and maintenance tools. It is the owner for command behavior; this overview does not duplicate a command-by-command catalog.

The ai-provider-key helper owns the autonomous spend ceilings for automation runtime keys directly.

The context index routes account, service, browser, secret, alerting and MCP procedures. Two cross-project principles were added on 2026-10-09 after The Hinge missed an issue: unattended programs renew their own sessions (context/secrets.md), and automations alert through the shared launchd-heartbeat service (context/alerting.md). Both are delivered by the automate skill’s scheduled-execution section (Claude and Codex copies), not by the always-loaded CLAUDE.md, per agents/instruction-placement.org. On 2026-10-10 the Forethought Gmail account joined mu4e: context/secrets.md records the exception that places Forethought credentials programs read in the personal Automation vault (Forethought’s 1Password has no Automation vault or service account), context/google-services.md and context/service-access.md describe gmail-maildir-sync --account forethought, and bin/update-gworkspace-refresh-token gained --account forethought, --no-browser, item creation on first grant, and a check that refuses to store a token granted for a different address. The same exception later took the Fireflies and Granola keys and the Notion OAuth client of Peter Hartree’s task-manager skill. Later that day context/secrets.md made the Forethought Employee vault the default for Forethought secrets read interactively (through op-desktop with the Forethought account), keeping the personal Automation vault only for those programs read unattended; moving the Notion OAuth client is tracked there as a TODO. On 2026-10-18 context/lulu-firewall.md recorded that the LuLu outbound firewall blocks a Homebrew binary after an upgrade changes its path (git 2.56.0_1 could not reach GitHub). The diagnose skill (Claude and Codex copies) now points to the context index for such known local causes; bin/lulu-homebrew-rules and a hold in bin/personal-updates now keep upgrades from stranding the rules. The gitignored context/personal-details.md holds Pablo’s form-filling details (name, date of birth, contact, address, phone device); like voice-samples.md, it is private, Drive-synced and never committed. The chrome-use skill points to it for form filling and has agents leave card numbers, passport numbers and passwords for Pablo, saying so up front instead of asking permission to type them; the context index lists only tracked documents, so it is not indexed. CLAUDE.md points to these files by absolute path: it loads as ~/.claude/CLAUDE.md, where a relative context/ path would not resolve. The sync audit rejects relative context/ references in both global files. Sensitive procedures should be loaded only when the task needs them. In particular, read the secrets context before handling credentials rather than copying credential paths or token recipes into this public overview.

Skills

The Claude skill index documents discovery, pairing, private and programmatic roots, and maintenance. The generated tracked skill inventory is the repository-wide catalog. It covers tracked public skills only; runtime plugins, private skills, ignored system content, and skills owned by other repositories are outside its scope.

The emacs-freeze skill moved from emacs/.claude/skills/ to the global root on 2026-10-10: freezes are reported from sessions in any project, and a project-scoped skill is invisible to a session started at the repository root. It reads emacs/freeze-incidents.org first, so that remedies already tried are not proposed again.

The handoff, live-session-status and message-agent-session skills ship with the agent Emacs package, in etc/skills/ of its checkout, and are not tracked here. M-x agent-setup links them into this directory’s skills/; the links are ignored by Git. bin/agent-send runs the package’s tool of the same name.

The order-salads skill polls the supplier’s WhatsApp chat about every 5 minutes while waiting for a ticket, not every minute. It books Uber within the fare cap under a standing terms approval, without asking again. Its browser-flow.md records the tested routes: saving the Mercado Pago receipt PDF from the in-page viewer with a page script, attaching it to WhatsApp without the native file picker, deleting the PDF from Downloads once sent, and committing only the agent’s own ledger hunk.

The org-note-conventions skill applies only to personal notes. It distinguishes root-backed and rootless heading layouts, preserves established structure, and joins soft prose wrapping without removing intentional Org hard line breaks.

The overnight-todos skill — disabled, see skills-disabled.json — requires an explicit personal-TODO run. Its default dry run classifies metadata without reading note bodies or starting workers. Act mode uses private outputs, token-based task accounting and an idempotent ledger; only verified outcomes justify completion, and budget limits produce deferrals. Existing ledger history requires explicit migration, not a reset.

The overnight-watch skill runs in its own session while Pablo sleeps. Its watch_snapshot.py reads every live agent.el session’s transcript, subagent activity and terminal screen; faint composer text is a prompt suggestion, not input. When the Emacs server refuses connections it falls back, with a warning, to the last session list it cached and reads transcripts only. It nudges stuck sessions through agent-send once per cause and never answers decisions, permission dialogs or menus that are Pablo’s. Nudges are one line pointing to a file, because long messages have arrived truncated; a kill its own permission mode refuses is reported, never routed through another session. A nudge’s effect, and any outcome the morning report would call unknown, is checked by searching the sessions’ transcripts. It depends on the agent package’s agent-send.

The paste-via-kill-ring skill separates manual handoff from sending and is one command: claude/bin/kill-ring-put reads the text from stdin or a file, finds the running Emacs server socket, and verifies exact non-secret ring placement without implicit clipboard forwarding, honoring text-transform acceptance and never echoing the payload. Outside Emacs (no INSIDE_EMACS, CLAUDE_BUFFER_NAME or CODEX_BUFFER_NAME) it also copies to the clipboard, since a plain terminal pastes only from there. The Lisp form and private temp-file handling live in the helper, not the skill text. Credentials follow the secrets workflow, not the persisted Emacs kill ring; clipboard placement (--clipboard) is reported separately. The agent shows the text only after staging it, as the global rule requires.

The personalize skill keeps private voice examples out of public artifacts and separates voice matching from factual or personal claims. Missing samples are explicit; concision preserves required answers, and prose preferences do not alter quotations, code or links. Drafting does not authorize sending; a Slack draft is staged in its composer in the same turn, through the workspace’s Slack drafting skill, and once Pablo approves it the agent sends it with slack.py send.

The pin-elisp-pr skill — disabled, see skills-disabled.json — preserves one effective installation owner per active profile, including explicit-owner/config-only timing exceptions. It reuses the shared PR and profile lifecycle and distinguishes a configured recipe from an installed revision and verified loaded behavior.

The post-push-ci skill observes an exact pushed target without pushing again. Its helper reports a bounded run snapshot; expected-check coverage, run attempts and pending or non-success outcomes remain explicit. Status checks alone do not authorize repairs, reruns or publication. Attempt inspection validates GitHub’s attempt-specific URL against the listed run and exact attempt.

The pr-audit skill binds the requested PR and tested artifact to frozen base/head revisions. It separates commit selection from the net PR patch, reviews merge parents explicitly, and keeps readiness evidence distinct from permission to repair, rewrite history or publish.

The project-local config-audit respects a requested subset and includes procedural bodies when full skill audits are requested. It distinguishes skill discovery from instruction loading and checks actual hook coverage before recommending removal of an overlapping instruction. Its context-delivery criterion checks that each rule reaches the agent when needed (resolving pointers, skill triggers, hook messages), flags unregistered or over-blocking hooks from rendered-log BLOCKED counts, and requires a caller search before any deletion.

The project-local find-skills — disabled, see skills-disabled.json — checks the selected agent and project/global destination before installation. It preserves canonical paired roots and uses copies for Drive-backed destinations, then resolves the installed skill to verify discovery.

The project-local gitguardian-triage separates read-only classification from authorized rotation and closure, verifies exact credential ownership and consumers, and treats ambiguous provider responses as inconclusive. Its bin/gitguardian-incidents reader rejects HTTP redirects so authenticated requests cannot forward credentials to another endpoint.

The project-local install-mcp-server — disabled, see skills-disabled.json — preserves documented endpoint paths and uses explicit secret resolution: Claude expands environment references but does not dereference op:// values. It distinguishes configured, authenticated, and tool-tested states before reporting readiness.

The project-local optimize-agent-instructions — disabled, see skills-disabled.json — distinguishes always-loaded imports from references read on demand. It preserves useful instructions until replacement enforcement and discovery have been checked for each target runtime.

The project-local skill-resolver — disabled, see skills-disabled.json — honors exact session-catalog paths before name lookup and keeps project edits in their original scope. Its filesystem inventory is distinct from the runtime’s enabled skill catalog; account and installed plugin-version evidence determine which sources it searches.

The project-local skill-audit remains explicit-invocation-only. Its historical collector distinguishes failed reads from successful output envelopes, preserves personal/plugin identities and exact name boundaries, and handles task-start context, hidden inputs, and fractional timestamps. Counts remain reconstructed evidence, not native invocation telemetry or proof of workflow completion.

The dx-audit skill distinguishes observed collaboration friction from imagined or unmeasured gaps. Its bounded implementation mode accepts plain-language requests, respects runtime-specific instruction discovery and checks command effects before running diagnostics.

The dotfiles-context skill resolves canonical sources without guessing from stale profile directories. It checks buffer and profile identity before tangling, preserves runtime-specific pairing, and binds local PR pins to the exact reviewed change while keeping generated files distinct from live activation. Agent-facing guidance changes go through the paired instruction-placement skill instead.

The instruction-placement skill delivers the canonical placement policy, agents/instruction-placement.org, at the moment an agent decides to persist a rule, preference, workflow or fact for future sessions, in any project. Its description carries the trigger; its body only points to the policy by absolute path, so the policy stays the single source. The shared rebuild helper requires runtime-, source- and token-bound completion evidence. It rejects unqualified cached success and keeps uncertain requests recorded so cooperating callers cannot silently launch a competing rebuild. If the original waiter exits, an observer can acquire its now-free package lock and certify the same completion token after rechecking the recorded source, runtime and status identity. It neither replaces an active owner nor requests another rebuild when an existing completion token can be certified. If the request reply was lost before its token was recorded, the helper checks the live package under the exclusive owner lock. Only an idle package with no queued tokens and unchanged source, runtime and status permits archiving that abandoned request and starting a fresh bound rebuild. The abandoned request never counts as completion evidence. A pending status alongside a terminal previous owner can be a producer startup gap: the observer waits for the new owner instead of resuming the old token. Active requests still require the exact recorded source, runtime and status identity. Commit and rewrite hooks leave every status write to the locked helper and observe an existing request, so an amend cannot overwrite its first rebuild’s status or force a second rebuild. Each hook keeps its own diagnostic log. For the legacy overwritten-pending state, the helper requires the original token to be terminal and the same runtime to have no queued or unknown package requests. It archives the rejected owner and replacement status identity, then requires a fresh rebuild; the overwritten completion never becomes evidence.

The end-to-end skill separates disposable test fixtures from retained results, checks startup effects and runtime identity, and accepts automation that observes the real criterion. Historical scheduler evidence and black-box service checks are credited only for what they establish; retry cohorts cannot erase failures.

The elisp-conventions skill distinguishes registry IDs, checkout evidence labels, unmanaged packages and non-package files. Batch and ERT runners use a clean source-first Emacs without deleting bytecode or falling back to the package’s build. Staged project checks validate a private indexed snapshot, not unrelated working-tree bytes. A file rename does not authorize whole-package unloading, and a metadata predicate does not prove a live menu works. Its coding style forbids network waits on the main thread from timers, sentinels, filters and hooks, after the 2026-10-25 freezes. Connections that cannot leave Lisp, such as forge’s and Slack’s, may instead probe the host from a subprocess with simple-extras-probe-url, as a recorded exception.

The documentation guard reads generated-output declarations from the proposed commit’s manual bytes, including explicit selections and inherited amendments. It handles independent Texinfo/Info names without evaluating Org. Unrelated unstaged headers cannot hide outputs; ambiguous pending staging is refused with a reason to stage the intended manual separately.

Automatic manual exports compare source identity and content, allowing metadata-only changes during export. Replaced sources, changed contents and symlinks remain rejected; generated destinations retain strict state checks.

Elisp helpers under standard skill roots’ scripts/ directories, including project-local .claude/skills/ and .codex/skills/, require their own changed SKILL.md or references/*.md in the selected commit. An unrelated package manual or another skill’s documentation cannot satisfy that requirement. Mixed package changes still need their manual; unresolved combined staging of skill helpers must be split into separate staging and commit calls. Candidate indexes preserve Git’s original index timestamp so rapid same-size source edits remain visible to --all and --include checks.

The document-elisp-package skill preserves established manual paths, credits, anchors and export identities. It separates documentation from new save automation and documents supported public contracts without evaluating package source. Automatic exports preserve validated declared sibling names, including a README.org manual that generates package-named Texinfo and Info artifacts. Private staging also preserves those basenames inside the generated Info file’s navigation headers.

The document-elisp-extras batch separates coverage-only reports from authorized manual updates, accounts for every source, and preserves per-package ownership. Exports use reviewed destinations and distinguish format generation from accuracy.

The diagnose skill applies whenever unintended behavior has no established cause. Before reporting, it has a fresh subagent challenge the draft diagnosis. It separates configured, loaded, triggered and acted-on evidence for the incident’s actual runtime. It preserves uncertain/mixed causes, requires practical enforcement coverage, and tests legitimate and unsafe guard cases.

The design-audit skill accepts explicit plain-language refactoring requests, requires concrete maintenance benefits, and preserves runtime/public contracts. Counts, local call searches and single implementations alone do not justify edits.

The code-audit skill requires supported failure evidence, distinguishes defects from generic hardening, and checks exact failing behavior after fixes. Audit-only checks cannot smuggle in snapshot rewrites, installs or external side effects.

The chrome-permission-audit helper defaults to non-recording reads and requires exclusive closed-browser store access. Revocations consume reviewed exact plans, preserve deny/transient records, and retain permission-key-only recovery backups. Its classification rules do not infer safety from generic documentation hosts. Owner-specific rules live in ~/.claude/chrome-permission-audit/private-rules.json; changes to them invalidate previously reviewed revocation plans.

The build skill preserves the requested spec/planning/implementation endpoint. It investigates before interviewing, records decisions incrementally, separates real blockers from assumptions, and keeps execution plans tied to the accepted spec.

The automate skill distinguishes advice, construction and runtime activation. It chooses among deterministic, gptel, skill and scheduled implementations, preserves scope and authorization, and verifies loading separately from behavior.

The new-emacs-package workflow (2026-11-01) takes a new standalone package from name to a public, documented and registered repository. It only sequences the skills that own each step (elisp-conventions, document-elisp-package, generate-readme, dotfiles-context, add-to-emacs-packages, release-package), after collecting every decision up front.

The add-to-emacs-packages workflow checks the list, existing manual and profile card independently. It resolves canonical sources, preserves note IDs and keeps local registration separate from clone and publication authorization. It also sets the repository’s About description, manual link and topics.

The clean-citations workflow resolves source references in selected personal Org notes, adds missing works through add-bib-entry, and preserves verified locators and resumable progress. The shared bibliography policy owns work and edition selection, metadata quality, and book PDF eligibility. Structural validation and hash-bound book reviews complement source-based semantic review.

The add-bib-entry workflow stays enabled across sessions and follows zotra-extras-add-entry with the actual ebib-extras-process-entry command. It resolves active bibliography state and serializes attachment callbacks. Ad-hoc calls use emacs-eval to bound results before the live server prints them; complete selected-entry exports use UTF-8 files. It reviews all fields before and after processing with bib-entry-check and the shared policy. Book candidates, including registered publisher/archive PDFs, are inspected before attachment through paper-fetch; existing nonempty abstracts still require semantic review. Article PDF downloads go through paper-fetch; metadata, explicit attachment and entry processing stay in Emacs. The skill has no separate downloader or BibTeX writer.

The download-paper workflow obtains one paper PDF by DOI, URL, arXiv id, md5 or title through bin/paper-fetch, the single paper-acquisition tool (shared logic in lib/python/paper_fetch.py). The skill names no other downloader: the website’s legacy download-missing-pdfs.py CLI is retired. The tool exhausts every route that works from a shell and verifies the file’s identity; the skill adds the one step the tool cannot take, a real Chrome session for hosts behind a bot challenge, and the exact order that step needs. For Anna’s Archive SciDB records it clicks the page’s Download link and falls back to the page’s Sci-Hub link when the partner server cannot serve the file. It reports the tool’s status taxonomy verbatim and hands a staged file to add-bib-entry for attachment. Its browser step loads chrome-use and uses Claude in Chrome, the Codex CLI’s Playwright MCP browser_* tools, or the Codex desktop app’s control-chrome. The tool’s LibGen route works where the ISP’s resolver blocks LibGen (it falls back to DNS-over-HTTPS) and downloads LibGen’s md5s from LibGen’s own mirror before spending Anna’s fast-download quota. Anna’s Archive’s only API is the member fast-download endpoint, which takes an md5; it has no search API, so its book search runs in Chrome and paper-fetch book-candidates --annas-search-html consumes the saved page, while LibGen’s unchallenged search covers the CLI. Before attachment it stores the verified document language; PDF processing keeps the intended file and language across buffer switches without a language prompt. The agent retains one explicit entry/database operation through attachment, abstract generation and OCR, and polls its own pending task count. Existing abstracts are preserved; unresolved choices and conflicting edits produce a blocked result. After resolving a failure and draining callbacks, retry the existing entry with a new operation. Agent runs load the database in the background without selecting Ebib entries, displaying PDFs or asking questions. Manual commands retain their interactive behavior. No global input functions are rebound. Metadata corrections use Ebib’s bracing field setter and contextual save wrapper; verification includes parsing the saved entry back to its original field values.

Paired configuration audits allow matching resource deletions within retained skills while still rejecting one-sided resource or whole-skill deletions.

Both interactive and programmatic walk-list copies use exact input identity and locked lifecycle transitions. Native atomic exchange detects displaced-file races and retains uncertain recovery data; new stored copies and evidence use owner-only mode bits. Workers must finish or stop before claim reuse. Reconcile lost command acknowledgements before retrying, and distinguish a fully recorded queue from successful task outcomes.

The programmatic Hartree trial queue reviews source before execution, keeps trial dependencies outside Drive, and promotes only a user-accepted candidate. Verdicts, pending evidence and prior verdict history live in one private progress file outside the repository: ~/.local/state/agent-skills/try-hartree-skills.json, overridable with HARTREE_SKILLS_STATE. Public sources contain only the candidate queue and procedure; a completed private queue does not restart itself.

The programmatic open-session-log helper resolves the current runtime’s session identity and exact transcript in its active account root. It opens that path in Emacs and checks the rendered buffer’s identity before reporting success; concurrent sessions cannot be selected by recency or project alone.

The profile-ai-cli-performance skill measures selected client conditions with exact terminal-success evidence and nullable, failure-aware paired metrics. Dry runs do not access credentials or launch clients; selected conditions alone determine dependencies. Unsafe token duplication and identity/provider changes cannot be hidden as clean baselines. Private reports preserve incomplete cohorts and unobserved isolation; timing completion does not establish a causal claim.

The paste-via-kill-ring skill owns the procedure for handing over text the user must paste himself (Emacs kill ring, clipboard for Chrome or native forms, opening the target thread). The global instructions only point at it, and the service-access routing table lives in context/service-access.md for the same reason: procedures load on demand, while the always-loaded file keeps one trigger line each. The service-access trigger names services only (Signal, Fireflies and Granola joined it on 2026-10-10, routed to the read-only bin/sgn-read, bin/fireflies and bin/granola; Granola left it on 2026-10-20, when its API access ended, and rejoined the same day with a key for the BAISH workspace); browser work reaches its rules through the chrome-use skill’s own trigger. The paired chrome-use skill, which loads before any browser step, owns the browser rules: background research stays off live Chrome, standing authorization for logged-in sites, each agent’s browser control, closing every tab an agent opens (since 2026-10-19, after a download run left tabs in Pablo’s windows), never stealing focus (the agent’s own tab or tab group comes only from the browser tool’s own calls, work only in those tabs, no other new windows, including visible windows opened by Playwright or similar scripts, no activating windows, chrome-profile-open only for manual launching), and one retry policy that separates connection failures (governed by chrome-bridge-recover.py for Claude) from page/REPL stalls. It takes precedence over the vendor Chrome skills where they differ.

The service-access and Google account reference also record the dedicated Forethought Chrome profile (forethought.org, pablo@forethought.org). Forethought browser work uses that profile. Of the Google CLIs, gdoc --account forethought (since 2026-10-10) and gmail-maildir-sync --account forethought have Forethought authentication; the others do not. Personal-profile guest access to the onboarding checklist is a separate, explicitly permitted route.

The local gdoc extension (claude/bin/gdoc_download.py, run by bin/gdoc) adds download and makes write and push refuse an empty source file unless given --allow-empty, after a parsing slip blanked a live doc on 2026-10-11. It also makes cat append the doc’s comment threads, open and resolved, unless given --no-comments: on 2026-10-14 a read of the retreat doc missed 232 threads that bore on the task. With --tab, threads are placed by where their quoted text occurs (the Drive API doesn’t say which tab a comment is on); those on other tabs, or whose quote is gone, are counted rather than shown. --plain and --revision output stays unchanged, with a note on stderr. Suggestion threads remain out of reach of the API. The Google account reference’s “Editing Google Docs” section says which gdoc command fits which kind of change; the service-access table points there.

Browser control is runtime-owned rather than mirrored through a tracked Codex-App handoff skill. Claude therefore carries no pointer skill or helper command for moving a Codex thread into the desktop app.

The move-session-log adapter reassigns individual resumable sessions or whole renamed projects by moving verified Claude transcript/sidecar owners and selected history metadata. Preview precedes apply; offline stores, writer checks and private recovery backups protect against overwrite and lost appends. Encoded-path and destination collisions fail before mutation, as does a backup directory whose parent does not yet exist, each with a named reason. Historical tool records remain unchanged, and project trust/settings remain untouched unless separately authorized. A recovery journal records partial failures; metadata counts alone do not establish live resume behavior. Its --rename-history mode moves prompt-history entries of a project whose transcripts no longer exist, once no old bucket remains. bin/migrate-profile uses both modes to move every retired Emacs profile’s sessions into the current profile. A transcript still naming the old project whose history already names the migration target counts as a half-applied earlier migration and is completed; history naming any third project still refuses. Origin: on 2026-09-27 the 8.3.0-dev buckets reappeared after their history had been rewritten, blocking 35 sessions. A rename also accepts workflow-run agents under subagents/workflows/<run>/agent-*.jsonl (their journal.jsonl stays opaque), a transcript whose own cwd is exactly the old project even when its prompt history names another directory, and a title-only stub transcript, which belongs to its bucket. The writer check reads lsof’s *NNN form for descriptors above 999. Origin: the 2026-10-06 workspace split refused seven project buckets on these shapes, so resuming those sessions still opened the pre-split directory. A session resumed from another directory has one transcript per bucket under one UUID. A transcript’s origin is accepted when any history entry for that session names it, not only the first, so such a split no longer blocks imports into either bucket. The paired Codex adapter allows single closed-session imports while unrelated sessions remain open, using SQLite transactions and consistent recovery backups. Its bulk rename mode retains the full-store offline checks. Codex relocation also reconciles the exact session’s Emacs buffer and live working directory, rather than treating a history-listing change as completion. When Agent Log is the entry point, its cached catalog, rendered file location and actual agent-log-resume-session command are checked together.

Model-invocable tracked skills do not preapprove bare high-risk tools through allowed-tools. Loading a skill therefore leaves Bash, write, edit, agent, and user-question permission decisions to the ordinary runtime policy. A narrowly scoped Bash pattern may be declared when a skill genuinely needs one.

The paired publish-dotfiles skill is an ignored local workflow, excluded from this repository’s public distribution. The public publication guard helpers scan the full history automatically and require manual review of all findings, risky and opaque source objects, and known manual incidents. Exhaustive patch review applies to outgoing commits, including comparisons with every merge parent. Scanner findings carry fingerprint-redacted source context. Runs, manifests and audit receipts bind the review-policy version and its scope. Raw scanner reports are confined to a private off-tree temporary workspace. Git scans use the same zero-context patch stream as Gitleaks and never reinterpret patch coordinates as blob lines; directory scans use authoritative source lines and advisory byte-column spans. Schema-4 runs separate scrub values from keyed review signatures. Review writes are locked so bounded agent pools cannot overwrite one another’s verdicts.

Readiness uses an enforced non-publishing scan mode that preserves refs and FETCH_HEAD. Full-audit receipts age from the scan and cannot clear later incident invalidations; missing advertised public objects fail the audit closed. Release authorization binds the exact tag name reviewed by the scan. Read-only checks never authorize history repair or provider-side credential changes. Audit receipts bind the reviewed scan and manifest, including across concurrent same-boundary rescans; earlier clean status cannot certify a replacement.

The paired release-dotfiles skill preserves the profile-test gate, excludes the read-only Elpaca mirror from package pushes, and verifies live recipes before serializing a lockfile. It uses supported GitHub PR fields, strict release tags, path-only version commits, and the same approved notes file for review and release creation. Preparation alone grants no publication authority.

The paired verify skill maps actual requirements to version-bound evidence. Verification-only requests do not authorize repair. Human judgment and missing evidence stay visible rather than becoming agent-approved proxies. Criteria cannot be relaxed to obtain a pass, and completing a review does not certify that its target is correct. Sampling and fixture limits remain explicit.

The paired test-suite skill binds tests to the intended source and independent behavior contracts, isolates side effects, and checks discovery and failure sensitivity. Tests-only work preserves product code and reports valid failing regressions without claiming a green suite or a repaired bug.

The paired symptom-check skill — disabled, see skills-disabled.json — evaluates a proposed repair against a supported invariant and related code paths. It preserves read-only diagnosis, allows an explicitly pending disposition, and requires direct symptom evidence before claiming repair or closing an architectural issue.

The paired session-learning-capture skill records proposals only after checking session provenance and permission to use the Drive-synced personal inbox. Stable full-session record keys and no-clobber publication protect retries; capture does not promote lessons or imply that a review consumer is installed. Records are read and published only through the bundled scripts/learning-record helper, which owns the per-record lock so that no hand-written lock cleanup triggers a permission prompt.

The paired release-package skill distinguishes candidate audits and preparation from publication. An explicit release request is itself the authorization to publish that one package, so the skill reports its plan and proceeds instead of asking again. It binds versions, notes, checks and exact branch/tag objects, compares versions without losing patch components, and reconciles partial releases without silently widening push destinations. When CI fails on the pushed release commit, it repairs the branch and moves the tag only while no GitHub release exists for it; a published release tag is never moved.

Some retained local workflows are encrypted with git-crypt at their existing skill paths. Their working instructions remain available in an unlocked checkout; the encrypted bodies are not public skill catalog entries. Personal ordering workflows use this layout too, keeping contact and delivery details encrypted without adding symlinks inside the Drive tree. Standing delivery spending authorizations live in those encrypted instructions, not the public catalog. Ledger templates with personal account names remain alongside the encrypted workflow. Payment preparation stops at review; the user executes account transfers. Quantity conventions, ticket-handling rules, receipt-sharing authorization and message templates belong in the encrypted workflow as well. Tested browser routes and UI recovery notes remain in its encrypted supporting reference. Authorized ledger recording uses confirmed charges and preserves pending work across invocations. Pickup instructions use the dedicated form field, with no meeting-option or chat substitution. Contact-sharing permissions, terms-acceptance preferences and receipt retrieval guidance stay there too.

The paired record-decisions skill records evidenced choices and dated amendments without rewriting decision history. It preserves numeric IDs beyond three digits, keeps summaries consistent with records, and leaves no-op runs unchanged. Reading the skill does not authorize writes or imply automatic summary loading.

The paired proofread skill resolves one external Node dependency runtime for both setup and execution, canonicalizing both its root and actual node_modules destination before rejecting paths into this Drive-synced tree. Only the LLM engine consumes an injected provider key; skill-local plaintext .env files are no longer loaded. Review-only work does not write copies or imply provider transfer. Editing preserves source files, requires validated literal changes, and distinguishes engine failures and protected-syntax gaps from clean results. The runtime installs with npm ci --ignore-scripts from the frozen package-lock.json (its only lockfile), honoring the user’s npm configuration and release-age gate; bump a dependency with npm update PKG --package-lock-only.

The paired personalize skill carries a hard concision rule: outgoing drafts carry only what the recipient needs to act or decide, lead with the ask, and hold Slack replies to a few sentences. Short chat replies take a fast path that skips the humanize catalogue, whose clusters cannot form in a few sentences, so a one-line reply is one drafting step and one staging command. It also enforces act-first replies: the draft never says Pablo will do something he can do now; the work happens first and the reply reports it done.

The lint-elisp runner uses literal file arguments, private bytecode output and separate compiler/checkdoc completion records. Errors and skipped compilation cannot become clean results. Its fixes preserve binding and initialization semantics, and source-first batch diagnostics do not prove live behavior.

The interpretability-audit skill distinguishes sampled from exhaustive coverage, accepts plain-language fix requests, and grounds clarity findings in actual reader misunderstandings. Renames and extractions require contract checks; unknown rationale does not become an invented comment, and audit-only checks cannot rewrite files.

The paired humanize skill owns the single tells catalogue that personalize reads by reference. Beyond the Wikipedia-derived tells it launched with, the catalogue covers the engagement register (throat-clearing openers, faux-insight setups, colon reveals, fake-profound kickers, and kin) merged from petergyang/no-ai-slop.

Its patterns are editing heuristics, not authorship tests or numerical quotas. It preserves meaningful qualifications, list items, citations and functional markup; citation lookup failures do not justify deletion, and URL cleanup keeps functional query parameters. Inline edits and read-only reviews stay in scope. Calling workflows invoke it explicitly; it is no longer a wikipedia-article phase.

Org task triage is split across two skills over one engine. triage-personal-todos here owns personal notes; the project-local triage-project-todos in the Epoch workspace owns Epoch work. bin/triage-notes loads its owner-supplied engine and excluded roots from ~/.config/triage-notes/config.json, then binds personal commands and artifacts to their corpus; project ownership still needs checking beyond a corpus label. Index/worktree publication uses snapshot checks and owned rollback, not an indivisible transaction against arbitrary writers.

Personal brief-only requests do not assign priorities. Selection checks the task’s current state and premise, then rebuilds the eligibility ledger when that evidence changes. Missing priority values limit verified ranking; old notes and absent provenance alone do not prove completion or staleness. VERIFY_WITH is an evidence hint, not authority to execute commands or writes.

The personal a/s/d/r/m choices bind to the exact briefed task. Confirmed d uses the engine’s supported complete update after checking Org workflow and date semantics; it does not rewrite unfinished work or create follow-ups. All mutations preserve foreign index/buffer state and inspect actual postimages before committing. Failed or ambiguous publication retains recovery evidence.

The paired update-log skill runs only on an explicit bookkeeping request or the deliberate --auto chain. It preserves established map modes, intentionally different agent instructions, foreign changes and actual file-tracking policy. It holds no Epoch rules: for an Epoch session it follows the workspace’s own epoch-update-log skill ($EPOCH_WORKSPACE/.claude/skills/epoch-update-log/), the single source of the Epoch closeout, which other maintainers use without these dotfiles. Likewise, a Forethought sprint session (~/repos/forethought or below) follows the sprint repo’s own closeout skill (~/repos/forethought/ph-ps/skills/closeout/), which Peter Hartree also uses. Ignore rules do not make tracked or Drive-synced content private. Post-update-log hooks refresh the brief before its map is derived, within the caller’s authority; derived fields cannot originate unverified claims.

The paired exit-skill-audit skill runs after update-log in the Emacs before-exit chain and audits an exit-time skill once a month. Each target (so far only the Forethought closeout) is a directory under exit-skill-audit/targets/ holding the skill’s path, its checks and its test sessions. The audit runs in fresh-context subagents, writes proposals and holds the session for Pablo’s decision; it never edits the target skill itself. Its script keeps due dates, a per-target lock and pending proposals under ~/.local/state/exit-skill-audit/. The principle and the targets still to add are in claude/context/skill-audits.md.

For Epoch, a routing table sends project logs, briefs, TODOs and decisions to the shared workspace ($EPOCH_WORKSPACE) and the root session log, the latest-session narrative and personal/<path>.org notes to the private root ($EPOCH_PRIVATE_ROOT); workspace text must pass the 14 approved privacy rules (“if in doubt, private”), and a two-root closeout commits the workspace first, then the private root with a Workspace-Commit: trailer.

Work commits and bookkeeping transactions are separate. Epoch closeouts use the harness’s explicit input set; publication needs its own authorization and verification at the intended destination. Final receipts publish without overwriting an existing target and validate bounded evidence, but do not prove project identity or commit scope. Failed or ambiguous outcomes retain recovery evidence instead of erasing it during cleanup.

The paired orchestrate-review skill coordinates fixed Claude and Codex sessions through one guarded, stage-atomic workflow. Its helper binds each actor’s backend, repository, and transcript, and records delivery attempts before contact. A phase becomes active only when the marker-bearing prompt itself appears in the actor’s transcript past the recorded boundary; a busy terminal or background transcript growth is not acknowledgement. Terminal assistant returns distinguish completion from tool activity. Attempt hashes are taken over the prompt as the terminal records it, without trailing newlines, and user-role records the harness injects (teammate messages, task notifications) do not end the prompt turn. Ambiguous restarts and steering require reconciliation, not another submission. Shared session primitives live in lib/python/agent_session_lib.py. During implementation, monitoring exposes the fixed actor’s state and its published progress, not internal task transcripts or processes. The watcher does not automatically enforce a timed stop-loss or detect login screens. Agents use supported host waiting mechanisms and keep the host’s required update cadence. Cleanup closes only run-owned, confirmed-inactive sessions; adopted or busy sessions remain protected.

The paired request-review skill requests one opposite-CLI review of an immutable committed plan. It preserves review-only scope, starts and submits to the reviewer session without displaying it, snoozes that session so it stays out of the user’s sight, binds a stable session identity and exact run-specific prompt before dispatch, and accepts only a terminal return from that submitted turn. Ambiguous delivery remains pending; missing evidence cannot authorize retransmission or an unsupported process-loss restart. Its helper reuses agent_session_lib and records private recovery evidence. Codex startup records are recognized field by field, so a new CLI field or record (such as 0.162.0’s turn_attribution or 0.163.0’s thread_settings_applied event, or 0.164.0’s additional_tools item and model.base_instructions developer message) leaves delivery pending until the recognizer is taught it. As the one exception to the submitted-turn rule, a Codex reviewer whose submitted turn ended with a recorded usage_limit_exceeded error and no output, followed by exactly continue, is certified from that continuation turn and recorded as such; any later user turn still refuses. Its watcher returns as soon as the reviewer awaits input and names the next command, because only finish-review or reconcile-submission advance the run. Closing the run-owned reviewer is a required numbered step before the report, through agent-kill-session-buffer in the reviewer’s own buffer; cleanup protects active, reused and unowned sessions. A finding that would reverse, defer or weaken an explicit user requirement is put to the user as its own question rather than folded into the revised plan (added 2026-10-19, after a review finding silently deferred a requirement).

The google-sheets-comments skill — disabled, see skills-disabled.json — binds account/file/thread/reply identity, uses current API evidence rather than email notifications, and preserves exact action authority. Its gdoc contract records projection and receipt limitations; fresh pre/post checks reconcile ambiguous writes before any retry.

The generate-readme skill preserves established Org manuals, README-specific material and foreign edits. It distinguishes package/feature/repository names, checks recipe prerequisites and installed revisions, verifies publishable links, links Pablo’s packages to their manual on stafforini.com rather than the Org file, and reserves manual renames or roadmap migrations for explicit requests.

The fix-drive-errors skill separates read-only diagnosis from authorized repairs and binds live evidence to the displayed account and sync root. Its reader preserves duplicate rows and exact reasons, refuses incomplete scans, and labels simulated panels and heuristic filesystem candidates. Repairs preserve exact files and consumers; Epoch .gdoc replacements require checking the actual saved .url → OS handler → Epoch document route before authorized trashing of an unchanged original. Restart and settling checks are bounded.

Hooks

The Claude hook index owns registration boundaries and the per-file index. Claude has events with no Codex equivalent here, including Read, Grep, Glob, Skill, Notification, and Chrome-specific events. Those are explicit manifest differences, not missing peers.

On 2026-10-05 the hook scripts that nothing registered, installed, or called were removed: the two CR guards cr-card-body-gate.sh and cr-skill-routing-reminder.sh, the coverage-map hook and its one-shot installer, the sync-agent-c-worktree.sh compatibility wrapper, and elpaca-run.sh. The reasoning-task sync hook and the freshness guard it installs stay, since bin/cr-review-worktree and bin/sync-reasoning-tasks-worktrees call it.

Since 2026-10-03 the agent package passes its status line and its Emacs notification hooks (Stop, Notification, turn state, background tasks) to each Claude session it starts through --settings, so ~/.claude/settings.json registers none of them. The dotfiles copies of those Emacs notification hooks (and their fire-and-forget.sh wrapper) were registered here before that and were removed on 2026-10-05: with them, every event reached Emacs twice. A Stop from a claude -p run inside a session is dropped by the agent package itself, which compares the payload’s session_id with the id it records for the session.

Since 2026-10-27 the Stop hook hooks/inbox-pending-stop.py makes a session that accepts inbox-monitor wake-ups (m m, “toggle monitor”, in agent-menu) handle the messages routed to its project while it was busy, before it stops: the monitor wakes only idle sessions, and a FIAS session that worked for 90 minutes left Ro’s final contract and Eitan’s payment approval unseen. Since 2026-10-28 it also blocks after a hook-continued turn when newer items arrived meanwhile (a message that came in during such a continuation waited all night), still without looping.

Since 2026-10-10 the Stop hook hooks/block-done-over-running-tasks.py refuses a turn whose last status line is Status: done while Claude Code’s Stop payload still lists a running background shell, Monitor or background agent (teammates are excluded: an idle one is reported running). The agent must stop each task or report the work as still open. After two refusals in one prompt, counted by the hook itself, the stop is allowed with a message naming the tasks. Origin: a four-week audit of 3,962 background tasks found leftover tasks with many unrelated causes, all left behind by agents that had declared the work done; see docs/plans/2026-10-10-stale-background-jobs.md.

Since 2026-10-10 the TeammateIdle hook hooks/hold-idle-teammate-with-running-task.py keeps an agent-team teammate from going idle while a background shell it started is still running (some process holds the task’s output file open for writing). It tells the teammate to wait in the foreground instead, because Claude Code can queue a teammate’s completion notice without delivering it, leaving the teammate idle for good. The suggested wait loop stays under 600 s so the wait itself is never moved to the background. A task that ended without its notice reaching the teammate is reported to it once, with its output path. Origin: the 2026-10-10 Epoch/meetings stall; see docs/plans/2026-10-10-teammate-idle-with-running-task.md.

Since 2026-10-01 every PreToolUse guard runs in one Python process: ~/.claude/settings.json registers only hooks/agent-guard.sh, which runs the engine in lib/python/agent_guard and turns any failure into a denial, except that a crash on Read, Grep or Glob allows the call unguarded with a warning to the user and the agent, so a broken guard can still be read and diagnosed (2026-10-05, after crashes on 2026-10-01/02 denied every tool). The engine handles the crashes it catches; agent-guard.sh handles the rest (no interpreter, an import error, a signal), reading the top-level tool_name with JSON.parse under osascript -l JavaScript, not the guard’s Python. A denial by another policy in the chain still stands when one crashes. A policy’s deliberate HookFailure or an unparsable payload still denies every tool; ReadOnlyToolCrashTests in tests/test_hook_failure_safety.py covers both paths. Each former guard hook is a policy module in lib/python/agent_guard/policies/ (secret_leak, sensitive_read, untrusted_execution, destructive, github_write and so on), and engine.py runs them per tool in the order the old registrations did; for Bash, agent_guard.policies.dispatcher composes the checks the former shell dispatcher ran. The decisions the engine deliberately changed from the shell guards are listed in docs/plans/2026-10-01-guard-engine.md. bin/test-guards runs the guard tests in-process.

agent_guard.policies.retired_secrets (run for Bash and Edit|Write|NotebookEdit since 2026-10-01) keeps the secrets CLI that 1Password replaced from coming back. It denies new text, commit messages and shell commands that call or name that CLI, its store or its Emacs integration, and tells the agent to use op-automations or op-desktop as described in context/secrets.md. Text being removed is never inspected and read-only searches stay allowed, so stale references can be found and deleted. The patterns live only in the shared lib/python/retired_secrets_cli.py and tests/test_retired_secrets_cli.py, the two files exempt from it. Origin: on 2026-10-01 the Home Assistant health collector still called the retired CLI and failed; stale references kept resurfacing as warnings. The same day the sensitive-read, secret-leak and untrusted-run path lists dropped that store’s directory, which no longer exists.

agent_guard.policies.search_root (run for Bash, Grep and Glob since 2026-10-06, and for Codex’s shell tools) denies a search whose root is /, $HOME, ~/My Drive, /Users, /Volumes or another root in HUGE_ROOTS, or an ancestor of one: the Grep and Glob tools (their path, else the session cwd) and shell grep -r, rg, ugrep, find, fd and bfs (this Claude’s find and grep are its embedded bfs and ugrep). Paths are compared after expanding ~, resolving relative paths against the cwd or an earlier cd, symlinks and ..; a narrower subdirectory or a depth limit of at most 2 stays allowed, and the denial tells the agent to name a narrower directory. hooks/session-search-cleanup.sh, registered for SessionStart and SessionEnd, terminates search processes an ended session left running, matched by its CLAUDE_CODE_SESSION_ID, by process ancestry, or by a CLAUDE_PID owner that is gone (Claude skips SessionEnd when a signal ends it, so the next session’s start catches those); never by name alone. Origin: on 2026-10-06 agent searches read the disk at ~2 GB/s, pushed the load to ~150, and a bfs ran orphaned for 35 minutes after its session ended.

agent_guard.policies.stale_team (run for SendMessage since 2026-10-06; the agent-guard.sh registration’s matcher must include SendMessage) denies a message to an agent bound to a stale agent team. Claude keeps one team per process at teams/session-<id8>/, named after the session id the process started with, so a restart that resumes a session gets a new team while the session id stays the old one, and an agent of the earlier process resumed by name or agentId still delivers to the old team’s unread inbox. The current team is the one the process (sessions/<pid>.json, matched by the payload’s session_id) created just before its startedAt; a target that is a member of another of this session’s teams (from its subagent records, or one it led) but not of the current team is denied, with advice to spawn a fresh agent and hand it the old agent’s transcript. Read-only; it allows the call whenever a file is missing or malformed or the current team is ambiguous. Origin: on 2026-10-06 resumed agents’ 17 reports sat for two hours in a stale team’s inbox.

agent_guard.policies.named_agent (run for Agent since 2026-10-19; the agent-guard.sh registration’s matcher must include Agent) denies an Agent call that passes a non-empty name. Claude Code makes a named agent an agent-team teammate, which stays listed as an idle background agent after it finishes until it is stopped by hand, and which the Stop hook block-done-over-running-tasks.py cannot see (it excludes teammates). An unnamed subagent is listed as completed when it returns, and SendMessage still reaches it by agentId. Origin: on 2026-10-18 a tangodb session left 28 finished named case workers listed as background agents.

redact-tool-output.py runs after every tool call (PostToolUse, matcher * since 2026-09-30). It masks every string in the result, keeping its structure, through two layers: the shape patterns of redact-secrets.sh and the keyed-fingerprint index of this machine’s actual secret values (bin/secret-index, lib/python/secret_index.py), which catches secrets with no recognisable shape. It drops screenshot images when the browser may be on a page listed in agents/credential-pages.txt (or when no URL can be found); on such a page it also masks credential-like strings (long letter-and-digit runs, outside URLs) in browser tools’ text, since a newly created key is not yet in the index (2026-10-23: R2 S3 keys printed by get_page_text); and it fails closed: if masking cannot run, the output is withheld, keeping only block type fields so Claude Code accepts the replacement. Verified live 2026-09-30 for Read, Bash, subagent tool calls and Chrome screenshots. Not covered: the final report of a background subagent (a SubagentStop hook cannot rewrite it), so a subagent that deliberately transforms a secret it reads can still return it. Origin: on 2026-09-27 a Chrome find result echoed a new Cloudflare token and a 1Password title listing printed a Mullvad account number.

The shared numeric-shape filter (lib/python/redact_numeric_credentials.py) preserves complete UUID tokens, including MusicBrainz IDs whose first sixteen digits resemble an account number. Standalone sixteen-digit numbers, malformed UUID lookalikes, contextual credential patterns, and known-secret fingerprints remain protected in both runtimes.

On 2026-09-30 which NAME printed a zsh function whose body held a literal Google OAuth client secret and refresh token, shapes the redactor did not know. redact-secrets.sh now masks GOCSPX- client secrets, 1// refresh tokens and ya29. access tokens (the input guards deny them in commands too), the Bash output wrapping (agent_guard.policies.dispatcher) routes shell introspection (which, type, whence, functions) through it, and agent_guard.policies.secret_leak denies printing the body of a known shell function outright through lib-function-body-policy.py, since a body can hold a credential of any shape. tests/test_function_body_guard.py covers all three.

chrome-bridge-recover.py runs after every mcp__claude-in-chrome__ call (PostToolUse and PostToolUseFailure). The Claude in Chrome extension answers only sessions of the Claude account it is signed in to, and only while its Chrome profile is open; with one CLAUDE_CONFIG_DIR per account and several Chrome profiles, sessions regularly started with that profile closed and misreported “no browser access”. When a result says the extension is not connected, the lookup timed out, or list_connected_browsers is empty, the hook checks that Chrome’s native-messaging manifest points at a host wrapper whose claude binary exists, opens the profile mapped to this config dir in chrome-extension-profiles.json (via bin/chrome-profile-open --background, so it never steals focus, at most once per 90 s), waits until the profile has been open 25 s (and at least 10 s between retries, within the hook’s 45 s timeout), and returns additionalContext telling the model to keep retrying; only once the profile has been open 60 s may it report a browser problem. The wait exists because the extension takes some seconds to connect after its profile opens, and an immediate retry used to fail and be reported as “Chrome won’t connect”. An unmapped config dir is reported as such, so the mapping is filled once rather than guessed. It is silent while the bridge is connected. tests/test_chrome_bridge_recover.py covers detection, the manifest checks, mapping, rate limiting and the retry waits.

epoch-brief-freshness.sh runs after Read and Bash (PostToolUse) and dispatches only when the payload mentions an .org file. The Epoch helper decides whether a project brief was read, runs the TODO freshness audit for that project and returns the flagged TODOs as additionalContext, once per session and brief (subagents are keyed separately). An audit failure is reported in the notice rather than hidden. The registration timeout is 40 s, above the helper’s 30 s audit timeout. Design: Epoch/docs/specs/2026-09-26-brief-freshness-and-document-reachability-design.md; tests/test_epoch_brief_freshness_hooks.py covers the wrapper contract. The wrapper finds the helper under $EPOCH_SHARED_DIR, else $EPOCH_PROJECTS/shared, else $EPOCH_WORKSPACE/projects/shared (EPOCH_ROOT is an older alias for EPOCH_WORKSPACE), defaulting to the workspace checkout ~/repos/epoch/ai-enablement/projects/shared.

Hooks that return JSON follow the Claude hook contract exactly: hookSpecificOutput carries hookEventName and additionalContext, and a branch that wants Claude to react exits 0 with top-level decision and reason. A bare message field fails schema validation and is dropped, and JSON printed beside a non-zero exit is discarded, so both shapes silently lose the hook’s result. Plain PostToolUse stdout reaches only the transcript, never the model: remind-plan-review.sh and remind-claude-readme-update.sh printed plain text until 2026-09-26, and their reminders were never seen. Every reminder therefore returns additionalContext JSON. tests/test_hook_output_contract.py exercises every output branch of load-elisp-after-edit.sh and regenerate-manual-after-edit.sh and scans both hook trees for the invalid shape. The live registration gives load-elisp-after-edit.sh a 150 s timeout, matching Codex, because the script waits up to 120 s for the Elpaca rebuild before reporting.

The Trajectory CR hooks are no longer registered globally. Their source and project history are retained, but new sessions do not run Trajectory sync, coverage regeneration, skill routing, or card gates.

Sensitive-read guards permit the security audit’s shell-export classifier only for its exact absolute helper and target paths. The classifier reports names, line numbers, scopes, and classifications without printing values.

Since 2026-09-18 the sensitive-read guards no longer classify .env.op as an environment secrets file. It is the 1Password reference template (NAME=op://vault/item/field lines, tracked in git, no values by construction), so reading it, editing prose that names it, or committing a message that mentions it passes; .env, .env.local and .env.op.bak stay covered. The secret-leak guard in turn stopped exempting .env.op from its pattern scan, so a literal token written into the template is denied like anywhere else. Tracked .env.example / .env.op.<name>.example templates are exempt on the same grounds, and a quoted git commit -m message is masked before the scan (mask_git_commit_messages in lib/python/agent_guard/shell_text.py): the message is data git stores, so naming a real dotenv file in it is inert, while git commit -m "$(cat FILE)", git commit -F FILE and a following cat FILE still deny. tests/test_env_op_template_policy.py runs both guards against both sides; the decision record is docs/plans/2026-09-18-env-op-template-classification.md.

Commit gates resolve explicit path selections against a disposable candidate index. A git commit --only checks the selected working-tree changes, including inherited changes when amending, without counting unrelated staged Elisp or documentation. Unsupported selection syntax is denied with a reason; these checks do not alter the shared index.

Candidate documentation audits execute the checker committed at HEAD. They omit private skill entrypoints from the disposable audit index only when both their indexed git-crypt attribute and ciphertext header agree. This keeps encrypted skills out of the public inventory without reading unlocked private text or executing a staged checker. Declared encryption with a plaintext indexed blob fails the audit.

Quoted commit-message heredocs are shell input. Their delimiters and bodies are excluded from Git’s selected paths; interpreter-fed heredocs remain visible to the command classifier.

The Elisp evidence gate recovers a nested functions.exec command’s working directory before routing it to a repository. The reduced routing payload cannot recover the parent transcript context afterward. Directory selections include files known to Git, so an untracked manual cannot satisfy the documentation gate.

The Bash dispatcher (agent_guard.policies.dispatcher) runs its former delegated guards in-process. A guard that cannot complete denies the command with a reason naming that guard, without exposing the command payload; no guard returns an interactive approval response.

Claude editing calls have one destination, so the native Write secret-file exception remains scoped to that file. The Codex patch guard separately classifies every destination in multi-file patches; an exempt file cannot suppress checks on ordinary files. Claude has no apply_patch entrypoint.

Changes to the secret guard follow docs/secret-guard.md: its incident inventory, no new per-site exemptions (tests/test_secret_guard_scope.py keeps the route registry from returning), and the 2026-09-30 redesign. The deny message points agents there and to WebFetch for public URLs instead of asking them to patch the classifier. bin/test-guards runs the guard tests in parallel.

The secret guard also blocks process listings that expose arguments or environments, including macOS pgrep -fl. PID/name diagnostics remain available; supported forms and limits are in the hook contracts. Quoted Python-stdin heredocs distinguish ordinary ps / pgrep variable names from process inspection. An empty list with one binding in its own module or function scope may use append. Parameters, rebinding, closure receivers, global=/=nonlocal and dynamic namespaces remain conservative. Indexed read_text() calls are also recognized for uniquely bound lists from literal pathlib.Path joins and glob patterns; shadowed builtins/imports, dynamic receivers and mutations remain conservative. A name whose every binding in its own module or function scope assigns parsed JSON (json.load without hooks, its subscripts, .get and conditionals) or a builtin container (a literal, comprehension, list(), dict() …) may call container methods such as ps.values() or ps.get(k). Other calls/attribute bases, imports, psutil and /proc markers remain checked; unquoted heredocs stay conservative. String literals count as data only in programs that cannot start a process or evaluate text.

The secret-leak guard denies direct agent-shell invocations of raw op, macOS security, and pbpaste, including wrappers, nested shells, process substitution, executable globs (denied with their own reason: a program named by a glob could expand to a protected tool; only text the shell expands counts, so ${q%%|*}, case patterns, quoted code and heredocs read by Python or cat are not globs in a command position), and shell-word obfuscation. The 1Password brokers op-automations and op-desktop are the sanctioned access path: lib-op-policy.py allows a closed list of command shapes whose stdout carries no credential (masked run, captured or filed read, metadata-only jq over item output, --out-file documents, writes without --format, share links restricted with --emails, metadata commands, including the vault user list / vault group list / group user list membership listings and service-account ratelimit usage counters) and denies every other shape, including unrestricted share links and shapes it cannot place. Interpreter programs are not parsed as shell: one that names a broker is denied unless it is a recognized Python document edit. Broker paths passed to ls, stat, or readlink are inert file metadata arguments; broker execution elsewhere in the command is still checked. The exact bash -n FILE syntax-check form is also allowed; extra options are not. Shell expansions are fine in broker arguments (--env-file "$ROOT/…"); an expansion in the subcommand position is unclassifiable and denied. The op-automations account selector (@epoch, @personal, @tlon) is stripped before classification, so each shape behaves the same with or without it; any other @… word is denied. The broker’s own cache read verb prints what read would and takes the same non-printing shapes (without --out-file); cache forget prints nothing and is allowed. The sensitive-read guards deny reading, grepping or naming the cache directory (…/op-automations/cache) in a command, since it holds plaintext values. Bash, Read and Grep reach this decision through the same agent_guard.policies.sensitive_read policy. Quoted argument globs, including rg exclusion patterns beginning with !, remain inert data rather than executable command words. A shell case default pattern is likewise grammar, not an executable glob. So is the * in a standalone assignment ended by a command separator, such as l=${r##*:};; an expansion in command position is still denied. Heredoc bodies fed to a known data sink (cat, tee, git, gh, and similar) with no pipe or substitution around them are data too, so a commit message or a Markdown file written through a heredoc is never scanned as shell text; bodies fed to shells, interpreters, or unknown programs stay in the scan. For a narrowly recognized quoted Python stdin program, the secret guard first checks Python protected references, exempting genuine pass statements and a closed text/JSON document-edit language, then excludes classified bodies from shell lexical checks. Document edits permit only validated standard imports, data bindings, text reads/replacements/writes and JSON conversions. Bound JSON data may receive an update statement with one validated dictionary literal; the mutation alone does not qualify as a document write. Unknown calls, executable targets and shell tails retain conservative scanning. Denials describe a failure to classify the command safely, not proof that a credential tool ran: protected names inside an unclassified Python program may be ordinary prose. Programs with subprocess calls remain outside the closed document-edit language even when those particular calls are benign. This exception requires one standalone direct Python command. Codex additionally requires one complete literal functions.exec wrapper with inert metadata; multi-call and dynamic orchestrations receive no document-edit exemption. A separate closed writer permits a literal .py path and literal source, with an optional existence guard, mode 0600 and path-only print. Its generated source must parse as Python and pass the same protected-reference checks; only actual pass statements receive the syntax exemption. Other outer strings/comments, source transformations, additional writes and execution calls retain the original denial. The writer uses the same standalone-command and complete-wrapper restrictions as document edits. Python varargs are not shell executable globs. Other secret checks still receive the original input. Literal pyenv exec python - stdin programs use the same Python classification, including its protected-reference checks; other pyenv subcommands and dynamic interpreter names receive no exemption. When such a heredoc follows other commands, where that classification does not apply, the heredoc masking (agent_guard/shell_text.py) still treats its body as interpreter source for the executable-glob rule only; names of credential tools in it remain denied. An assignment prefix may hold a closed command substitution; an unclosed one starts the inner command, so in f=$(ls x*.jsonl | head -1) the glob is an argument of ls, not a program name. A command substitution elsewhere on the line does not change who reads the body; one around the interpreter itself keeps the body scanned. Consecutive supported Python heredocs receive the same classification in order; unknown shell structure stops the scan without exempting the remaining text. A control operator or output redirection after the delimiter (python3 - <<'EOF' | head -3) keeps the heredoc recognized; that suffix stays in the outer-scanned shell text, and a document edit requires no suffix. This classifier is not a Python sandbox. The Git classifier also recognizes these quoted Python bodies before parsing shell substitutions. Backticks and substitution markers in data without standalone Git/commit references no longer fabricate ambiguous commits; Git-bearing interpreter source retains conservative scanning. Quoted Git commit-message input is excluded before substitution parsing as well.

The opaque-token heuristic no longer denies (intended difference 3 of docs/plans/2026-10-01-guard-engine.md). Where it would have been the only reason to deny, agent_guard.url_heuristic appends one line naming the runtime, tool and a fixed label, never the value or the command, to ~/.local/state/agent-guard/heuristic.log. Its classifier is the former lib-public-url-scan.py without the verified-route registry, which existed only to exempt public URLs from the denial: only URL operands of recognized literal curl=/=wget commands are parsed, headers, bodies, filenames and URL tails stay scanned with bounded percent decoding, and local read paths are projected first. Known values and prefixed token shapes still deny. tests/agent_guard/test_url_heuristic.py pins the parser boundaries.

The quote-aware network parser classifies literal curl=/=wget output-file options itself. A quoted URL query containing & cannot turn its separate local destination into a network payload. URI-shaped output values, headers, bodies, URLs and unknown command forms remain scanned; known-secret checks still inspect the complete original command.

Literal newline command separators and backslashes inside single quotes are parsed without evaluating shell text. Literal mkdir -p absolute directories are local destinations when no pipeline can forward diagnostic output. Expansions, unquoted or double-quoted escapes, and unknown command forms retain conservative scanning.

The Wayback CDX API’s single url target and HTTPS web.archive.org/web/ replay URLs with a 14-digit timestamp and optional id_ flag are parsed as archive envelopes. Their embedded targets still pass the ordinary URL checks; malformed decoded targets and nested archive envelopes receive no exemption. Other query fields, fragments, headers and bodies remain scanned.

Literal curl --get searches are parsed as requests; --data-urlencode values retain payload scanning and output paths remain local.

Local-path projection preserves real newline, semicolon and && command boundaries; it does not quote them into inert arguments. Request payloads remain scanned across every command in a sequence. A closed pdftotext reader form accepts optional positive -f / -l page bounds, one absolute PDF pathname and stdout. Only its local pathname is projected; pipelines, wrappers, passwords and unknown options stay scanned.

A terminal quoted cat > literal.md/org/txt/json heredoc is document data for network-command detection. Pipes, continuations, extra commands, unquoted bodies and executable destinations remain conservative. If a network marker exists elsewhere, the full original command is scanned; known-secret checks always retain the original bytes. This is a lexical boundary, not a filesystem or shell-rebinding sandbox.

Quoted Python stdin may use curl, wget, nc or ncat as ordinary data variables without activating network detection. Callable/attribute receivers, imports, literals and unsupported source remain scanned. This projection only controls activation; actual network markers still trigger inspection of the original payload, and known-secret checks always retain the original bytes.

The network heuristic treats dots and hyphens as part of names, so curl.py, curl-cffi and zotra-use-curl do not activate it. Actual curl=/=wget=/=nc=/=ncat names, including interpreter subprocess arguments, retain the opaque-token scan. This does not exempt interpreter bodies or alter known-secret checks. tests/test_secret_guard_network_names.py covers the local wheel-provenance and installed-source reads, and unsafe controls through both runtimes. Relative ./curl and absolute /usr/bin/curl remain scanned.

The inline-secret scan recognizes public MusicBrainz entity URLs before tokenizing their identifiers. Queries, fragments and credential arguments remain scanned; URL components never receive filesystem-path exemptions. DAHR matrix URLs normalize only the exact public host, fixed route and decimal record ID. Their free-form title slug, further path, query and fragment remain scanned, as do credential headers and request bodies. PMLR proceedings URLs similarly normalize the public host and numeric volume route; article identifiers and all additional URL content remain scanned. Jagiellonian University repository URLs normalize only the exact ruj.uj.edu.pl/bitstreams/UUID/download route and the same host’s server/api/core/bitstreams/UUID/content route. Queries, fragments and request credentials remain scanned; UUIDs elsewhere receive no exemption. UPLOpen’s exact uplopen.com/en/books/NUMBER/files/UUID.pdf route receives the same narrow treatment; its query, fragment and request credentials stay scanned. EJPE’s exact ejpe.org/journal/article/download/NUMBER/NUMBER/NUMBER route also normalizes public record IDs without exempting extra path or request content. ZNU’s exact files.znu.edu.ua/files/Bibliobooks/InshiNUMBER/NUMBER.pdf route receives the same treatment for its public library identifiers. Adelaide’s exact digital.library.adelaide.edu.au/bitstreams/UUID/download route and its server/api/core/bitstreams/UUID/content counterpart normalize the public document identifier. ZORA’s exact HTTPS host www.zora.uzh.ch normalizes only /id/eprint/NUMBER/NUMBER/; filenames remain scanned. UCL Discovery uses that same numeric eprint prefix on the exact HTTPS host discovery.ucl.ac.uk. Jacob Barrett’s author site recognizes only the exact HTTPS host www.jacobbarrett.org and public account prefix /uploads/1/2/3/6/123631127/. Bob Beddor’s exact HTTPS host www.bobbeddor.com similarly recognizes only /uploads/3/2/0/3/32037343/. JESP’s migrated exact HTTPS host www.jesp.org recognizes the complete /pdf/UUID document route, with no trailing path exemption. Kent’s exact HTTPS host blogs.kent.ac.uk recognizes only the /futureofnormativity/files/YYYY/MM/ public conference upload prefix. Brown’s exact HTTPS host www.brown.edu recognizes only the public /Departments/Philosophy/bears/ review prefix. Jagiellonian’s exact HTTPS host ruj.uj.edu.pl also recognizes complete /entities/publication/UUID metadata routes. Filenames, query strings, fragments and request headers/bodies remain scanned; other accounts and lookalike authorities are not classified as these public routes. LSE’s exact HTTPS host eprints.lse.ac.uk projects only numeric record/file prefixes. JESP’s jesp.org projects its complete numeric article-download route; Frontiers projects only the observed artificial-intelligence article route with a numeric 10.3389/frai.YEAR.NUMBER DOI. The exact HTTPS host 80000hours.org projects its dated WordPress uploads prefix. Cambridge’s exact HTTPS host www.cambridge.org projects the public content-view prefix with a 32-character hexadecimal document ID and numeric article-file ID; the trailing descriptive filename remains scanned. These are verified source schemas, not domain exemptions. Unknown routes retain slash-spanning scans, because splitting every path at slashes would hide Base64 credentials that contain slashes or payloads distributed across path segments. Ergo’s HTTPS journals.publishing.umich.edu/ergo/article/NUMBER/galley/NUMBER/download/ route normalizes public article and galley IDs. These routes retain query, fragment, header and body checks; extra path and spoofed hosts stay scanned. Sabanci’s exact myweb.sabanciuniv.edu/ozgurkibris/files/YYYY/MM/ HTTPS upload prefix is normalized without dropping filenames, request payloads or URL tails; other authors and routes remain subject to the original scan. HLI’s exact www.happierlivesinstitute.org/wp-content/uploads/YYYY/MM/ prefix normalizes public upload routing; filenames and all subsequent URL content stay scanned, along with credential headers and request bodies. SEC EDGAR’s exact HTTPS host www.sec.gov projects the complete /Archives/edgar/data/CIK/ACCESSION/[SUBDIR/]FILE.EXT archive route, whose CIK, 18-digit accession directory and dashed accession filename otherwise scan as one opaque run. The route requires an archive extension and bounds each component below the run length, so a token cannot stand in for the filename; queries, fragments, other hosts and plain HTTP stay scanned. Slash structure never grants a filesystem-path exemption. Only positively classified literal file operands, including curl @file and output files, are omitted from the entropy scan. Unknown options and shell forms stay scanned. Literal file operands of isolated cat, head and tail reads are excluded from entropy scanning when a separate network check follows, as are those of wc, ls, stat and file, which report a path’s size, name or type without sending it anywhere. Those four accept short option clusters only. Pipelines, redirects, shell expansions and unknown read options receive no exemption; known-secret checks still inspect the original command. The harness session scratchpad root (/private/tmp/claude-UID/CWD-SLUG/SESSION-UUID/scratchpad) is projected to AGENT_SCRATCHPAD wherever it appears as a literal path, before tokenization and regardless of expansions elsewhere: every tool call is told to use that directory, and its single mixed-class run otherwise denied any network command that named it as cwd or operand. Only that exact layout at a token boundary is projected; what follows it, a URL that embeds it, and any other root, UUID-less path or leaf name stay scanned. Loopback ports and API-version prefixes are routing metadata; the remaining URL path, query and fragment remain scanned. A numeric port is dropped from the cross-component residual for every host, not only loopback: the authority field has already scanned it and urlsplit has already proved it is digits, so leaving it there only let its digits join an ordinary path into one run. Each candidate is classified before choosing a hit, so a harmless early string cannot conceal a later credential. False positives require classifier repair, not manual guard bypass. The sensitive-read guard applies the data-sink rule and names the heredoc in its denial when a path is mentioned only inside an interpreter-fed program. A shell eval is recognized only at a word boundary, so an emacsclient --eval argument is Elisp, not shell.

Since 2026-09-05 the protected names pbpaste and security are denied only where something could run them: as a command word, behind env, sudo, xargs or find -exec, joined with === (git -c core.pager=…), inside bash -c source, or inside a heredoc body fed to a shell or interpreter, where a string literal counts as a reference. A plain argument of a read-only text tool (grep -rn security docs/, git log -S pbpaste, ls /usr/bin/pbpaste) is inert and passes; lib-inert-mentions.py performs that projection. Heredoc bodies fed to known non-shell interpreters (python, node, ruby, emacs, sqlite3, jq, …) are no longer scanned for executable globs, so ? and [ in a script are not misread as shell words, and the Python classifier recognizes cd x && python3 - "$S" <<'EOF' shapes. Wrappers consume only their options, assignments and durations, so env FOO=2 grep pbpaste f runs grep. The audited bin/op-clipboard-store consumer is the sanctioned route from a console Copy button into 1Password. With --token it trims whitespace around the copied value (selection slack) but still refuses whitespace inside it, which signals a terminal-wrapped copy; tests/test_secret_guard_inert_mentions.py pins the allowed and denied shapes for all three guard entry points.

bin/copy-slack-draft places a Slack draft in an Emacs composer and the kill ring. Rerunning it with revised text replaces the draft it last staged in that composer, recognised by a SHA-256 under $XDG_STATE_HOME/copy-slack-draft/; other text there is never replaced. Its JSON status distinguishes refused (unsent text it did not stage; nothing changed) from uncertain, and carries a reason when the failure is one of its own messages. Other Emacs error text is withheld because it could quote the composer.

bin/slack.py reads each workspace’s session credentials through op-automations cache read, so an agent that runs it hundreds of times spends two 1Password requests a day rather than two per call. If Slack rejects a cached credential (invalid_auth, not_authed, token_revoked), it runs cache forget and retries once. The altruismo-eficaz item’s title contains “/”, which no op:// reference can express, so that workspace still reads uncached until the item is referenced by ID. The becas-aisar and baish workspaces use the personal Automation items slack-becas-aisar and slack-baish (fields token and cookie). Since 2026-10-24 the 1Password items are only a fallback: when Slack rejects a pair, slack_chrome.py reads the current d cookie from Chrome’s cookie database (any profile signed in to the workspace), derives the token from the workspace’s /ssb/redirect page, verifies it with auth.test, caches the pair in ~/.cache/slack-chrome/ (0600) and slack.py retries, so nobody copies credentials by hand. The slack-credentials skill covers the remaining failures (signed out of Slack in Chrome, Keychain access refused, a Chrome format change). The inbox monitor (~/repos/inbox-monitor, described in context/service-access.md) imports slack.py to poll the becas-aisar, baish, forethought and epoch workspaces every two minutes; its project routing, notify-only fallback and pre-authorized actions (inbox-policy, inbox-act) are described there too.

slack.py channels [QUERY] follows conversations.list through every page and prints one compact JSON line per conversation, with a “N of M” count on stderr. Slack’s pages are sparse (a page can hold two channels and a cursor), so a single page once passed for the whole list and hid 56 of 58 channels; the command now fails rather than return a partial list.

slack.py send (2026-10-28) posts a message Pablo approved, as him, with chat.postMessage: --permalink replies in that message’s thread, --channel posts at top level. Until then agents had no Slack send path and left every approved reply in the Emacs composer for Pablo to send himself, a step he rejected; WhatsApp had gained wa-send the day before. A request that gets no usable response raises _NoResponse (still exit 1 for the other commands), which send turns into exit 3, because Slack may have posted it: the caller checks the conversation before retrying, and nothing retries on its own. A credential rejection is still refreshed and retried once, since Slack posts nothing when it rejects the session. Because personalize stages each Slack draft in its composer as it is written, send then runs copy-slack-draft --clear for the same conversation, which empties the composer if it still holds the unedited staged draft; otherwise Pablo could send the stale copy a second time. The receipt’s composer field reports the outcome.

The Elisp post-edit hook rebuilds and reloads complete package worktrees, but skips test-only files and edits while Git has an active operation or unmerged index entries, so a live Emacs never loads tests or a partially resolved tree. Test-only Elisp commits likewise do not require package-manual updates.

Elisp test and live-verification evidence is enforced on content, never on the spelling of a shell command. batch-test.sh, elisp-ert and elisp-check-evidence record, after a successful run, which Elisp files at which contents they covered; elisp-live-verify records the commit it exercised. claude/bin/elisp-evidence keeps these records under ~/.local/state/agent-elisp-evidence/. The global Git hook directory claude/git-hooks (installed as core.hooksPath in ~/.gitconfig) runs its checks only in agent sessions, identified by CLAUDE_CODE_SESSION_ID or CODEX_THREAD_ID, and skip repositories in system temporary directories such as test fixtures: pre-commit rejects a commit whose staged Elisp no run covered, and post-commit records a live check as owed for a production package, plus a test as owed when the commit skipped pre-commit through --no-verify. The Stop hook elisp-evidence stop-check keeps the committing session’s turn from ending until those debts are paid; live evidence for a later commit covers earlier ones, and an amended commit that no branch contains is dropped. After two consecutive blocks in one turn it lets the turn end and reports the unpaid debts to the user instead of looping. No other command is blocked meanwhile. Every hook in claude/git-hooks then runs the repository’s own hook of the same name, so repository hooks keep working. The only related command guard denies overriding core.hooksPath; a plain read (--get or the bare key) is allowed. Repository remotes. Every repository under ~/repos has a private GitHub remote, so forge, backups and other machines can reach it. A repository that deliberately stays local records why with git config repo.noRemote "REASON". In agent sessions post-commit runs claude/bin/repo-remote-check, which warns on a commit in a repository under ~/repos that has neither a remote nor that setting, and prints the gh repo create command to stage for Pablo. It only warns; commits made outside agent sessions are not checked. The documentation, README, and paired-config commit gates also run from that pre-commit hook, through claude/bin/agent-commit-gates, so they read the staged files from the index instead of inferring them from command text. An Elisp support file outside any package tree with its own doc/, such as a helper under bin/, is documented by the README.org of its directory or a parent; the Codex README gate ignores the project-local .codex/skills/ mirrors, whose pairing ai-config-sync guard-commit checks. Neither README gate, nor that guard’s documentation-owner check for project-local skills, counts a dependency lockfile such as package-lock.json, so a lockfile-only version bump needs no documentation edit.

Hooks that judge a Git command resolve the repository from the command itself, via hooks/lib-repo-root.sh. A cd at any command head decides the repository, the last one winning, because a session working in one repository routinely commits in another and the cd is often preceded by a preamble. The target is extracted by parameter expansion, never evaluated; $HOME is substituted, and a target carrying command substitution or any other unexpanded variable is treated as unresolvable. When the target cannot be resolved the helper reports no repository at all rather than substituting the session’s own, since judging a tree the command never touched both refuses correct commits and approves ones it should refuse.

The GitHub write guard resolves the target repository from the command before it falls back to the surrounding checkout’s remote. gh repo verbs name their target positionally and gh api endpoints are read through a leading slash or surrounding quotes; both used to miss, leaving the ambient repository to stand in, so an unowned target inherited whatever allowlist entry that repository had. gh repo create now refuses a command that names no OWNER/REPO at all, because a creation never acts on the current repository and so cannot borrow its authorization. A compound command (pipes, ;, &&) is judged one and-or element at a time (2026-10-05): each write element is checked alone in the directory it runs in, which is the command’s own directory or the nearest preceding literal cd DIR of the same && chain, so cd ~/repos/x && git fetch -q && git push is judged as a push from ~/repos/x. Explicit git -C, -R=/–repo=, GH_REPO and URL targets still decide; writes to two repositories, or a write after a cd that cannot be resolved or whose chain ended (;, newline, ||, &, or a command that may change directory), are refused. Elements made only of printf, echo, kill-ring-put, pbcopy and the output filters below, with no substitution or file redirection, run no write whatever they quote. Anything outside that grammar (substitutions, subshells, groups, heredocs, eval, export, pushd and similar) keeps the old rule: a write in a compound command is refused before any repository check. A scoped grant must match the whole command. Read-only GitHub commands may be piped or chained freely. CompoundCommandTests in tests/agent_guard/test_policy_github_write.py covers the binding. A single write piped only into tail, head, grep, wc or cat, optionally after 2>&1, keeps its one target and is judged like the bare write. The one other piped write accepted is the sanctioned secret-provisioning pipeline op-automations [@ACCOUNT] read REF | gh secret set NAME -R OWNER/REPO (or op-desktop read [--account X] REF as the reader): exactly two stages, a literal -R=/–repo= in the gh stage checked against the allowlist, no other flags except --no-newline=/-n= on read (which keeps the trailing newline out of the stored secret), no other operators or substitutions within that and-or element, and the 1Password policy must approve the same element.

Writes hidden in code the command runs are judged too (2026-10-05). The write patterns need a separator before git or gh, so "$(git push)", a backtick, eval "git push", xargs git push or printf 'git push' | sh passed unchecked, and sh -c "cd X && git push" was judged in the starting directory because the cd after the quote was missed. The guard now extracts the bodies of command and process substitutions and backticks (outside single quotes, and in unquoted-delimiter heredocs), the string after sh=/=bash=/=zsh -c, the arguments of eval, the command xargs runs with its input, and text a shell or source /dev/stdin reads from a here-string, a heredoc or the literal arguments of earlier pipeline stages. Each is judged as a whole command in the command’s starting directory, under the same allowlist and target rules, so it is denied or allowed exactly as the plain write would be; a hidden write in a compound command whose elements were not bound one by one (a pipeline into an interpreter, for instance) is refused. Text the parser cannot follow, and env -C, env -S or sudo -D, refuse when a write pattern appears anywhere in it. Quoted mentions used as data (grep patterns, git commit -m, printf ... | kill-ring-put) stay allowed. Scripts in files, other interpreters (python3 -c, node -e) and code built from variables are not inspected. HiddenCommandTests covers the forms.

For Claude, the Bash dispatcher (agent_guard.policies.dispatcher) runs the GitHub write guard behind a prefilter, so the prefilter decides whether the guard runs at all. Until 2026-09-18 that prefilter matched only a bare git push or gh <verb>, and git -C DIR push and /usr/bin/git push to an unowned repository reached no guard. It now delegates on any git or gh command word, in every spelling the guard’s own git_push_command_p accepts, and the guard decides what is a write. Since 2026-10-05 it delegates on git or gh after any character that cannot be part of a word, and on any $'…', whose escapes can spell one: a word after a backtick reached no guard before. tests/test_github_write_guard.py runs those forms through the dispatcher.

Also since 2026-09-18, the GitHub write, Ahrefs and walk-list guards scan text with mask_heredoc_bodies and mask_git_commit_messages applied, as the sensitive-read guard already did: a commit message or a heredoc fed to a data sink that names a push, the Ahrefs host or the walk-list store is prose, not a command. Target extraction still reads the raw command, heredocs fed to a shell or interpreter stay in the scan, and a shell -c body is unquoted before the scan so sh -c 'git push …' is read as the push it is (a quoted body used to hide it). The commit-selection helper stops at shell redirections (2>&1, > file) and accepts a joined <<EOF delimiter instead of reading them as pathspecs, and ai-config-sync accepts the -qm short-option cluster the helper already accepted. tests/test_inert_text_scans.py pins all of it.

GitHub writes require repository authorization or an explicitly authorized scoped contribution grant. Topic branches, issues, and comments have no exemption; read-only inspection remains available for undeclared repositories. A registry match makes the guard fall through silently rather than explicitly approving the write, including for pull-request merges, so the host’s normal permission layer and the global user-authorization rule still apply. The guard reads the allowlist and the automations registry from the committed HEAD revision, so appending a target to the working tree and retrying confers nothing. The guard implementation and hook registrations remain agent-editable; there is no separate self-edit guard.

The dotfiles-context and elisp-conventions workflows prepare a local topic branch, PR draft and retention plan for external-package fixes even when no PR was requested. Diagnosis-only and explicitly local-only tasks retain their scope; publication and profile activation still require applicable authorization.

Scoped fork and PR-creation grants use the operation authorization procedure and a shared checker. Only committed, expiring records match; each binds an exact command and authenticated account, plus the PR body hash where applicable. These grants do not authorize other upstream writes or establish user consent. Completed grants are removed without editing the hooks or broadening repository access.

A change under claude/skills, claude/hooks, claude/settings*.json or claude/CLAUDE.md must be accompanied by an update to this file. The guard accepts only that exact path, named among a command’s git add arguments: some other README does not stand in for it, and a commit message mentioning the file is not an update to it. Both were previously accepted, which is how the triage split reached this repository undocumented.

Guards that inspect a Git command read its arguments only, never its message. Heredoc bodies are dropped before lexing, because an apostrophe in prose made the lexer raise and the parser fail closed on a commit that staged no Elisp at all; argument text that genuinely cannot be lexed still fails closed. Since these are PreToolUse hooks they run before the git add they are judging, so the paths a command is about to stage count as staged. A second git commit in the same command is modelled in sequence when && links it to the first: the adds between the two commits are its content, and the guard audits the union, which is the tree the whole command leaves behind.

The SessionStart reasoning-worktree synchronization hook is also Claude-only. It repairs required key links before eligibility checks, updates eligible worktrees without pushing, reapplies the local overlay, injects private project skills, and installs worktree-local freshness protection. Because the key repair precedes the eligibility checks, it delays the start of every session in every repository, and it therefore starts no subprocess while the links are correct. Keep it that way: an added basename or readlink for each worktree previously cost approximately 0.41 seconds of session start time. Its bulk wrapper fetches shared state once before processing multiple worktrees. All worktree-creating helpers construct destinations under the shared external root ~/repos/.worktrees/<repository>/<branch> — never inside a repository checkout or ~/My Drive — while maintenance helpers keep covering legacy worktree locations until those are migrated.

Project-local pairing reminders follow the repository’s ai-config-sync.json policy for local.skills and local.instructions. Explicit single-agent statuses such as claude-only or codex-only suppress counterpart reminders for that artifact type only; unrelated hook reminders still apply. Missing, unreadable or malformed manifests, including invalid text encoding, retain the default pairing reminders. These are advisory messages, not commit approval.

Verification

Run bin/ai-config-sync audit after changing shared configuration. When a tracked public skill name or catalog description changes, first run bin/docs-audit generate. For a registered hook change, verify both its standalone decision logic and the real Claude event path; mutable ~/.claude/settings.json registration is part of that behavior. The audit counts a Claude plugin’s cached version only if ~/.claude/plugins/installed_plugins.json names it: Claude Code keeps an uninstalled or replaced version in the cache, unmarked, while sessions still use it, and counting it reported a false duplicate skill on 2026-10-19.

Claude’s settings format, event surface, permission model, browser integration, and account overlays are genuine tool-specific state. The sync contract records those differences rather than forcing them into Codex-shaped files.

Forethought notes workspace

Forethought sessions launch from ~/My Drive/Forethought/ or the selected notes project. The Emacs project picker includes the workspace, prep/, and immediate projects/*/ directories with instruction files, across existing accounts. Claude loads the workspace CLAUDE.md and then the project’s CLAUDE.md through native ancestor discovery. Project creation guidance lives in Forethought/context/project-workflow.md.

The notes folder stays outside Git. Code checkouts belong under ~/repos/forethought/; start from the notes folder when the engagement instruction chain is needed. Shared instruction pairing and Codex root-marker configuration are documented in agents/configuration.org.