Skip to content

Latest commit

 

History

History
1391 lines (1231 loc) · 90.8 KB

README.org

File metadata and controls

1391 lines (1231 loc) · 90.8 KB

Codex configuration

Overview

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

Layout and activation

codex/
├── AGENTS.md          global user instructions
├── config.toml        tracked native configuration
├── hooks.json         tracked hook registration
├── bin/               installation and maintenance commands
├── hooks/             hook implementations
├── rules/             command policy
├── skills/            tracked public global skills
├── programmatic-skills/
├── private-skills/
└── README.org

The live ~/.codex/AGENTS.md, ~/.codex/config.toml, and ~/.codex/hooks.json paths are symlinks to the tracked files. The installer links ~/.codex/skills to the tracked skill tree and exposes the programmatic root used by agent-run-skill. Repo-root .codex/ directories are project-local and must never be used as global symlink targets.

The subtree owners are the command index, the hook index, and the skill index.

Trajectory project trust entries have been removed from the active configuration. Retained project files and session history do not grant automatic project trust.

Instructions

The kill-ring rule in 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), with the context needed to decide each one, never in the kill ring.

Codex has no counterpart yet to the Claude Stop hook inbox-pending-stop.py (2026-10-27), which makes a session that accepts inbox-monitor wake-ups handle messages routed to its project while it was busy; ai-config-sync.json records the parity as deferred.

The “Do the work yourself” rule in AGENTS.md gained a hand-off clause on 2026-10-10: when a step needs Pablo, the agent first prepares everything around it (page opened, permitted fields filled, secrets on the clipboard, cursor placed) and then gives numbered click-by-click instructions naming window, tab, field and button.

Two operating rules added on 2026-10-27 (mirrored from claude/CLAUDE.md): 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: this setup’s Codex session replaced the per-platform noise thresholds with one 2% value without telling Pablo.

Unfamiliar code execution uses the shared bin/untrusted-run VM boundary; reviewed local workflows retain host execution. AGENTS.md states only the routing rule; staging and trust details live in the runner contract. The paired routing guard is registered in hooks.json for recognizable untrusted launchers and 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. Its exact definition needs native hook trust. 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 prerequisites.

The native agents.max_concurrent_threads_per_session setting allows 32 concurrent case workers, excluding the coordinator. This is an explicit operating limit, not a claimed Codex product maximum. Existing loaded threads may retain their previous limit; verify activation with actual worker launches.

Codex reads user instructions from AGENTS.md in $CODEX_HOME. Project instructions also live in AGENTS.md files, next to their Claude CLAUDE.md peers. project_doc_fallback_filenames is deliberately absent: Codex does not load project CLAUDE.md as a fallback.

The global codex/AGENTS.md and claude/CLAUDE.md files are semantic peers. Both carry the same operating rules, including “solve classes, not instances”, and the same repository-root contract (notes workspaces under Drive keep their git directories in ~/git-dirs/). Both also require every message an agent itself sends under Pablo’s name, to anyone, to start with =[agent] = (extended from messages to Peter Hartree on 2026-10-29); messages Pablo approved in a review file go out without it (2026-10-30). The send tools add the prefix themselves (claude/bin/agent_prefix.py); --reviewed FILE marks text Pablo approved in a review file. The paired find-past-session skill directs agents looking for a past session to claude/bin/agent-log-find, which searches the rendered Markdown session logs rather than raw transcripts. Both global files point to the context docs by the same absolute paths, and the audit rejects relative context/ references in either. The audit normalizes tool-specific titles, paths, and Claude @context syntax before comparison. It also masks each file’s browser-availability rule: Claude asks for claude-in-chrome, while Codex names the playwright MCP server’s browser_* tools. 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, which also says agents work only in tabs their browser tool opened. User-facing replies use ASD-STE100 Simplified Technical English.

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.

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 2026-09-02 prompt-audit plan records the removed patterns.

Native configuration

config.toml owns Codex model, reasoning effort, service tier, sandbox and approval behavior, TUI preferences, trusted roots, plugins, MCP configuration, and feature flags. [features].hooks enables the tracked hook integration. Codex-only trust, plugin, theme, application, and MCP state does not need a fictional Claude counterpart.

