This commit modifies the ai-jail script to properly handle the `--dry-run` option without affecting the jail arguments file. Additionally, it updates the migration version assertion in the API credentials tests from 70 to 71, ensuring consistency with the latest migration changes.
This change addresses an issue where the `ai-memory upgrade` command rejected official Linux/macOS release tarballs due to missing entries in the extraction allowlist. The allowlist now includes top-level directories and files that are part of the release archives, ensuring successful extraction and installation. Additionally, tests have been added to verify the integrity of the allowlist against the release workflow configuration.
`ai-memory upgrade` replaced its own binary and then re-rendered the staged
hook configs from the same, now exe-deleted process. On Linux the kernel
reports `/proc/<pid>/exe` as `<path> (deleted)` once the inode is unlinked,
and that string was embedded verbatim into every hook command, so all nine
hooks failed with `not found` until the suffix was stripped by hand (#1027).
Two layers:
- the staged-hook refresh now re-runs `install-hooks --apply` from the
freshly installed binary as child processes, so rendering happens in a
process whose own exe is valid;
- hook rendering centralizes the executable resolution in one helper that
strips the kernel's ` (deleted)` marker and falls back to the bare
`ai-memory` command name when the resolved path is missing, instead of
baking a dead absolute path into every hook.
Verified: 5 new unit tests (marker strip, the corruption-window strip,
missing-path fallback, live-exe sanity, refresh command shape); full CLI lib
suite (1215 passed); and an end-to-end sandbox run where the fixed binary
self-upgrades from a local release and the rewritten settings stay clean.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Add a FutureInfra example to the openai-compat section of docs/install.md, list it in docs/llm-providers.md, and add an [Unreleased] CHANGELOG entry. Docs only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review on #1004 (part A) found two real problems with the original
warning:
1. The wording implied a leak ("secret-redaction rule is NOT
applying"). An invalid marker fails CLOSED: capture_policy turns
every file and shell tool event into MetadataOnly until the marker
is fixed. Nothing leaks -- content is reduced to metadata, not
exposed. Reworded to say exactly that.
2. The check was parse-only (capture_config_problem used to just ask
"did the TOML parse"), which misses a `[capture]` table that parses
as valid TOML and has a valid shape, but whose `ignore_paths` still
gets rejected by `compile` -- an unsupported glob character
(`ignore_paths = ["secrets/{a,b}"]`), too many patterns, or a `~/`
pattern with no home directory to expand it. That case reported
`problem=None` even though `capture_policy(cwd).state()` was
already `Invalid`.
Fix: `capture_policy(cwd).state()` is now the single source of truth
for whether the marker has a problem -- the exact state the live hook
path itself resolves to, not a separate parse-only re-derivation. A
verbose parse (`read_capture_config_verbose`) still supplies the
reason text for a TOML/shape failure; a new
`describe_invalid_capture_config` (mirrors `compile`'s own validation,
now returning descriptive `Result<_, String>` errors instead of `()`)
supplies the reason for a config that parses but doesn't compile, so
the message names the specific rejected pattern and why.
`CapturePolicy` gains a `state()` accessor for this purpose; the hot
hook path is unaffected (same resolution, same fail-closed behavior,
just an added read of a field it already computes).
Tests: 8 new (3 in hook_capture.rs's capture_config_problem, including
a regression test for the exact dropped-`#`-in-a-comment TOML shape
found in practice, with a neutral fixture this time; 5 in
capture_policy.rs covering `state()` and `describe_invalid_capture_config`
across every PolicyState, including the parses-but-rejected-glob case
this fix exists for).
CHANGELOG entry added under [Unreleased].
Follow-up to #1001 (#998's real fix), per review on the superseded #1009:
the negative case -- that grok, kimi-code and pool must never call
/handoff from session-start at all -- isn't covered by #1001, which
only touches the eight bundles that do fetch one.
Each of the three documents why in its own session-start.sh: Grok and
Kimi Code discard SessionStart stdout (Kimi delivers the brief from
user-prompt-submit.sh instead), and Pool's own comment says fetching
there would be destructive and silently lose an undelivered handoff.
A regression here would burn a single-use handoff with nothing to
show for it on the harness side.
Runs each real shipped script against a PATH curl shim (same pattern
the existing antigravity and grok post-tool-use cases already use) and
asserts exactly one request -- the capture POST -- with no /handoff
GET anywhere in it.
Verified: 108 -> 114 assertions passing under busybox sh (also checked
against upstream main pre-#1001 state to confirm the +6 delta is exactly
this addition, no loss elsewhere); 137 -> 143 under Git Bash. shellcheck
clean (every existing finding in the file predates this change).
usable_ai_jail requires bwrap on Linux but sandbox-exec on macOS, and the
fixture's PATH holds only its own fakes, so on the macOS runner ai-jail read
as unusable and --jail correctly failed closed. Write the backend the host OS
needs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
The checklist and `--jail=LIST` emitted only enabled toggles, so the user's
own ai-jail config (e.g. a global `~/.ai-jail` enabling `docker`) could still
mount what an unchecked row or an explicit list left out, and the summary
line misreported it.
- Interactive checklist: `marked_choices` passes every row the user saw,
checked as `--X` and unchecked as `--no-X`.
- `--jail=LIST` (including `none`): after the named entries, every visible
checklist row the list did not name is forced off with `--no-X`. Rows that
are not visible (absent credentials, CLI-only toggles) are never forced.
- Bare `--jail` is unchanged: smart-default rows only, the rest left to the
user's ai-jail config, because no selection was shown.
- `JailToggleChoice::implied` marks rows forced off by omission, so the
summary names the user's own `no-X` entries and says "everything else in
the checklist off" for the rest.
- Tests: an adversarial unit test (unchecked docker row and `--jail=none`
yield `--no-docker` / `--no-*` for every visible row, with bare `--jail`
as the control); parse, checklist, summary and end-to-end expectations
updated, the latter platform-aware for the Linux-only rows.
- Docs: design §5 semantics, cookbook, support matrix, CHANGELOG.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
`ai-memory run` can now decide which ai-jail credentials and capabilities a
jailed session gets, from the CLI or an interactive checklist, with smart
defaults so Enter does the friendly thing.
- ai-memory-workstream/jail.rs: a toggle table (credential mounts github,
aws, kube, gcloud, docker-config; ssh; worktree; docker, gpu, display,
pictures, tailscale; CLI-only audio, x11, host-shm, terminal-passthrough,
update-check, mise, toolchains), support detection from the installed
ai-jail's `--help` (exact `--X` tokens, so `--docker` never matches
`--docker-config`), injected host facts (home, SSH agent, origin URL,
linked worktree, project `.ai-jail` presence), the checklist with smart
defaults, and the `--jail=` list parser (`no-X`, `all`, `none`; reserved
security switches and ai-memory-owned flags refused).
- build_ai_jail_invocation emits the chosen `--X`/`--no-X` and a
`--no-save-config` baseline before the `--`, so ai-jail never writes the
run's transient flags into the repository's `.ai-jail`.
- inspect_repository reports the `origin` URL and whether the cwd is a
linked worktree.
- run.rs: `--jail[=TOGGLES]` / `--no-jail` (also stripped when they land in
the native argv), a pure jail_decision table, an explicit `--jail` re-exec
before the managed run is prepared (failing closed when ai-jail is not
usable), and the line-based checklist after the `--yolo` offer. A project
`.ai-jail` replaces the checklist and the bare-`--jail` defaults.
- Tests: unit coverage for parsing, support detection, defaults, the
decision table, flag stripping, and the checklist grammar; a real
`ai-jail --dry-run` over every toggle the installed ai-jail advertises;
end-to-end runs of the built binary with fake ai-jail/bwrap/claude and a
mock server, including PTY runs of the offer, the checklist, a project
`.ai-jail`, and `--yolo --no-jail`.
- Docs: design §5, cookbook yolo recipe, support matrix, CHANGELOG.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
is_racy_read only recognized libgit2's Filesystem-class "file changed before
we could read it", so the Os-class "failed to read file into stream" (ENOENT:
listed by the walk, gone when streamed in — e.g. an atomic writer's temp file
renamed away) failed the checkpoint outright instead of taking the bounded
racy-read retry. Seen as an intermittent
concurrent_commits_queue_instead_of_failing failure on a loaded CI runner
(v2.5.1 RC dispatch run). Classifier unit test covers both racy shapes and
two non-racy controls; it fails with the old single-branch check.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
- frontend-api.md (#986): the list, search, and recent routes return bare
JSON arrays, not `{ "workspaces": … }`-style wrappers (the route tests
assert `as_array()`); a page read returns `body_markdown`, not `body`; a
search hit carries workspace/project/kind and no `id`.
- windows.md (#758): native `ai-memory upgrade` is done (#801/#802), not
in-progress.
- managed-workstreams.md + support matrix (#987): document the Codex shared
daemon handing hooks a stale AI_MEMORY_RUN_ID and the `--no-daemon`
workaround until the server-side fix lands.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
Accept the design with corrections verified against the code: note the
existing live-source skip (LIVE_BATON_QUIET_PERIOD) and handoff expiry
(expire_same_cwd_auto_handoffs + post-claim sweep), so open question 5 is
mostly answered; require the notice to name the exact handoff_id; keep the
managed-run ledger claim at session start in offer mode; weigh a per-project
marker key over a server-wide switch; let an explicit accept by id ignore
to_agent; fix the memory_handoff_pop reference.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
An admission refusal is a policy decision, as on the MCP path
(invalid_request), not a server fault, so the admin send route now answers 403
instead of 500, and the reject test asserts that status. Records the
MCP/admin admission parity as security-boundaries row 8e; the reject test
fails when the send authorizes against an op the webhook does not subscribe
to.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm