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.
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.
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.
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.
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.
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
- Secret scans include native
Write.contentandEdit.new_stringas 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.
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 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.