Files
Bo 83e1dd0cb5 feat(gc): pre-seed Codex trust for materialized Gas City homes (#1031)
## Defect

Gas City materializes Codex session homes with project-local hooks. The
first
Codex process in an untrusted home can stop at the interactive
workspace/hook
trust dialog, leaving the agent pane alive but unable to take dispatched
work.

Codex persists two independent decisions in `$CODEX_HOME/config.toml`:

1. workspace trust under `[projects."<dir>"]`
2. one content hash per hook under `[hooks.state."<hook-key>"]`

Trusting a parent directory does not trust a session home, and the hook
digest
input is intentionally owned by Codex rather than reimplemented here.

## Change

`ao gc prepare` now discovers the Gas City directories that exist when
it runs
(city and rig roots, materialized agent homes, and materialized rig
worktrees)
and pre-seeds both trust layers for those exact targets.

- Hook identities and current hashes come from Codex's `hooks/list`
app-server
  method.
- Returned hooks are restricted to the discovered targets; user- or
  plugin-level hooks are never granted trust by this command.
- Explicit operator decisions are preserved. An untrusted workspace,
modified
hook, disabled hook, unusable hash, malformed response, or malformed
TOML
  fails loudly rather than being rewritten or accepted as complete.
- The merged TOML is validated in memory and installed with the CLI's
durable
  atomic writer while preserving existing permissions.
- `ao gc check` verifies the same values from local files only. It
starts no
  Codex subprocess and writes nothing.

## Deliberate boundary

Discovery is filesystem-based. A home Gas City creates *after* `prepare`
is not
pre-seeded by an earlier invocation. `prepare` compares configured agent
identities with materialized homes and warns about missing homes,
including the
real dotted-name shape (`gastown.mayor` → `.gc/agents/mayor`). The
operational
rule is documented explicitly:

```text
prepare → start the city → prepare again → dispatch
```

This PR does not claim that one pre-start invocation covers future homes
or
that every future pane can never encounter a prompt.

## Evidence

Automated tests cover:

- value-based workspace and hook trust, including `enabled = false`
- malformed/unexpected `hooks/list` responses
- regular local `hooks.json` files that derive zero hook identities
(`{}`,
`{"hooks":null}`, and `{"hooks":{}}`), keeping `prepare` and `check`
aligned
- real TOML spellings, invalid merges, idempotence, and mode
preservation
- target filtering and derived hook-key fidelity
- subprocess-free `check`
- missing-home identity reporting for nested and dotted qualified names
- operation with no Codex binary
- package-wide HOME isolation

An isolated real-Codex smoke on a disposable Gas City home established
the
behavioral differential: with the home's trust entries removed, Codex
rendered
the trust dialog; after seeding the same home, it reached the composer
without
the prompt. This proves the existing-home mechanism, not future-home
timing.

Final recovery checks on commit
`a4b52b2354b9f96e5e10e07b2916339c87190bfc`:

```text
go test -count=1 ./internal/gcmaintainer
ok github.com/boshu2/agentops/cli/internal/gcmaintainer 11.943s

go test -race -shuffle=on -count=2 ./internal/gcmaintainer
PASS

go test -count=1 ./internal/testsupport
PASS

go vet ./internal/gcmaintainer ./internal/testsupport
PASS

scripts/check-test-home-isolation.sh
PASS

scripts/check-test-isolation.sh
PASS (raw os.Setenv remains at the 10/10 baseline)

GOCACHE=/private/tmp/agentops-gocache \
GOLANGCI_LINT_CACHE=/private/tmp/agentops-golangci-cache \
WORKTREE_DISPOSITION_CI_SKIP=1 \
./bin/ao gate check --full --workflow-coverage --require-workflow-parity
PASS (68/68 full/head checks)

GOCACHE=/private/tmp/agentops-gocache bash scripts/regen-all.sh --check
PASS

git diff --check
PASS
```

Recovery fixed the prior CI findings with `storage.AtomicWriteFile`,
package-wide
HOME isolation, and `json.Encoder.Encode`. The first repaired CI replay
exposed
one further ratchet: raw `os.Setenv` calls in the new `_test.go`
TestMain raised
the repository baseline from 10 to 12. The final commit moves that
one-time
setup into the existing shared test-support boundary, keeps environment
changes
outside `m.Run`, and teaches the HOME-isolation gate only the exact safe
helper
shape. CI will rerun on the exact pushed commit.
2026-08-03 09:51:53 -04:00
..