bin/trust-epoch-workspace adds the trusted-root entries for the Epoch workspace checkout ($EPOCH_WORKSPACE, default ~/repos/epoch/ai-enablement, and each projects/* directory) to this file and every ~/.codex-epoch*/config.toml. It is idempotent and refuses a checkout that does not exist yet; run it once at the workspace-split cutover.

The Epoch Asana MCP server is declared per machine, outside this file, in the untracked .codex/config.toml of the workspace checkout (Claude’s twin is the checkout’s .mcp.json). Its env pins EPOCH_OP_BIN (the dotfiles op routing shim), NPX_BIN and EPOCH_ASANA_EXPECTED_EMAIL, because the server’s child process gets neither the shell’s op function nor a shim-first PATH; ai-config-sync audit requires those keys and checks the token’s identity.

Hook trust belongs to an exact source path, handler key, and current definition. Review the command identity when approving changed hooks; event labels alone can name several handlers. Trust changes use Codex’s configuration API and must preserve unrelated settings and hook approvals. Every Codex home (~/.codex and each ~/.codex-* account home) keeps its own config.toml, so a new or changed hook runs only in homes whose trust was updated. For each home, run hooks/list and then config/batchWrite with CODEX_HOME set to that home. Missing one home makes the hook silently absent in sessions that use it.

The current default model is gpt-6-astra. Model, reasoning, and service-tier choices are Codex preferences, not cross-tool parity requirements.

bin/skill-prune owns managed [[skills.config]] disable entries and keeps them aligned with Claude’s native skill overrides and skills-disabled.json. Usage checks include default and active homes plus every account home declared in emacs/config.org, deduplicate resolved account homes, and refuse stale-skill decisions when evidence is unavailable or malformed. bin/codex-config-prune removes stale project and hook trust state. Generated or managed entries should be changed through their owner, not edited as unexplained one-off configuration.

The [projects."..."] trust keys name absolute repository paths, so moving a repository means rewriting its key. A running Codex appends its own trust entry whenever it starts in a directory the config does not already cover, so a rewrite has to merge with any entry Codex added for the new path rather than land beside it. TOML rejects duplicate tables, and Codex reports a duplicate as a fatal failed to load configuration that aborts every session, not as a warning about the one affected project. The 2026-08-03 migration out of ~/My Drive/repos hit this: the rewritten tangodb key collided with an appended one.

Python entry scripts that import dotfiles modules set sys.dont_write_bytecode themselves: the Codex app and launchd jobs do not inherit PYTHONDONTWRITEBYTECODE from .zshenv, and Drive must hold no caches.

Skills

The Codex skill index documents discovery, programmatic and private roots, managed .system content, pairing, and maintenance. The generated tracked skill inventory is the cross-tool catalog of repository-owned public skills. Dynamic plugins, private skills, ignored runtime system skills, and skills in other repositories are outside its scope.

The emacs-freeze skill moved from emacs/.codex/skills/ to the global root on 2026-10-10 for the same reason as its Claude counterpart; it reads emacs/freeze-incidents.org first.

The slack-credentials skill (2026-10-23, revised 2026-10-24) covers the xoxc/xoxd session credentials of claude/bin/slack.py, which now refreshes them from Chrome by itself (claude/bin/slack_chrome.py); the skill handles the remaining failures, and its manual browser fallback runs in Codex through the browser tool that the chrome-use skill selects.

Project skills use synthetic contact examples and route MCP credentials through the owning account’s secrets context without listing account or vault topology.

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 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 documented OpenAI Chrome workflow uses the desktop app and its Chrome extension. Direct use of desktop browser components from CLI or Emacs is not established by that documentation as a supported standalone installation. Do not infer CLI support from the availability of a browser helper executable.

The desktop app generates its browser MCP configuration and installs bundled plugins for its active Codex home. These writable settings and plugin directories must remain account-local. The agent account manager seeds them once from the canonical home, preserves later account-local changes, and converts legacy shared symlinks into independent files and directories. Other shared resources, such as skills and session history, retain their existing sharing behavior. Changes to canonical preferences do not automatically update existing accounts.

Use the app’s generated plugin registration and the standard plugin installer. A marketplace source belonging to another account can prevent the app from refreshing its bundled plugins. Do not compensate by maintaining a separate set of release versions, browser-client hashes, or broader trusted code paths. Homebrew’s supported desktop package is chatgpt; the discontinued codex-app package can report its older runtime as current.

The command-line shell/shims/codex launcher retains signed Homebrew runtimes so updates do not delete an executable backing a running CLI session. This protects CLI executable lifetime; it does not establish browser functionality.

Local sessions get their browser from Playwright MCP, not from the desktop app. The launcher adds process config overrides to every local session launch: they disable the chrome@openai-bundled plugin and the app-generated node_repl MCP server, whose components live inside ChatGPT.app, move with each app release and are documented only for the app, and they register a playwright MCP server. bin/playwright-mcp-launch starts the pinned @playwright/mcp installed in ~/.local/share/playwright-mcp (outside Drive) with --extension, so it drives the user’s own Chrome, logged-in profile and visible tabs, through Microsoft’s Playwright MCP Bridge extension, and writes page snapshots to ~/.cache/playwright-mcp rather than the session directory.

The extension asks for approval on every connection unless the server presents the token of the profile it connects to; each profile has its own token, and a token from another profile is rejected rather than prompted for. The launcher therefore fixes the profile at server start (the one Chrome last used if it has the extension, else the first that does, which is Playwright’s own rule) and passes that profile’s token from the personal 1Password Automation item playwright-mcp-token-<profile directory> (e.g. playwright-mcp-token-profile-9), read through the op-automations cache. A profile without a stored token gets the approval page. After regenerating a token, store the new one and forget its cached reference. The desktop app keeps its own Chrome plugin; account configurations are not edited. The global AGENTS.md names the server’s browser_* tools as Codex’s browser control. The paired chrome-use skill holds the browser rules for both agents (focus, windows, including visible windows opened by browser-launching scripts, retries) and loads before any browser step or such script. It also points form filling at the private, gitignored claude/context/personal-details.md, and has agents leave card numbers, passport numbers and passwords for Pablo, saying so up front instead of asking permission to type them. Since 2026-10-19 it also requires agents to close every tab they open, including tabs a download link opens outside their tab group, and to work through lists of pages in one tab.

Named-profile runtime settings and effective Chrome availability are inspected through the CLI’s profile-aware diagnostics; installation metadata uses the separate plugin commands. Neither private diagnostic output nor credentials are printed. This local lifecycle integration uses the standard installer and does not maintain release pins or weaken trust. It is not a claim that OpenAI supports standalone desktop browser components in every CLI environment.

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 triage-personal-todos skill keeps brief-only requests read-only, binds the personal corpus and canonical repository, and verifies current task evidence before selection. Confirmed completion uses the shared helper’s prepared update, with Org workflow/date checks and explicit rollback limits. The engine is a separate private dependency configured, with excluded roots, in ~/.config/triage-notes/config.json; the launcher never discovers or copies it from another workspace.

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 move-session-log skill rewrites session metadata and Codex’s project-filtered thread index using exact session identities and known metadata fields, preserving historical tool records. It inventories discovered shared profiles’ active/archive stores, history, indexes and databases; absent artifacts and discovery limits are explicit. Single-session imports require the target session to be closed, while unrelated sessions remain open. SQLite read transactions, online backups and exact-row updates coordinate live database access; only JSONL files requiring replacement need closed writers. Bulk rename and explicit --offline retain full-store offline checks and captured database/WAL snapshots. Both modes require writer checks and private recovery backups; a backup directory whose parent does not yet exist is refused by name. Per-file and SQLite operations are not one transaction; a journal records partial failures. Metadata readback is distinct from observing the actual live resume consumer. Database updates cover thread cwd, not independent project associations; unsupported generations and schema side effects refuse before writes. The shared writer check reads lsof’s *NNN form for descriptors above 999 as a plain descriptor, so a descriptor closed mid-scan no longer refuses the run. Bulk rename accepts several source paths sharing one destination, which bin/migrate-profile uses to consolidate retired Emacs profiles into the current one. For Emacs sessions, completion also requires the matching buffer name and directory to agree with the live server and persistent metadata. Idle live buffers use thread/settings/update before their directory and name change; instance-name collisions preserve the existing buffer. Agent Log’s separate catalog and rendered index are part of its resume path. Moves used through Agent Log are verified by invoking agent-log-resume-session from the original rendered file, then reconciling that file’s indexed location.

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/.codex/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. The receipt check treats only documented first-turn runtime context as non-conversation: the AGENTS.md and environment user fragments plus the developer fragments codex-cli 0.153 emits at startup (generic, host skills, permissions, collaboration mode, multi-agent role and mode instructions), and the task_started event including the root_turn_id codex-cli 0.160 adds; any other preamble keeps the run pending. 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).

