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
The store test proves related_walk filters every hop, but nothing failed if
memory_read_page passed `None` instead of the caller's viewer — exactly the
2.5.0/2.5.1 leak. bob_does_not_see_restricted_pages_through_the_related_walk
drives the real tool as an ungranted user (restricted page absent from
`related`) with a no-viewer control, and fails with the call reverted to
`None`. Cited in security-boundaries row 14d.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
ai-jail integration (`ai-memory run --yolo`):
- The re-exec built `ai-jail <flags> <exe> run …` with no `--`. ai-jail
rejects one of its own flags after the command and `run` shares flag names
with it, so `run claude --yolo --env GH_TOKEN=…` aborted. The invocation now
emits `--` before the wrapped exe (forwarding a colliding flag additionally
needs ai-jail >= 2.4.2, whose guard honors the separator; the cross-tool
test gates on that version).
- The offer only checked for a file named ai-jail: Windows could show it, a
host without bwrap/sandbox-exec was offered a jail that cannot start, and a
~/.local/bin-only install was offered and then not found by the bare
`Command::new("ai-jail")` re-exec after the run was already cancelled.
usable_ai_jail(os, lookup) now returns the exact binary to exec only on
Linux/macOS with the backend present; otherwise no question is asked.
--true-yolo:
- It now implies --yolo (warning, ai-jail offer, harness dangerous mode):
alone it used to apply Claude's bypassPermissions with no warning. It is
interchangeable with --yolo for non-Claude harnesses, and recognized after
native arguments (`run claude --model opus --true-yolo`), where clap leaves
it in the native argv and it was forwarded to Claude as an unknown option.
- The claude_true_yolo config key only upgrades an explicit yolo launch, as
its doc comment stated, instead of bypassing permissions on every run.
- Removed what never worked: three CLAUDE_CODE_DISABLE_*RM* env vars Claude
Code does not read (absent from the 2.1.280 binary and its env reference),
and an empty permissions.ask array that cannot clear ask rules from other
scopes (Claude unions them). Docs now state that Claude honors explicit ask
rules and its command-safety checks in every permission mode.
Relaunch after an interrupted run:
- A launcher killed before releasing its lease (terminal closed, ai-jail
torn down) left the workstream held for up to 90s and the next launch failed
after a 5s retry. An interactive launch now parses the holder and expiry from
the 409, waits for that lease to lapse (bounded by one lease; Ctrl-C aborts),
then proceeds. A holder that renews meanwhile is reported as live and never
displaced; the server's busy check stays the only arbiter (security
inventory row 13b). Non-interactive launches keep the short window.
Tests: usable_ai_jail OS/backend/Windows/exact-path, `--` placement and the
colliding-flag regression (unit + real ai-jail --dry-run), the yolo_modes
table, --true-yolo in both argv positions via real clap parses, the reduced
true-yolo argv, and HTTP-level held-lease wait / renewed-owner / Ctrl-C cases.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
A capture-exclusion candidate or shell argument spelled with a leading `//`
(e.g. `//repo/secret/token.txt`) self-classified as a Windows UNC path
regardless of the actual host. On a POSIX host `match_paths` then filtered it
against zero POSIX `ignore_paths` patterns (a flavor mismatch), so the file was
captured instead of dropped — a fail-open in the capture trust boundary.
Path flavor for an untrusted candidate is now derived from the host (the cwd):
on a POSIX host a leading `//` collapses to a single `/` before classification,
so it matches POSIX `ignore_paths` as intended. A genuine Windows/UNC host's
UNC candidates are unaffected. Fixed in both front doors — the native hook
(`ai-memory-hooks` `capture_policy.rs`) and the generated TypeScript
integrations (`ai-memory-cli` `render_shared.rs`, `captureHostWindows` /
`windowsHost`) — with a shared regression fixture and an adversarial test per
surface (violation + POSIX control + Windows-UNC control).
Also documents (docs/users.md) that a trusted-proxy non-root user is
`AuthLevel::User` with no DB identity, so `restricted`/grant enforcement does
not apply to them — the proxy is the authz boundary — and records that boundary
and its pinning test in docs/security-boundaries.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
RUSTSEC-2026-0285 (rustls <0.23.45, TLS1.3 wrong-encryption-level handshake
accept; GHSA-2mjx-qc3c-rqvc / referenced by GHSA-wc7c-cm87-463c). The main and
relay lockfiles were already at 0.23.45; the importer companion sat at 0.23.41
because CI never audited companions/ai-memory-importer/Cargo.lock. Bump it and
add that lockfile to the cargo-audit step so the drift class is caught. Also
drop the now-stale RUSTSEC-2026-0194/0195 (quick-xml) ignores — quick-xml is in
none of the three lockfiles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
A prior forward-merge CHANGELOG reconstruction dropped the '# Changelog' /
Keep-a-Changelog header from release/2.5's CHANGELOG (main and the v2.4.2 tag
were unaffected). Restore it so the file — and the generated release notes —
start with the standard preamble again. No entry content changed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
Pre-release doc-staleness sweep for 2.5.0:
- ARCHITECTURE.md config reference: add the [maintenance] section
(enabled/forget_sweep/lint/embedding_backfill intervals +
reconcile_tombstones_deleted_pages, #929/#964) and release_base_url (#801).
- README support matrix: Hermes Agent Community -> Supported, aligning the
compact table with the authoritative docs/support-matrix.md (#933 hooks +
#942 skills are first-party).
- research-codebase-memory-mcp.md: correct the stale '19 tools' -> 23.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
ai-memory run --yolo now, on an interactive TTY only (never in hook/CI/
detached paths):
- warns that --yolo runs every tool call unconfirmed, [Y/n] default-yes;
- offers to re-run inside ai-jail when it is installed, re-execing the
original argv under `ai-jail --network --agent-state --env <NAME>…`
(--network shares the host net namespace so the loopback ai-memory server
stays reachable; only already-set credential/config env is forwarded);
- skips both prompts when already inside ai-jail (Linux hostname ai-sandbox
/ macOS PS1 (jail) ; fails open to showing the warning; Windows never).
Opt-in Claude "true yolo" (--true-yolo / [config] claude_true_yolo,
AI_MEMORY_CLAUDE_TRUE_YOLO) silences the pauses --dangerously-skip-permissions
leaves: sets the CLAUDE_CODE_DISABLE_*_RM_* env vars and injects
--settings bypassPermissions. Claude-only, off by default, sandbox-first.
Detection and argv assembly are pure/OS-explicit (ai-memory-workstream::jail)
with unit tests for every branch; a CLI integration suite asserts the argv
against the real ai-jail via --dry-run (skips cleanly when ai-jail/bwrap are
absent). Design: docs/design-yolo-safety-ai-jail.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
Patch release from the 2.4.x line. Fixes: sanitize observation titles before
truncating (#982/#980), export-okf interoperability (#979/#960), the prior
#967-#973 batch, and more. Docs: TLS-interception recipe (#954), Cheaper
Inference provider doc (#981), OpenRouter and backup docs (#949/#950, recorded
as documentation of already-shipped behavior). No new capabilities → patch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
- ids.rs: #944 and #921 each hoisted SessionId::from_native; keep one
definition (git auto-merged both into a duplicate).
- render_shared.rs: align the generated-TS shell flag with the native
capture_policy determination (shell = non-file && name != web_search)
after #966 added search_web/manage_task/manage_subagents to the non-file
list — the two front doors must agree.
- mount.rs / admin_repair_session_times.rs: set the WebMountSpec
trusted_proxy_identity and AdminState contradiction_band_min/max fields
that main added, in the release/2.5-side test constructors that predated
them.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
#944 adds a finalize-on-exit entry point that resolves a native session; per
invariant #16 and the security-boundary rule, record that own_native_session
refuses a discovered/concurrent session and the test that proves it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
ai-memory's reqwest client is built with rustls-tls-native-roots, so it trusts
the OS store and honors SSL_CERT_FILE/SSL_CERT_DIR. Document how to give the
container a combined CA bundle (public roots + the interception root) so
outbound LLM/embedding calls stop failing with UnknownIssuer behind a
corporate MITM appliance or inspecting antivirus. Cross-referenced from
deploy.md's provider-failures troubleshooting bullet.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
The submitted text stated a specific '15–60% less' range the vendor site does
not substantiate; reword to a claim we can stand behind (below list price)
without quoting an unverified figure.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
The sanitizer boundary (row 7, invariant #6) gained enforcing code in #982:
Sanitized::new now scrubs the title before applying the 80-char display cap.
Per the mandatory security-boundary rule, record the adversarial tests that
would fail if the guard were removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
DATA_HANDLING.md said there is no built-in retention expiry, but the daily
forget sweep tombstones cold episodic pages (below [decay] cold_threshold)
and hard-deletes them after hard_delete_after_days, and TTL (expires_at) pages
expire too — both on by default. Describe the actual [decay]/TTL behavior and
what stays (semantic/procedural/pinned, raw observations).
Closes#972
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
The forward-merge's CHANGELOG rebuild took the [2.4.0]+ tail from release/2.5,
but the frozen-section CI check requires the released half (## [2.4.1] down) to
match origin/main byte-for-byte. Since main ⊆ release/2.5 now, main's released
history is authoritative — replaced the released half with main's verbatim.
[Unreleased] (all 2.5 features + merged main fixes) is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
The main->release/2.5 forward-merge combined main's #919 (occurred_at field on
NewObservation/NewSession + a 4th end_admitted_session arg) and #926 (build_request
existing_titles arg) with release/2.5 test constructors that predate them. Adds
occurred_at: None to the affected literals, the missing args, and fixes doctor.rs
to import main's build_launch_plan (relocated_session_dir) after adopting the
injected session_dir_for closure. Preserves invariant #8 (embed_page_version uses
model_identity) and the AI_MEMORY_CAPTURE_OWNER gate in the generated TS capture policy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
Slice 4 (#925) identity routing was merged with its design-doc section
deferred pending the always-on vs opt-in review. Decision: always-on in
2.5.0 (opt-in would leave the same-basename grant hole open). Documents the
V70 no-backfill migration, lexical normalization, resolution order, and the
four-front-door parity.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
The merged #952 recipe used `ai-memory status --format=json`, but StatusArgs
only has --json (no --format), so the snippet failed with unknown-argument.
Also widened the example script's SECRET_EXCLUDES default (and its doc
references) to include *.pem/*.key/*.crt so private CA bundles and keys under
the config dir are excluded from the mirror by default, matching the shipped
.gitignore.
Follow-up to #952 (#950).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
The merged #951 stated OpenRouter exposes an OpenAI-compatible /v1/embeddings
endpoint as fact; that is unverified (historically OpenRouter has not) and
would silently fail for a user who configured it. Reworded to a conditional
'verify it exists first' pointer and made the multilingual-embedder
recommendation provider-agnostic.
Follow-up to #951 (#949).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm