Skip to content

v2.8.28

Choose a tag to compare

@github-actions github-actions released this 26 Oct 08:39
· 84 commits to main since this release
v2.8.28
3d0b98f

[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, before settings_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.json or .codex/hooks.json, or runs aipass 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 PreToolUse ask into a deny, 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, or codex 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 carry provider; a pointer for the other provider starts fresh. The monitor reads startup from the live stream, the thread id from thread.started and the result from the last agent_message; an error event or a run with no turn.completed bounces and is not re-run. --model is passed to Codex verbatim; none means Codex's own config. Codex hooks ride the repo .codex/hooks.json written from the manifest's cli.codex rows (the hand scripts it replaced rest in .codex/hooks/.archive/).

  • hooks: SessionStart:session_pointer records every interactive session as its branch's resume target, with the CLI that runs it: the Codex bridge tags each payload provider: codex, a payload without the tag is Claude's, a compact start leaves the pointer alone, a cwd without a passport writes nothing. Interactive sessions only: a claude -p/--print ancestor or a Codex rollout whose session_meta source is exec leaves the pointer as it is, so a stray one-shot never becomes a branch's resume target. Rows in .aipass/hooks.json and in both manifest cli blocks. 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.sh also exports AIPASS_HOME in ~/.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.md is the master (the Agent Skills standard folder Codex scans natively); .claude/skills is a tracked symlink to it, so Claude Code lists the same three as /prep, /memo, /compass and Codex as $prep, $memo, $compass. The duplicated .claude/commands/{prep,compass}.md are gone (Claude Code reads a skill folder exactly like a command file); compass states its arguments as the words after the command, since Codex has no $ARGUMENTS. The switch skills stay provider-local: /codex in .claude/commands/, $claude in .codex/skills/. .agents/skills/README.md says the rule.

  • hooks: the context gauge and the compact snapshot read a Codex rollout as well as a Claude transcript (context_window.py 1.4.0; the shape comes from the first line, session_meta means Codex). On Codex the fill is the newest token_count event's last_token_usage.total_tokens (what Codex's own status line counts), the model the newest turn_context, the window the rollout's model_context_window (else 95% of model_context_window in ~/.codex/config.toml, else 200k), and the auto-compact point 90% of the full window, lowered by model_auto_compact_token_limit when set (codex-rs openai_models.rs). The gauge says $prep when the payload carries the bridge's provider: codex, /prep otherwise. 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, new and adopt write a project's prep skill at .agents/skills/prep/SKILL.md and make the .claude/skills symlink, instead of .claude/commands/prep.md; where the filesystem refuses a symlink the skills are copied into .claude/skills and the CLI says the copy will not follow the master (git config core.symlinks true). init update and doctor --fix make a missing link and retire an unedited legacy prep.md to prep(disabled).md; an edited one is kept and named. doctor gains the Scaffold rows shared skills (every SKILL.md named and described), skills link (resolves to .agents/skills; warns on missing, copy or elsewhere) and legacy prep. New doc src/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.md rewritten as the short map of that folder and .codex/README.md added. (aipass suite 1,370 passed.)

  • daemon: a command job may declare ok_exit_codes in its schedule row; a run ending in one of them is recorded as a completed fire, not a FAILED one. Seedgo's ratchet-weekly declares [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, no tomllib; context_window.py 1.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.json is gitignored), and the failed-ps test counts only ppid lookups (macOS reads args through ps too).
  • 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.py 2.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 reclaim stops 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; verify checks the Codex PreToolUse matcher reaches Bash and apply_patch; test reports a crashed injector as crashed; speak leaves no WAV behind; feedback on says 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.fsync a 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-server daemon and a headless codex exec no longer count as an interactive session, so they no longer block a dispatch (pid_lock.py 1.2.0 branch_occupant; the daemon trio is spawned by the first Codex TUI opened in a folder, parented by systemd --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.py 3.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-weekly declares ok_exit_codes: [6].
  • flow: the post-close runner's stale-lock takeover is serialized under an OS lock on <lock>.takeover and 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 /tmp and external-path quarantine plus the test-made FPLAN-9990) dropped and saved to flow's .archive/, their 22 backup copies archived; register/unregister with 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.