Delivery and manual reconciliation require the exact frozen prompt, including its marker, in a new user message after the saved transcript boundary. Busy state and unrelated transcript growth do not acknowledge delivery. Emacs client calls time out after 15 seconds; an ambiguous result leaves the submission pending for receipt reconciliation.

Native [[skills.config]] entries can disable a tracked skill without deleting it. Programmatic skills remain outside ordinary interactive discovery and are invoked explicitly by the runner.

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 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. It verifies exact non-secret ring placement without implicit clipboard forwarding, while honoring text-transform acceptance and checking an independent payload copy. Credentials follow the secrets workflow, not the persisted Emacs kill ring; clipboard placement and verification are reported separately. A session outside Emacs also gets a clipboard copy by default. 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 — configures Claude Code when explicitly requested from Codex. It preserves documented endpoint paths, uses an explicit secret resolver, and distinguishes configured, authenticated, and tool-tested states before reporting readiness.

The project-local optimize-agent-instructions — disabled, see skills-disabled.json — verifies instruction placement for each target runtime before externalizing guidance. It preserves routed references and useful rules, and does not assume Claude imports or path rules load in Codex.

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. 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.

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. Since 2026-10-18 it first checks the known local causes indexed in claude/context/README.org (e.g. lulu-firewall.md, LuLu blocking a Homebrew binary after an upgrade).

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. For scheduled jobs it points to the shared alerting principle (claude/context/alerting.md, launchd-heartbeat) and to unattended credential renewal (claude/context/secrets.md), added 2026-10-09.

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 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. A one-sided hook deletion passes only when the hook’s manifest entry is not paired and names no file on the deleted side (for example claude-only with codex: null). The 2026-10-05 removal of unregistered Claude-only hook scripts deleted their manifest entries with them and needed no Codex change.

