Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/aer-cli-hooks-doctor-checks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@adastracomputing/aer': patch
---

`aer doctor` now reports an end-of-session hook entry with no time budget,
a relative `CODEX_HOME` and an Antigravity registration that records no
tool calls, and compares an installed `aer-hook` against the aer-hooks
0.6.0 checks bundled into this release instead of 0.5.1's.
12 changes: 6 additions & 6 deletions .changeset/aer-hooks-antigravity-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ and Antigravity loads that form for a tool event without complaint but never
runs it, so a run recorded its turns and none of its tool calls. The tool
events are now written as a matcher group, the form Antigravity fires them
from; run `aer-hooks install antigravity` again to update an existing
registration. The hook also reads the argument names Antigravity actually
sends (`CommandLine`, `AbsolutePath`, `TargetFile`, `Url`), so a shell line is
reduced to its programs and hosts, and a file read or write records its path,
as they already were for Claude Code and Codex.
Until it is, `aer-hooks status` and `aer doctor` report such a registration
as recording no tool calls and name that command.
registration. Until it is, `aer-hooks status` and `aer doctor` report such a
registration as recording no tool calls and name that command. The hook also
reads the argument names Antigravity actually sends (`CommandLine`,
`AbsolutePath`, `TargetFile`, `Url`), so a shell line is reduced to its
programs and hosts, and a file read or write records its path, as they
already were for Claude Code and Codex.
2 changes: 1 addition & 1 deletion .changeset/aer-hooks-codex-home.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
`aer-hooks install codex` now honours `CODEX_HOME`. Codex reads its
configuration from `$CODEX_HOME` when that is set, but the installer always
wrote `~/.codex/hooks.json`, so on such a machine the install looked finished,
`aer-hooks status` reported the hooks wired, and Codex never ran them. The
`aer-hooks status` reported the hooks wired and Codex never ran them. The
installer, `status` and `uninstall` now use `$CODEX_HOME/hooks.json` when
`CODEX_HOME` is an absolute path, including with `--dir` set to your home
directory; an explicit `--dir` anywhere else is unaffected. A relative
Expand Down
4 changes: 3 additions & 1 deletion .changeset/aer-hooks-headless-end.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ without a summary.
- The installer now writes `--end-budget-ms` on the entry that ends a session
(Claude Code and Codex SessionEnd, Antigravity `Stop`). The hook delivers for
that long itself, up to 30 seconds. Run `aer-hooks install` again to add
it; `aer-hooks status` and `aer doctor` point out an entry without it.
it; `aer-hooks status` and `aer doctor` point out an entry without it. On
Codex, re-running install changes the registered command, so Codex skips
the hook silently until it is approved again.
- The hook also starts a short-lived background process that outlives the
harness and finishes what is left: all of it when no budget is declared, in
which case the hook itself stops 1.2 seconds after it started. The process
Expand Down
6 changes: 6 additions & 0 deletions .changeset/aer-mcp-guard-readme-install.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@adastracomputing/aer-mcp-guard': patch
---

Docs only: the README's install line now uses the default tag instead
of `@next`, so npm shows the current instructions.
6 changes: 6 additions & 0 deletions .changeset/aer-resource-node-readme-install.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@adastracomputing/aer-resource-node': patch
---

