Repository navigation
v2.8.28
[2026-10-25] — v2.8.28: Codex as a second provider (dispatch, hooks, context gauge, one skills dir for both CLIs), the owner's settings gate, the ratchet's budget exit, the api suite at 75 s
Added
-
hooks:
settings_gate, the provider-settings gate (fail-closed, PreToolUse, beforesettings_backup): no AI changes~/.codex/*.toml,hooks.json,auth.json,rules/**(and the same under$CODEX_HOME),~/.claude/settings*.json, the repo's.claude/provider_manifest.jsonor.codex/hooks.json, or runsaipass doctor --fix/setup.sh/provider_wire.py, without the owner. Interactive Claude Code asks (its own settings stay with Claude Code's native prompt); a Codex caller and every headless seat are denied, the reason pointing at @devpulse. The Codex bridge turns any PreToolUseaskinto adeny, since Codex runs the call on an ask.docs/settings_gate.md. (hooks suite 2,730 passed.) -
ai_mail:
dispatch --provider codex [--model <slug>]wakes a branch on the Codex CLI (codex exec --json, orcodex exec resume <thread_id>when the branch's pointer names a Codex thread) through the same monitor, lock, register row and wake-back as a Claude wake. The session pointer and the register row carryprovider; a pointer for the other provider starts fresh. The monitor reads startup from the live stream, the thread id fromthread.startedand the result from the lastagent_message; anerrorevent or a run with noturn.completedbounces and is not re-run.--modelis passed to Codex verbatim; none means Codex's own config. Codex hooks ride the repo.codex/hooks.jsonwritten from the manifest'scli.codexrows (the hand scripts it replaced rest in.codex/hooks/.archive/). -
hooks:
SessionStart:session_pointerrecords every interactive session as its branch's resume target, with the CLI that runs it: the Codex bridge tags each payloadprovider: codex, a payload without the tag is Claude's, acompactstart leaves the pointer alone, a cwd without a passport writes nothing. Interactive sessions only: aclaude -p/--printancestor or a Codex rollout whosesession_metasource isexecleaves the pointer as it is, so a stray one-shot never becomes a branch's resume target. Rows in.aipass/hooks.jsonand in both manifestcliblocks. A missing transcript at turn 0 logs at debug, not as a failed stat; a hook chain that ends in Codex's app-server daemon logs "Codex session" once at debug instead of "no session-file ancestor" at info. -
commands:
/codex(Claude Code) and$claude(Codex skill) move a chat to the other CLI in one turn: memories saved, the live thread written to.ai_mail.local/handoff.md, and the tmux pane respawned with the other CLI greeted by the handoff. The pane is found by walking the shell's ancestry, else by command and path (Codex runs its shells from a daemon, not the pane).setup.shalso exportsAIPASS_HOMEin~/.profile, which every login shell, Codex's daemon included, reads. -
skills: one source for the rituals both CLIs run.
.agents/skills/{prep,memo,compass}/SKILL.mdis the master (the Agent Skills standard folder Codex scans natively);.claude/skillsis a tracked symlink to it, so Claude Code lists the same three as/prep,/memo,/compassand Codex as$prep,$memo,$compass. The duplicated.claude/commands/{prep,compass}.mdare gone (Claude Code reads a skill folder exactly like a command file);compassstates its arguments as the words after the command, since Codex has no$ARGUMENTS. The switch skills stay provider-local:/codexin.claude/commands/,$claudein.codex/skills/..agents/skills/README.mdsays the rule. -
hooks: the context gauge and the compact snapshot read a Codex rollout as well as a Claude transcript (
context_window.py1.4.0; the shape comes from the first line,session_metameans Codex). On Codex the fill is the newesttoken_countevent'slast_token_usage.total_tokens(what Codex's own status line counts), the model the newestturn_context, the window the rollout'smodel_context_window(else 95% ofmodel_context_windowin~/.codex/config.toml, else 200k), and the auto-compact point 90% of the full window, lowered bymodel_auto_compact_token_limitwhen set (codex-rsopenai_models.rs). The gauge says$prepwhen the payload carries the bridge'sprovider: codex,/prepotherwise. Before this, the gauge never fired on Codex and the snapshot stamped a None fill. Eight Codex cases inline in the existing tests (hooks suite 2,696 passed). -
aipass:
init,newandadoptwrite a project's prep skill at.agents/skills/prep/SKILL.mdand make the.claude/skillssymlink, instead of.claude/commands/prep.md; where the filesystem refuses a symlink the skills are copied into.claude/skillsand the CLI says the copy will not follow the master (git config core.symlinks true).init updateanddoctor --fixmake a missing link and retire an unedited legacyprep.mdtoprep(disabled).md; an edited one is kept and named.doctorgains the Scaffold rowsshared skills(every SKILL.md named and described),skills link(resolves to.agents/skills; warns on missing, copy or elsewhere) andlegacy prep. New docsrc/aipass/aipass/docs/two_clis.md: what each CLI reads, the spelling of each ritual on each, a compaction on each, the limits;.claude/README.mdrewritten as the short map of that folder and.codex/README.mdadded. (aipass suite 1,370 passed.) -
daemon: a command job may declare
ok_exit_codesin its schedule row; a run ending in one of them is recorded as a completed fire, not a FAILED one. Seedgo'sratchet-weeklydeclares[6], the budget-spent exit: a week that measured a fragment is a recorded week.
Fixed
- hooks: the Codex context gauge reads
config.toml's compact limit and window on Python 3.10 too (one top-level int by regex, notomllib;context_window.py1.5.0), and the process-args test waits for its child to exec and accepts the macOS framework interpreter. - hooks: three tests now pass in a clean CI clone: the process-args test reads a child it spawns (pytest-xdist retitles its workers), the reclaim help test builds its own registry (a clone carries none,
AIPASS_REGISTRY.jsonis gitignored), and the failed-ps test counts only ppid lookups (macOS reads args throughpstoo). - hooks: the Codex bridge's ask-to-deny branch states the parsed answer exists, clearing a type-check error; Codex's config path lookup is tested directly (
codex.py2.4.2; hooks suite 2,759 passed). - hooks: settings_gate judges a move or delete of a settings folder (
mv ~/.codex,rm -r ~/.codex/rules, a folder holding one) and counts only an executed installer (./setup.sh,bash setup.sh), not one that is read; the Codex bridge still denies PreToolUse when prax cannot load;sessions reclaimstops sessions by branch path, not a namesake; the PreCompact stamp locks.trinity/;boot's dead-window resume uses the picked session id; a failed tmux kill falls through to the signal; testwrite_gate reads relative paths from the payload's cwd;verifychecks the Codex PreToolUse matcher reaches Bash and apply_patch;testreports a crashed injector as crashed;speakleaves no WAV behind;feedback onsays when hooks.json still has it off; the alert cut marker names.aipass/alerts.json. The thirteen findings of @hooks' own ponytail audit, run on Codex, fixed on Claude. (hooks suite 2,755 passed.) - api: the test suite runs in 75 s instead of 212 s (361 s under load) with the same 2,008 tests: a session fixture makes
os.fsynca no-op (the autouse fleet-cache clear logged an fsynced JSON write four times per test), and the read-only host-API test modules share one session-scoped FastAPI app while the modules that mutate app state (pairing rate window, face, added routes) keep their own. Seedgo's mutation probe can now afford the suite. - ai_mail: Codex's
app-server/mcp-serverdaemon and a headlesscodex execno longer count as an interactive session, so they no longer block a dispatch (pid_lock.py1.2.0branch_occupant; the daemon trio is spawned by the first Codex TUI opened in a folder, parented bysystemd --user, and outlives every ended room, so a dispatch to that branch was refused as occupied with no room open). The occupancy row names the CLI it found (wake.py3.7.0). - ai_mail: a dispatch to a branch whose dispatch lock is held (a session woken for other work, or a monitor draining) now says "NOT woken" in the output and the log and exits non-zero with the re-wake command, instead of reporting that the running session will read the mail; that session has its own prompt and may finish without reading it.
- seedgo: a ratchet campaign that runs out of budget exits 6 (
EXIT_BUDGET_EXHAUSTED), not 2, and still writes its series line; a branch whose last series line is a budget fragment is the next pick, before never-probed branches, and the run probes only the functions the fragment left (slice k/N, N forecast from this slice's rate) until the tree is measured.ratchet-weeklydeclaresok_exit_codes: [6]. - flow: the post-close runner's stale-lock takeover is serialized under an OS lock on
<lock>.takeoverand re-reads staleness inside it; two runners that read the same dead pid could both take over, the slower one deleting the faster one's live lock. 46 dead registry rows (the/tmpand external-path quarantine plus the test-made FPLAN-9990) dropped and saved to flow's.archive/, their 22 backup copies archived;register/unregisterwith a missing argument pinned at exit 2 with the usage line. (@flow's 2026-10-25 rounds, the owner's cleanup ruling.)
Changed
- prompts: the tier 0 kernel states the standing rule that
.archive/is ignored everywhere in the tree: never read, moved, swept, fed or processed by any agent or sweep.