The Claude Stop hook block-done-over-running-tasks.py (2026-10-10) has no Codex counterpart yet. Whether Codex background terminals outlive a turn, and whether its Stop payload lists them, is unassessed; the manifest records it as claude-only until then.

The Claude TeammateIdle hook hold-idle-teammate-with-running-task.py (2026-10-10) has no Codex counterpart because Codex has no agent teams.

The Claude-only guard policy named_agent (2026-10-19) has no Codex counterpart for the same reason: the agent-guard.sh engine runs it for Claude’s Agent tool only.

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 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 catalogue preserves meaning, quotations, citations and functional markup. It uses style heuristics rather than authorship tests or word quotas, keeps functional URL parameters, and does not delete unverified citations. Inline edits and read-only reviews do not imply file or public writes. Calling workflows invoke it explicitly; it is no longer a wikipedia-article phase.

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 and rules

The tracked hooks.json is the registration source. It lives outside the dotfiles repo’s project-local .codex/ directory so Codex does not load it twice. Registrations normalize Codex payload and tool-name differences before dispatching to the paired scripts documented in the hook index.

Since 2026-10-01 every PreToolUse guard runs in one Python process: hooks.json registers only hooks/agent-guard.sh for the shell tools and for apply_patch|Edit|Write. It runs the engine in lib/python/agent_guard and turns any failure into a denial. Claude’s exception for a crash on its read-only tools does not apply: Codex has no Read, Grep or Glob tool, so its agent-guard.sh keeps READ_ONLY_TOOLS empty; it shares the Claude script’s strict JSON.parse reading of the top-level tool_name, which never runs for Codex. 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), run per tool in the order the old registrations did. For credential-prone shell commands the last policy, codex_wrap, returns an updatedInput that pipes the output through claude/hooks/redact-secrets.sh, keeping the exit status. 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 the shell tools and apply_patch|Edit|Write 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. For patches only added lines are inspected; removals 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. The same day the sensitive-read, secret-leak and lib-codex-paths.sh path lists dropped that store’s directory, which no longer exists.

epoch-brief-freshness.sh runs after shell tool calls (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.

Like any new definition, it runs only after native Codex hook trust is approved.

The reminder hook remind-claude-readme-update.sh returns PostToolUse additionalContext JSON; plain stdout would not reach the model.

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 guard no longer classifies .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), while git commit -m "$(cat FILE)", git commit -F FILE and a following cat FILE still deny. tests/test_env_op_template_policy.py covers the Codex runtime too.

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: 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. A shell -c body is unquoted before the scan so sh -c 'git push …' is still a push. The commit-selection helper stops at shell redirections and accepts a joined <<EOF delimiter instead of reading them as pathspecs. tests/test_inert_text_scans.py runs the Codex runtime too.