Docs only: the README's install line now uses the default tag instead
of `@next`, so npm shows the current instructions.
8 changes: 6 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,11 @@ hooks belong in an owner-only file (`aer-hooks install <harness> --env-file

To check that an existing integration still works, run `npx
@adastracomputing/aer doctor`; it exits non-zero and names each problem, so
do not report an integration healthy without a zero exit.
do not report an integration healthy without a zero exit. A zero exit does
not mean every hook registration is current: a stale one (an end-of-session
entry with no time budget, a relative `CODEX_HOME`, an Antigravity
registration recording no tool calls, an outdated `aer-hooks` release) is a
warning, not a failing check, so read the warnings too.

## Choosing a package

Expand All @@ -117,7 +121,7 @@ do not report an integration healthy without a zero exit.
| Instrument a Node agent with no code changes | `aer-auto-node`, through `aer init` |
| Emit events from your own code | `aer-sdk-ts`, or `aer_sdk` for Python |
| Record MCP tool activity without touching the harness | `aer-mcp-recorder` |
| Record Claude Code, Codex CLI or opencode | `aer-hooks` |
| Record Claude Code, Codex CLI, opencode or Antigravity | `aer-hooks` |
| Let only attested agents reach your MCP server | `aer-mcp-guard` |
| Check attestation tokens at your own API | `aer-resource-node` |
| Verify a signed record | `aer-verify` |
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,9 @@ npx @adastracomputing/aer-hooks install claude-code
`codex` and `antigravity` work the same way; opencode loads an in-process
plugin instead (see the `aer-hooks` README). `status` shows what is wired, and
lists any registration that has fallen behind: missing the current hook
lifecycle, running an older `aer-hooks` release or shadowed by a nix-profile copy of
lifecycle, an end-of-session entry with no time budget, a relative
`CODEX_HOME`, an Antigravity registration that records no tool calls,
running an older `aer-hooks` release or shadowed by a nix-profile copy of
`aer-hook` that sits ahead of the project's own on `PATH`. `uninstall` removes AER's entries and only those.
Every config write keeps a backup, and running `install` again is safe: it
updates an existing registration in place instead of duplicating it, which is
Expand Down
97 changes: 91 additions & 6 deletions docs/upgrading.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ action. The ones that do are listed first in each section.

| Package | From | To |
| --- | --- | --- |
| [`@adastracomputing/aer`](#aer-cli) | 0.1.4 | 0.4.0 |
| [`@adastracomputing/aer-hooks`](#aer-hooks) | 0.1.3 | 0.5.0 |
| [`@adastracomputing/aer`](#aer-cli) | 0.1.4 | 0.4.2 |
| [`@adastracomputing/aer-hooks`](#aer-hooks) | 0.1.3 | 0.6.0 |
| [`@adastracomputing/aer-mcp-recorder`](#aer-mcp-recorder) | 0.1.2 | 0.3.2 |
| [`@adastracomputing/aer-auto-node`](#aer-auto-node) | 0.3.0 | 0.5.0 |
| [`@adastracomputing/aer-auto-node`](#aer-auto-node) | 0.3.0 | 0.6.0 |
| [`@adastracomputing/aer-emit`](#aer-emit) | 0.1.2 | 0.4.0 |
| [`@adastracomputing/aer-sdk-ts`](#aer-sdk-ts) | 0.1.2 | 0.2.1 |
| [`@adastracomputing/aer-resource-node`](#aer-resource-node) | 0.1.2 | 0.3.0 |
Expand All @@ -32,8 +32,15 @@ recommended.

## aer (CLI)

From 0.1.4 to 0.4.0.
From 0.1.4 to 0.4.2.

- `aer doctor` reports the aer-hooks 0.6.0 checks below: an end-of-session
entry with no time budget, a relative `CODEX_HOME` and an Antigravity
registration that records no tool calls. It also compares an installed
`aer-hook` against aer-hooks 0.6.0, so a 0.5.x hook is now flagged as
outdated. These are warnings, not failing checks: a zero exit from
`doctor` does not mean every hook registration is current, only that
nothing it treats as a hard failure was found.
- `aer smoke` now exits 1 when the API has no completed session for your
agent after the workload ran. It used to exit 0 whenever the workload
itself exited 0, so a CI step that passed on a silent collector now fails.
Expand Down Expand Up @@ -75,7 +82,56 @@ From 0.1.4 to 0.4.0.

## aer-hooks

From 0.1.3 to 0.5.0.
From 0.1.3 to 0.6.0.

New in 0.6.0, from 0.5.1:

- Re-run `aer-hooks install <harness>` for Claude Code, Codex and
Antigravity to add `--end-budget-ms` to the entry that ends a session, so
it can declare how long the harness actually allows it and hand the rest
to a short-lived background process that outlives the harness. Until you
re-run it, `aer-hooks status` and `aer doctor` report `no_end_budget`.
- Re-run `aer-hooks install antigravity`. A 0.5.x install wrote Antigravity's
tool events in a flat form Antigravity loads and never runs, so those
registrations record turns and no tool calls. `aer-hooks status` and
`aer doctor` report this as `antigravity_flat_tool_entry` and name the
command to fix it.
- Codex users must approve the hook again after either re-install, because
it changes the registered command and Codex records trust against the
exact command text. A hook Codex does not trust is skipped silently, with
no error on either side. Start Codex and use its startup trust review, or
run `/hooks` and approve the AER entry; for a non-interactive `codex exec`
that has already vetted the hook, `--dangerously-bypass-hook-trust` runs
it without a recorded approval.
- `aer-hooks install codex` now honours `CODEX_HOME`: `install`, `status`
and `uninstall` read and write `$CODEX_HOME/hooks.json` when it is an
absolute path, rather than always `~/.codex/hooks.json`. A relative
`CODEX_HOME` is refused by `install` and reported by `status` and
`aer doctor` as `relative_codex_home`. If AER entries were written to
`~/.codex/hooks.json` before `CODEX_HOME` was set, Codex no longer reads
them there, but they would fire again alongside the new registration if
`CODEX_HOME` were later unset; `install` and `status` warn about the
stray file and print the command that removes it,
`CODEX_HOME= aer-hooks uninstall codex`.
- Codex now records a `file.written` event for each file `apply_patch`
touches, read only from the patch's file headers, never its content. A
patch naming more than 16 files records the first 16 and puts the true
count on the call's `tool.started` event.
- The opencode plugin declares its session as the `aer-hooks` collector
recording a harness rather than as a wrapped process, and every event it
sends, including LLM usage, now carries `harness: opencode`. It also
reduces tool calls the way the shell hooks already do: `bash` to the
programs it ran and the hosts its network clients reached, `read`,
`write` and `edit` to the file path, `webfetch` to the target's host,
never the command line, file content or URL path. Anything reading
opencode records should expect the smaller shape. Plugin `dispose`, which
is how `opencode run` ends, now waits at most 3 seconds for the API.
- A tool name in the opencode plugin longer than 200 UTF-16 code units or
containing control characters is now recorded as `(unrecordable tool
name)` instead of being refused, and each such call adds a
`collector.report` marker with phase `tool_name_replaced`.

From earlier releases, 0.1.3 to 0.5.0:

- Re-run `aer-hooks install <harness>` after upgrading. The record now
closes on `SessionEnd` instead of on every `Stop`, and the hook is
Expand Down Expand Up @@ -152,7 +208,36 @@ From 0.1.2 to 0.3.2.

## aer-auto-node

From 0.3.0 to 0.5.0.
From 0.3.0 to 0.6.0.

New in 0.6.0, from 0.5.0:

- The usage policy is now fetched when the collector starts for its
configured agent, instead of by each session on its first call, kept
fresh for 5 minutes and, on a failed fetch, retried after 30 seconds. A
call made while that first fetch is still in flight waits for it, never
more than 3 seconds, and a policy in block mode now governs even the
process's first call on the OpenAI, Anthropic and Vercel AI SDK paths. A
block-mode call to a denied model on the Vercel path now throws
`AerPolicyError` before the request is sent, where 0.5.0 let it through
with no policy check.
- `fail_closed` now takes effect once the last fetched policy is more than 5
minutes old and the latest refresh has failed, refusing every LLM call in
new sessions with rule `policy_unavailable`, rather than only when no
policy was ever fetched.
- Every request the collector makes to the AER API is now abandoned after
10 seconds. A host process can be delayed exiting by up to about 30
seconds after its last event, since the closing report and completion are
still sent one after another.
- A batch of events the API did not accept adds its count to the closing
`collector.report`'s new `events_dropped_budget` field, so the record
says it is short instead of looking complete. A tool name longer than 200
UTF-16 code units or containing control characters is recorded as
`(unrecordable tool name)` and counted per provider in
`adapter_activity`'s new `tool_names_replaced`, rather than refusing the
whole `tool.selected` event.

From earlier releases, 0.3.0 to 0.5.0:

- The collector does not start inside a Claude Code tool shell
(`CLAUDECODE=1` or `CLAUDE_CODE_ENTRYPOINT` set). Claude Code exports its
Expand Down
13 changes: 10 additions & 3 deletions packages/aer-hooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ never the prompt or completion text. Every payload, whatever tool produced it, i
of keys AER ingest stores before it is sent, so a value the record could not
hold never reaches the wire either.

If you need evidence about the arguments themselves, use content commitments
(ADR-011): the record carries a one-way tag you can later open against your own
If you need evidence about the arguments themselves, use content commitments:
the record carries a one-way tag you can later open against your own
retained plaintext with a key that never leaves your machine.

## Fail-open, never blocking
Expand Down Expand Up @@ -239,6 +239,13 @@ rest of the harness's environment, and reads the credential file itself. It writ
nothing to the terminal: what it has to report goes to `drain.log` in the state
directory, which is kept under 64 KB and never holds a token or a credential.

An install from before 0.6.0 has no `--end-budget-ms` on the entry that ends
a session. Run `aer-hooks install <harness>` again to add it; `aer-hooks
status` and `aer doctor` report an entry without it as `no_end_budget` and
name the harness to reinstall. On Codex, re-running install changes the
registered command, so Codex skips the hook silently until you approve it
again (see [Codex will not run the hook until you trust it](#codex-will-not-run-the-hook-until-you-trust-it)).

Two limits. A container, CI step or sandbox that ends with the harness ends the
background process too, so there the record completes only if the declared budget
was enough. Codex's is the tightest: 2.5 seconds inside its 3-second cap, so against
Expand All @@ -250,7 +257,7 @@ the record.

### Antigravity records each turn

An `aer-hooks` release before this one registered Antigravity's tool events
An `aer-hooks` release before 0.6.0 registered Antigravity's tool events
in a form Antigravity loads and never runs, so those installs record turns
and no tool calls. `aer-hooks status` and `aer doctor` report such a
registration; `aer-hooks install antigravity` rewrites it.
Expand Down
2 changes: 1 addition & 1 deletion packages/aer-hooks/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@adastracomputing/aer-hooks",
"version": "0.5.1",
"description": "Fail-open, redaction-by-default hook adapters that record the tool events a coding harness fires (Claude Code, OpenAI Codex CLI) or its plugins load (opencode) into AER. Tool and argument-key names only by default, never values.",
"description": "Fail-open, redaction-by-default hook adapters that record the tool events a coding harness fires (Claude Code, OpenAI Codex CLI, Antigravity) or its plugins load (opencode) into AER. Tool and argument-key names only by default, never values.",
"type": "module",
"private": false,
"main": "./dist/index.js",
Expand Down
6 changes: 5 additions & 1 deletion packages/conformance/src/event-schema.conformance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ async function emitted(args: string[], payloads: { args?: string[]; payload: unk
AER_ENV_ID: '01950000-0000-7000-8000-0000000000ad',
XDG_CACHE_HOME: cache,
HOME: cache,
// A session end hands leftover delivery to a detached worker that
// writes into this cache. Keep its life short.
AER_HOOK_DRAIN_BUDGET_MS: '2000',
}),
});
child.stdin.end(JSON.stringify(step.payload));
Expand All @@ -61,7 +64,8 @@ async function emitted(args: string[], payloads: { args?: string[]; payload: unk
}
} finally {
await new Promise((r) => api.close(() => r(null)));
rmSync(cache, { recursive: true, force: true });
// The worker may still be writing its last state when the hook exits.
rmSync(cache, { recursive: true, force: true, maxRetries: 20, retryDelay: 150 });
}
return bodies.flatMap((b) => JSON.parse(b) as unknown[]);
}
Expand Down
Loading