The GitHub write guard requires repository authorization or an explicitly authorized scoped contribution grant for pushes and mutating gh operations. It reads the allowlist and the automations registry from the committed HEAD revision, so a working-tree edit to either confers nothing. It classifies each literal shell command nested in functions.exec independently and refuses dynamic or ambiguous nested calls; read-only GitHub inspection remains available for undeclared repositories and may be piped or chained. A compound command is judged one and-or element at a time, as for Claude (claude/README.org, 2026-10-05): each write is checked alone in the directory it runs in, the command’s own or the nearest preceding literal cd DIR of the same && chain; explicit git -C, -R, GH_REPO and URL targets still decide; writes to two repositories, or after an unresolvable or ended cd scope, are refused; text-only elements (printf, echo, kill-ring-put, pbcopy, output filters) without substitutions or file redirection run no write. Syntax outside that grammar keeps the old rule, refusing any write in a compound command. Writes hidden in code the command runs (substitution and backtick bodies, sh -c strings, eval, xargs, text fed to a shell) are extracted and judged as whole commands under the same rules, as for Claude (claude/README.org, 2026-10-05). 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, and no other operators or substitutions within that element, which the 1Password policy must approve. A registry match falls through silently rather than explicitly approving the write, including for pull-request merges, so the normal permission layer and the global user-authorization rule still apply. 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.

The Elisp post-edit hook is registered on the outer composed-tool event as well as direct edit events, so it recovers changed paths from nested patches instead of depending on the nested runtime to dispatch a separate pre-tool event.

Security guards block unexpected runtime and bootstrap failures with exit code

  1. Secret scans include native Write.content and Edit.new_string as well

as patch text; the hook contracts list the covered entry points. The regression fixtures exercise those actual entry points with malformed input, missing dependencies and startup helpers, and harmless editing payloads. These guards run with the agent’s OS user and filesystem access.

Secret-file exceptions apply per destination in native multi-file patches. Ordinary-file sections remain scanned even when the patch also changes or deletes an exempt file. Moves from exempt to ordinary destinations deny because unchanged source contents are unavailable without reading the file. Native Write=/=Edit exceptions retain their single-destination scope.

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 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: claude/hooks/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) and denies every other shape, including unrestricted share links, shapes it cannot place, interpreter programs naming a broker (unless a recognized Python document edit) and a broker named outside a literal nested exec command. 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; 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 takes read’s non-printing shapes (without --out-file) and cache forget is allowed; the sensitive-read guard denies naming the plaintext cache directory (…/op-automations/cache). 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. Denials describe a failure to classify the command safely, not proof that a credential tool ran: protected names in an unclassified program may be ordinary prose. Programs with subprocess calls remain outside this language even when those particular calls are benign. Document edits permit validated standard imports, data bindings, text reads/replacements/writes and JSON conversions, including a bound data value’s update with one validated dictionary literal. A mutation alone does not satisfy the required file write. This exception requires one standalone direct Python command and one complete literal functions.exec wrapper with inert metadata. Unknown calls, dynamic expressions, multi-call wrappers and shell tails retain conservative scanning. 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. For a recognized literal functions.exec wrapper, Codex uses the decoded shell command for both activation and scanning; JavaScript newline escapes cannot conceal a following network command.

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. The closed single-call wrapper accepts the same literal fields with unquoted JavaScript keys or exact JSON-quoted keys; duplicate and dynamic fields deny. 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. Nested shell commands are decoded with valid UTF-16 surrogate pairs preserved. The secret guard validates all extracted command contexts before classifying any of them; malformed contexts, unsupported NULs and extraction failures deny execution rather than turning a failed extraction into an empty command list. 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.

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, so direct and functions.exec commands behave alike. 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 (core.hooksPath in ~/.gitconfig) runs its checks only in agent sessions, identified by CODEX_THREAD_ID or CLAUDE_CODE_SESSION_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 --agent codex keeps the committing thread’s turn from ending until those debts are paid, releasing after two consecutive blocks in one turn 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. agent_guard.policies.hookspath denies overriding core.hooksPath and allows a plain read (--get or the bare key). The Stop hook and agent-guard.sh both need trusted current definitions in the active Codex profile. 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. For a git commit -- PATH, the documentation audit writes its candidate tree from the temporary index Git is committing, because the real index is locked.

Emacs-owned sessions use eight explicit codex-hook-wrapper registrations for lifecycle forwarding. The wrapper comes from the Emacs package source tree because the build tree does not contain that script; its configured source path and Emacs socket are part of the integration. Shell matchers cover the supported shell and execution tool names; edit-style tools share compact matcher groups that must remain consistent with the trusted-tool list. UserPromptSubmit uses the event’s required empty matcher. Stop and compact events forward lifecycle state; they do not create a Claude Notification event. The wrapper calls Emacs through the emacsclient shim. Only PermissionRequest waits for an answer, for at most codex-hook-answer-timeout seconds; the other events are sent in the background, so a stalled Emacs server never holds up a Codex turn. After loading a different profile, (codex--ensure-hooks-json) rewrites the wrapper paths.

rules/default.rules is the native command policy. Hook allow/deny decisions supplement it; allowing one layer does not bypass another guard or authorize an external write. It allows paper-fetch without a prompt.

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. Nested Bash events from functions.exec omit their literal workdir and report the session directory instead. For those events, the Codex helper matches the exact command to the most recent parent tools.exec_command record in the session transcript and accepts only one unambiguous, existing workdir. The Elisp evidence gate recovers this context before routing the command, while the original transcript metadata is still available.

Commit guards check explicit path selections against a disposable candidate index, leaving unrelated staging untouched. An --only directory selection includes tracked files and staged additions even below ignored directories, but excludes untracked siblings. Untracked documentation cannot satisfy a requirement for a commit that would omit it. Inspection rejects configured clean filters without executing them. Unsupported selection syntax is denied with a reason rather than checked against a different candidate. 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 heredoc delimiters, like their bodies, are shell input rather than Git path selections. Interpreter-fed heredocs remain visible to the command classifier.

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 hooks run before the git add they are judging, 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.

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.

Codex has no counterpart to the Claude hook chrome-bridge-recover.py (claude-only in ai-config-sync.json): the Codex CLI drives Chrome through the playwright MCP server’s browser_* tools, and the desktop app through its Chrome plugin and control-chrome skill, not the Claude in Chrome extension whose per-account profile that hook reopens.

redact-tool-output.py (paired with Claude’s) masks shell and MCP tool output through the same two layers (shape patterns and the known-value index). The shared numeric filter preserves complete UUID tokens while still masking standalone sixteen-digit numbers and malformed UUID lookalikes. Contextual credential patterns and known-secret fingerprints still apply to UUIDs. Codex has no field that rewrites a result, so when masking changes anything the hook returns decision: "block" with the masked output as the reason, which the model sees instead; images from credential pages are dropped the same way, and on those pages browser text has credential-like strings masked (credential_pages.mask_opaque). Not covered (Codex 0.158.0): Codex’s own raw logs, and, for commands the guard engine does not wrap, chunks of a still-running exec_command read through write_stdin and results Codex marks as errors. New hook definitions need native trust approval before they run. The one output class Codex blocks before execution is a shell function body: agent_guard.policies.secret_leak denies which, type or whence -c on a shell function, functions, typeset -f and $functions through claude/hooks/lib-function-body-policy.py, because on 2026-09-30 such output carried literal Google OAuth credentials into a transcript.

Verification

After changing shared configuration, run bin/ai-config-sync audit. Its hook check resolves each command’s executable after skipping leading VAR=value assignments and interpreter prefixes. After a tracked public skill name or catalog description changes, run bin/docs-audit generate first. For hook changes, verify the registration in hooks.json and exercise the real Codex event payload as well as the standalone script. How the audit treats each tool’s plugin cache is documented in that tool’s README.

Codex’s instruction discovery, tracked native configuration, lifecycle hooks, rules, plugin state, and managed system skills are genuine differences. They remain explicit rather than being hidden by fallback behavior.

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. Each project has an AGENTS.md file; the workspace AGENTS.md supplies shared context. Project creation guidance lives in Forethought/context/project-workflow.md.

The notes folder stays outside Git. Codex’s shared project_root_markers includes .agent-workspace alongside .git, and Forethought carries that marker so nested sessions load the parent instructions. Existing Codex account homes retain separate configuration files; keep this setting in those copies as well as the shared source. Shared instruction pairing is documented in agents/configuration.org. Code checkouts belong under ~/repos/forethought/; start from the notes folder when the engagement instruction chain is needed.