# Evo Auto Enable

> Use this skill when the user wants to capture lessons automatically without invoking /ievo:evo explicitly — trigger words "turn on auto evolution", "auto-evolve", "capture lessons automatically", "evo auto on", "evolve without asking". Enables auto-evolution mode for this project — iEvo accumulates "corrections from the user" as evolution candidates during a session and surfaces them for review via /ievo:evo. Sets the project-local flag `.ievo/evo-auto.flag` and prepares the pending-candidate queue at `.ievo/evolution-candidates/`. Asks whether to also capture tool failures/denials (opt-in, scrubbed for privacy). Auto-mode writes ONLY unambiguous project-wide overlays; ambiguous or user-level matches are parked for manual review, never written silently.

- Skill: `ievo-ai/evo-auto-enable` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add ievo-ai/evo-auto-enable`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ievo-ai/evo-auto-enable/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: ievo-ai (https://skillmd.com/u/ievo-ai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ievo-ai/evo-auto-enable

---


# Evo Auto Enable — turn on auto-evolution mode

Switches on **auto-evolution mode** for this project: iEvo watches for
**corrections from the user** during a session and accumulates them as evolution
candidates, so lessons get captured without the user explicitly running
`/ievo:evo`. The mode is a project-local setting (lives in
`.ievo/evo-auto.flag`), so it survives sessions and — if committed — is shared
with teammates in the same repo, exactly like `/ievo:debug-on`'s flag.

Enabled here, disabled with `/ievo:evo-auto-disable`.

## Scope of this mode (read before enabling)

Auto-evolution is deliberately conservative — it never guesses at a silent write:

- **Signal:** always **corrections from the user** — semantic, agent-judged
  ("actually, do X not Y"; "no, we always Z here"). Optionally, ALSO **tool
  failures and permission denials** (`PostToolUseFailure` / `PermissionDenied`;
  on Codex, which has neither event, the closest true analog is approval
  **requests** via `PermissionRequest` — a narrower signal, disclosed as such
  in Step 3.6) — a purely mechanical signal captured verbatim with no agent
  judgment involved, opt-in via Step 2's `AskUserQuestion`, scrubbed for
  privacy before it ever touches disk. Off by default
  (`signal: corrections-only`); an absent or pre-existing flag with no
  `signal:` line behaves the same way.
- **Auto-write is project-wide only.** A candidate is written to the overlay
  automatically **only** when its scope is unambiguously **project-wide**
  (`.ievo/evolution/project.md` — see `/ievo:evo` Step 1).
- **Everything else is parked, never silently written.** When scope is ambiguous
  or the target matches a **user-level-only** agent/skill, the candidate is
  appended to the **pending queue** (`.ievo/evolution-candidates/pending.md`) for
  manual review through the normal `/ievo:evo` flow. Auto-mode never asks
  mid-session and never writes an agent/skill overlay silently.

## When to use

- User says "turn on auto evolution", "auto-evolve", "capture lessons automatically",
  "evolve without asking", "evo auto on"
- User wants corrections they make during a session to be remembered without
  stopping to run `/ievo:evo` each time
- A project where the same corrections keep recurring and should accumulate

## Steps

### 1. Verify `.ievo/` exists

If the `.ievo/` directory is absent → init hasn't been run in this project. Tell
the user:

```
iEvo not initialized in this project. Run /ievo:init first.
Auto-evolution builds on the same overlay model — nothing to evolve yet.
```

Exit.

### 2. Ask about failure/denial capture, then write the flag file

If `<project>/.ievo/evo-auto.flag` already exists, read its current `signal:`
value first (treat an absent line, or any value other than
`corrections+failures`, as `corrections-only`) and preselect the matching
option below — this re-run is a refresh, not a fresh opt-in choice.

Ask via `AskUserQuestion`:

```
Also capture tool failures and permission denials as evolution candidates?
- "corrections-only"     — capture only explicit user corrections (default)
- "corrections+failures" — also capture failed/denied tool calls
  (PostToolUseFailure + PermissionDenied), scrubbed for privacy, for later
  fixed-vs-noise review via /ievo:evo
```

Use the Write tool (NOT Bash) to create `<project>/.ievo/evo-auto.flag` with YAML
content (mirrors `.ievo/debug.flag`'s shape), `signal:` set to the answer above:

```
enabled: true
enabled_at: <ISO-8601 UTC timestamp>
enabled_by: <user identifier if known, else "user-invocation">
signal: <corrections-only | corrections+failures>
auto_write_scope: project-wide-only
```

The file format is YAML for easy human reading. Presence of the file = mode
enabled; the correction-capture hook and the periodic-analysis nudge read it to
decide whether to accumulate and surface candidates. The failure-capture hook
(Step 3.6) additionally gates on the `signal:` value — flipping it later (edit
the flag, or re-run this skill) takes effect on the next hook fire, no
re-install needed.

### 3. Prepare the pending-candidate queue

Ensure `<project>/.ievo/evolution-candidates/` exists. If
`<project>/.ievo/evolution-candidates/pending.md` is absent, use the Write tool to
create it with this scaffold (do NOT overwrite an existing queue — it may already
hold parked candidates):

**This directory is only where `pending.md` itself lives — not necessarily
where the raw per-session capture files land.** `pending.md` is always
project-root-relative, at exactly the path above. The raw `.jsonl` files the
hooks append to (via `evolution_candidates.mjs append`) are a separate
concern: since v0.78.8 (#564), when the project is inside a git working tree,
`append` writes new candidates under the repo's shared git-common-dir
(`<git-common-dir>/ievo/evolution-candidates/<session-id>.jsonl` — for a
plain checkout that's `.git/ievo/...`; for a linked worktree it resolves to
the *main* checkout's shared `.git`, so a session captured in a worktree
survives that worktree being removed) rather than under
`<project>/.ievo/evolution-candidates/`. Outside a git working tree, or for
data accumulated before #564, it falls back to the same directory as
`pending.md`. `count`/`list`/`prune` (and therefore the SessionStart nudge)
transparently merge both locations, so this split never breaks counting or
review — but do not assume `wc -l .ievo/evolution-candidates/*.jsonl` alone
reflects the true backlog on a git-repo project; check the git-common-dir
location too (`git rev-parse --git-common-dir` resolves it) before concluding
the queue is empty.

```
# Evolution candidates — pending review

Corrections captured while auto-evolution mode is ON, awaiting review via
`/ievo:evo`. Auto-mode writes unambiguous project-wide lessons to the
overlay directly; anything ambiguous or user-level-only is parked HERE instead of
being written silently. Review with `/ievo:evo`, then remove the entries
you have folded into an overlay.

Retention: candidates from the last 10 sessions are kept; older per-session
candidate files are cleaned up (suggest cleanup, never delete without asking).

Each parked candidate is appended below as one of two kinds:

Awaiting scope classification (from earlier auto-capture, reviewed via
`/ievo:evo`'s Step 0):

## <ISO-8601 UTC> — session <session-id>
- Scope: ambiguous | user-level-only
- Correction: <verbatim user correction / lesson text>

Already captured, only the commit failed (`evo/SKILL.md` Step 5.4's
headless-invocation fallback — the overlay entry was already written
successfully; this just needs a manual `git add` + `git commit --only`
on the noted file, never re-run through Step 0/1 classification — then
delete the entry from pending.md once you have committed the file
manually). Format: a `## <ISO-8601 UTC> — session <session-id>` heading
followed by four fields — `Scope: autocommit-failed`, `Overlay file:
<path>`, `Branch: <branch-name>`, `Reason: <failure reason>` — see
`evo/SKILL.md` Step 5.4 point 5 or `agents/evolution.md` Step 4.4 point 5
for the authoritative field list. Deliberately described in prose here,
not reproduced as a standalone block: a literal
`- Scope: autocommit-failed` line written into this very scaffold would
itself exact-match the nudge script's detector below (Step 3.5.3) on
every freshly created `pending.md` — faking a false-positive "commit
needed" nudge starting at session one, before any real entry ever
existed. If you add a worked example back here later, keep the `Scope`
value out of a standalone line (e.g. inline it in prose, as above).
```

### 3.5 Install the correction-capture + analysis + failure-capture hooks

This is what makes auto-evolution actually capture and surface corrections (and,
opt-in, tool failures/denials). Three hooks are wired into the **invoking
client's own hook config** — Claude Code: the project's `.claude/settings.json`;
Codex: the project's `.codex/hooks.json` (detect the client per `/ievo:init` Step
1.5's rule, **ordered**: `$CLAUDECODE` set with `$CODEX_CLI` unset → Claude Code, else `$CODEX_CLI` set → Codex, else a Codex Desktop signal (`CODEX_INTERNAL_ORIGINATOR_OVERRIDE=Codex Desktop`, or macOS `__CFBundleIdentifier=com.openai.codex`) → Codex, else Claude Code; never the bare `$CODEX_CLI` var in isolation, and never `command -v codex`). Writing
Claude Code hooks from a Codex session enables nothing: Codex never reads
`.claude/settings.json`, which left auto-mode claiming "ENABLED" with only a flag
and queue on disk (issue #432). All three hooks are **gated on
`.ievo/evo-auto.flag`** so they are no-ops the moment the mode is off (or
`/ievo:evo-auto-disable` removes the flag), and all **fail-silent and
non-blocking**. The installed scripts are identical on both platforms — only the
wiring differs (Step 3.5.4): Claude Code uses `/ievo:hooks-setup`-convention
exec-form `args: string[]`; Codex handlers take a single `command` string. The
correction-capture and analysis-nudge hooks emit `additionalContext` from the
hook command's stdout JSON, the failure-capture hook (Step 3.6) emits no stdout
at all (nothing for the agent to act on mid-failure). Verified against the
[Claude Code hooks reference](https://code.claude.com/docs/en/hooks)
(UserPromptSubmit + SessionStart both support
`hookSpecificOutput.additionalContext`; SessionStart is context-only and cannot
block startup; PostToolUseFailure's error payload field is `tool_error`, NOT a
top-level `error` — see Step 3.6's note on this) and against the
[Codex hooks reference](https://developers.openai.com/codex/hooks)
(same `hookSpecificOutput.additionalContext` support on UserPromptSubmit +
SessionStart; same `session_id`/`hook_event_name` stdin JSON; hooks fail open on
errors/timeouts).

The correction-capture and analysis-nudge hooks call the per-session accumulator
`plugins/ievo/scripts/evolution_candidates.mjs` (Node, stdlib-only) for
`append` / `count` / `prune`. It only ACCUMULATES — it never classifies scope or
writes overlays; analysis is deferred to the next session (Step 3.5.3 / the
contract below). The failure-capture hook (Step 3.6) also calls
`plugins/ievo/scripts/scrub.mjs` to redact the record before it ever reaches
disk.

#### 3.5.1 Resolve the plugin root and commit the hook scripts directly (closes #446, skills#552 follow-up)

A hook fired from the project's own config (`.claude/settings.json` on Claude
Code, `.codex/hooks.json` on Codex) does **not** get `CLAUDE_PLUGIN_ROOT` set
at fire time, so every script copied below prefers a live `CLAUDE_PLUGIN_ROOT`
when present and otherwise falls back to a **project-local committed copy** —
never a path baked from `CLAUDE_PLUGIN_ROOT` at setup time. That literal would
point into the versioned plugin cache (`~/.claude/plugins/cache/...`); it goes
stale on the very next plugin update (orphaned cache directories are purged
~14 days later) and the scripts' fail-silent contracts hide the resulting
silent death — a baked-path generator was found dead in the wild this way
(#422). This rule applies to every script this skill installs.

**The five files this step installs, and where they live in the plugin
(source of truth — never transcribe, always copy):**

| Installed as | Copied from |
|---|---|
| `.ievo/hooks/scripts/correction-capture.sh` | `${CLAUDE_PLUGIN_ROOT}/skills/evo-auto-enable/scripts/correction-capture.sh` |
| `.ievo/hooks/scripts/evo-analysis-nudge.sh` | `${CLAUDE_PLUGIN_ROOT}/skills/evo-auto-enable/scripts/evo-analysis-nudge.sh` |
| `.ievo/hooks/scripts/failure-capture.sh` | `${CLAUDE_PLUGIN_ROOT}/skills/evo-auto-enable/scripts/failure-capture.sh` |
| `.ievo/hooks/scripts/evolution_candidates.mjs` | `${CLAUDE_PLUGIN_ROOT}/scripts/evolution_candidates.mjs` (the shared accumulator — same file `/ievo:evo` Step 0 already reads) |
| `.ievo/hooks/scripts/scrub.mjs` | `${CLAUDE_PLUGIN_ROOT}/scripts/scrub.mjs` (the shared redaction engine) |

All five are **committed directly** — no gitignore, no per-clone regeneration
step, no dispatcher-shim-execs-companion split. A plain `git clone` of a
project that has already run this skill once gets working hooks immediately;
re-running `/ievo:evo-auto-enable` later simply re-copies all five from the
currently-installed plugin, overwriting whatever was there (idempotent, safe).

**Security note — this makes all five a review-gated exec path, deliberately
(skills#552 follow-up).** An earlier version of this skill kept the real hook
logic and the redaction engine gitignored specifically so a pull request to a
project using this plugin could never silently alter them — only a tiny,
static, four-line dispatcher was ever committed. This version accepts a
different, explicit tradeoff: trust ordinary code review over gitignore-
enforced immutability, in exchange for hooks that work the instant a project
is cloned, with no drift window where the flag claims "enabled" but nothing
is actually on disk yet (see the fresh-clone gap this closes, below). One
consequence to state to the user in Step 5, and to call out in any PR that
touches `.ievo/hooks/scripts/*`:

- **Review any diff to `.ievo/hooks/scripts/*.sh` or `*.mjs` as executable
  code, not as config.** These files run on every prompt, every tool failure,
  and every session start, for everyone who clones the project. A diff here
  is never routine churn — the only legitimate cause is a re-run of this
  skill against a newer plugin version (same content as the plugin's own
  `skills/evo-auto-enable/scripts/*` and `scripts/{evolution_candidates,
  scrub}.mjs` at that version). Anything that doesn't match the installed
  plugin's own copies is suspicious by default, especially from an untrusted
  contributor.

**Gitignore precondition — widen the negation before copying anything.**
`.ievo/hooks/tmp/` (the fixed-path scratch files Step 3.5.2/3.6 write to)
stays gitignored; everything else under `.ievo/hooks/scripts/` is now meant
to be tracked. A blanket `.ievo/hooks/` line ignores the whole directory as
one opaque unit, and git's own semantics make that form impossible to
selectively un-ignore later ("you cannot re-include a file if a parent
directory of that file is excluded"), so this skill needs a
**negation-capable** pattern instead: everything under `.ievo/hooks/` stays
ignored by default, with exactly these five filenames carved out. `/ievo:init`
Step 10 and `hooks-setup/SKILL.md` Step 8 write this same eight-line block
(kept byte-identical across all three, so whichever runs first converges the
project onto one `.gitignore` state, and a later run of either of the other
two can never re-ignore these five). Read the project's `.gitignore` (absent
= nothing to check, fall through to the append below):

- If it already contains the eight-line block below, nothing to do.
- If it contains the OLDER three-filename block (a pre-skills#552 install —
  only `correction-capture.sh`/`evo-analysis-nudge.sh`/`failure-capture.sh`
  carved out, `evolution_candidates.mjs`/`scrub.mjs` still implicitly
  ignored), REPLACE it with the eight-line block below via the Edit tool —
  the two new negation lines must be added, not left for a later run to
  discover, since Step 3.5.1 below is about to write files that would
  otherwise land gitignored.
- If it contains a blanket `.ievo/hooks/` line instead (a pre-#446 install,
  or a hand-written entry), REPLACE that one line with the block below —
  leave every other line untouched. A bare `dir/` entry still wins over
  later negations for paths inside it, so leaving both would silently keep
  everything ignored.
- If `.ievo/hooks/` is not mentioned at all, append the block (creating
  `.gitignore` first if the project lacks one).

```
.ievo/hooks/*
!.ievo/hooks/scripts/
.ievo/hooks/scripts/*
!.ievo/hooks/scripts/correction-capture.sh
!.ievo/hooks/scripts/evo-analysis-nudge.sh
!.ievo/hooks/scripts/failure-capture.sh
!.ievo/hooks/scripts/evolution_candidates.mjs
!.ievo/hooks/scripts/scrub.mjs
```

Verify with `git check-ignore -q .ievo/hooks/tmp` (exit 0 — still ignored) AND
all five negations before proceeding — checking only `correction-capture.sh`
is not sufficient: that filename was already carved out under the OLDER
three-filename block too, so a check limited to it would pass even if the
upgrade to the two NEW negations (`evolution_candidates.mjs`, `scrub.mjs`)
silently failed or was skipped, leaving those two files gitignored while the
three `.sh` files land committed:

```
for f in correction-capture.sh evo-analysis-nudge.sh failure-capture.sh evolution_candidates.mjs scrub.mjs; do
  git check-ignore -q ".ievo/hooks/scripts/$f" && echo "STILL IGNORED: $f"
done
```

Every line must print nothing (exit 1 = NOT ignored = trackable). If any
`STILL IGNORED:` line prints, the gitignore reconciliation above did not
fully apply — do NOT proceed to the copy step below; fix the `.gitignore`
first (re-check which of the three cases in the reconciliation logic above
should have matched, and apply it again) and re-verify before continuing.
Only skip this whole precondition when the project is not a git repo
(`git rev-parse --is-inside-work-tree` fails — nothing to track, so nothing
PR-tamperable).

**Copy the five files, then make the three `.sh` ones executable.** Run via
Bash, using the plugin root this skill itself is running from:

```
mkdir -p .ievo/hooks/scripts .ievo/hooks/tmp
cp "${CLAUDE_PLUGIN_ROOT}/skills/evo-auto-enable/scripts/correction-capture.sh" .ievo/hooks/scripts/correction-capture.sh 2>/dev/null && \
cp "${CLAUDE_PLUGIN_ROOT}/skills/evo-auto-enable/scripts/evo-analysis-nudge.sh" .ievo/hooks/scripts/evo-analysis-nudge.sh 2>/dev/null && \
cp "${CLAUDE_PLUGIN_ROOT}/skills/evo-auto-enable/scripts/failure-capture.sh" .ievo/hooks/scripts/failure-capture.sh 2>/dev/null && \
cp "${CLAUDE_PLUGIN_ROOT}/scripts/evolution_candidates.mjs" .ievo/hooks/scripts/evolution_candidates.mjs 2>/dev/null && \
cp "${CLAUDE_PLUGIN_ROOT}/scripts/scrub.mjs" .ievo/hooks/scripts/scrub.mjs 2>/dev/null && \
chmod +x .ievo/hooks/scripts/correction-capture.sh .ievo/hooks/scripts/evo-analysis-nudge.sh .ievo/hooks/scripts/failure-capture.sh && \
echo ok
```

If this does NOT print `ok` (empty/unset `CLAUDE_PLUGIN_ROOT`, or any source
script missing), the plugin root couldn't be resolved — tell the user
auto-mode's capture hooks can't be configured right now, and skip to Step 4
(the flag + queue from Steps 2–3 still stand; the user can re-run once
resolved).

**Clean up orphaned pre-#552 artifacts, if any.** A project that ran an
older version of this skill has a gitignored `.ievo/hooks/scripts/vendor/`
directory and up to three `.ievo/hooks/scripts/*.local.sh` companions from
the retired shim/companion design — safe to remove wholesale now that the
copy above replaced everything they provided, and leaving them would just
be orphaned clutter (they were never committed, so this has no git-history
effect):

```
rm -rf .ievo/hooks/scripts/vendor
rm -f .ievo/hooks/scripts/correction-capture.local.sh .ievo/hooks/scripts/evo-analysis-nudge.local.sh .ievo/hooks/scripts/failure-capture.local.sh
```

Safe to run unconditionally — both are no-ops on a project that never had
the old design (nothing to remove) and on a re-run of this skill itself
(idempotent).

The installed copies live at the **fixed, non-versioned, relative** paths
listed in the table above — every hook script below references its sibling
`evolution_candidates.mjs`/`scrub.mjs` by that literal relative path as its
fallback, never a `CLAUDE_PLUGIN_ROOT`-derived absolute one, so no
per-project substitution is needed. Hook scripts always run with `cwd` = the
project root (the existing `.ievo/evo-auto.flag` relative-path check in Step
3.5.2 already relies on this). Re-running `/ievo:evo-auto-enable` refreshes
all five to the currently-installed plugin version; between a plugin update
and the next re-run the installed copies can lag the live version by one
release — visible as a normal, reviewable diff the next time someone re-runs
this skill and commits the result, unlike the silent staleness a gitignored
copy would carry.

#### 3.5.2 The correction-capture hook (UserPromptSubmit)

Already copied and made executable by Step 3.5.1 above
(`.ievo/hooks/scripts/correction-capture.sh`, source
`plugins/ievo/skills/evo-auto-enable/scripts/correction-capture.sh`) — this
step exists to document its contract, not to write it again.

Fires on each user prompt WHEN auto-evolution mode is ON (`.ievo/evo-auto.flag`
present). Injects a conservative self-assessment nudge as `additionalContext`
so the agent can decide whether the user's message is a correction and, if
so, record it VERBATIM via a Write-tool temp file + the accumulator's
`--text-file` flag — NEVER by embedding the raw correction text inside a
Bash argument (a prior version did that with naive single-quoting, which an
apostrophe or shell metacharacter in the correction could break out of —
CWE-78, closed in #373). It does NOT classify scope or write overlays —
analysis is deferred to the next SessionStart.

**CONTRACT:** fail-silent (mode off / any error ⇒ emit nothing, exit 0),
non-blocking, ASCII-only `additionalContext` (no double quotes). NO `set -e`.

The temp file lives under `.ievo/hooks/tmp/` (gitignored — Step 3.5.1's
pattern keeps this one path ignored even though its sibling `scripts/`
files are now tracked) at a **fixed** path —
`.ievo/hooks/tmp/correction-pending.txt` — never a path built from the
correction text or any other untrusted value, so the Write tool call itself
can't be steered by a crafted correction either. Each capture overwrites the
same file; only the latest pending write matters until the agent appends it.

#### 3.5.3 The SessionStart analysis nudge

Already copied and made executable by Step 3.5.1 above
(`.ievo/hooks/scripts/evo-analysis-nudge.sh`, source
`plugins/ievo/skills/evo-auto-enable/scripts/evo-analysis-nudge.sh`) — this
step exists to document its contract, not to write it again.

On a NEW session, when auto-evolution is ON, prunes to the last 10 sessions
of candidates and, if any are pending, nudges the agent to review them via
`/ievo:evo`. No LLM work happens here — this only counts + surfaces.

It also asserts the **wiring** is genuinely installed (closes #551): a
hand-written `.ievo/evo-auto.flag`, or a project that never wired these hooks
into `.claude/settings.json`/`.codex/hooks.json` at all, can leave the flag
claiming ENABLED with the hook entries actually missing — checked every
SessionStart, not just at enable time, since a manual `settings.json` edit
can drop an entry later too. Two checks make up the assertion: **(a)** all
five files Step 3.5.1 installs are actually present under
`.ievo/hooks/scripts/` — committing them (skills#552 follow-up) makes this
true on a *correctly-set-up* clone, but Step 3.5.1's gitignore reconciliation
is an LLM-interpreted prose step, not compiled code, so a stale or
partially-applied `.gitignore` can still leave `evolution_candidates.mjs`/
`scrub.mjs` gitignored while the `.sh` files land committed — capture then
silently dies for anyone who clones after that point, exactly the class of
bug #551 exists to catch. This check is what catches it. **(b)** whether the
hook **entries** are actually wired into `.claude/settings.json`/
`.codex/hooks.json`.

Separately checks `.ievo/evolution-candidates/pending.md` for
`Scope: autocommit-failed` entries — these are already-classified overlay
writes whose commit failed (`evo/SKILL.md` Step 5.4's headless-invocation
fallback), not candidates awaiting scope classification, so the accumulator's
own pending-count never sees them; surfaced as a separate note appended
after the main message, distinguishing missing-wiring / pending-count-only /
both-present phrasing so the message never overclaims.

**CONTRACT:** fail-silent, context-only (`SessionStart` cannot block
startup), ASCII-only `additionalContext`. NO `set -e`.

**Why a nudge, not a blocking error:** `SessionStart` cannot block startup on
either platform, and this script's own contract (like every other hook this
skill installs) is fail-silent — so a drift finding is surfaced as
`additionalContext` for the agent to relay, exactly like the existing
pending-candidate count, never a hard failure. This mirrors ask #2 from
issue #551 ("a way to verify auto-mode is genuinely wired end-to-end") while
ask #1 (self-healing re-run) was already satisfied by this skill's existing
idempotency — re-running `/ievo:evo-auto-enable` after this nudge fires
repairs exactly the drift it names.

#### 3.5.4 Wire the correction-capture + analysis hooks into the client's hook config

**On Claude Code** (`/ievo:init` Step 1.5: no Codex signal) — read the project's
`.claude/settings.json` first (treat absent as `{}`); if it
exists but is **not valid JSON**, halt without writing (do not clobber manual
edits) and tell the user to fix it. Merge with the **Read + Edit** tools (not
shell JSON edits — preserves comments and key order), appending these two entries
(a third, for failure-capture, is Step 3.6) and deduping by the inner `command` +
`args` pair (skip if an identical entry already exists), using the same Read +
Edit merge mechanics `/ievo:hooks-setup`
Step 6 uses (that skill's own hook entries still lack `command` as of this
writing — see the `hooks-setup/SKILL.md` scope note in CHANGELOG.md — so the
dedup *key* differs; only the merge mechanics are shared). Claude Code's hook
schema requires `command` even in exec form — it holds the executable;
`args` holds only the argument vector, never the executable itself (a prior
version of this step omitted `command`, which Claude Code's settings validator
rejects at write time with `hooks.UserPromptSubmit.0.hooks.0.command: Expected
string, but received undefined` — closed in #384):

Under `hooks.UserPromptSubmit[]` (no `matcher` — fires on every prompt; the
script itself gates on the flag):

```json
{
  "hooks": [
    {
      "type": "command",
      "command": "sh",
      "args": [".ievo/hooks/scripts/correction-capture.sh"]
    }
  ]
}
```

Under `hooks.SessionStart[]` with `matcher: "startup"` (new sessions only, so a
mid-work resume/compact never re-injects the nudge):

```json
{
  "matcher": "startup",
  "hooks": [
    {
      "type": "command",
      "command": "sh",
      "args": [".ievo/hooks/scripts/evo-analysis-nudge.sh"]
    }
  ]
}
```

**On Codex** (`/ievo:init` Step 1.5: `$CODEX_CLI` set, or a Codex Desktop signal) — wire the SAME two scripts into the project's
`.codex/hooks.json` instead. Codex's native hook system supports both events
with the same semantics ([Codex hooks reference](https://developers.openai.com/codex/hooks)):
`UserPromptSubmit` and `SessionStart` are first-class Codex events, both accept
`hookSpecificOutput.additionalContext`, hooks receive the same
`session_id`-bearing JSON on stdin, and the `SessionStart` matcher filters by
source with the same `startup` value (possible values: `startup`, `resume`,
`clear`, `compact`). Differences from the Claude Code entries: a Codex handler
takes a single `command` **string** (no exec-form `args` array), and the
top-level key layout is `{"hooks": {<EventName>: [...]}}`.

Read `.codex/hooks.json` first (treat absent as `{"hooks": {}}`); if it exists
but is not valid JSON, halt without writing and tell the user to fix it — same
no-clobber rule as above. Merge with Read + Edit, deduping by the handler's
`command` string:

```json
{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "sh .ievo/hooks/scripts/correction-capture.sh"
          }
        ]
      }
    ],
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "sh .ievo/hooks/scripts/evo-analysis-nudge.sh"
          }
        ]
      }
    ]
  }
}
```

Two Codex-specific caveats — state both to the user rather than claiming
unconditional success (the "claims enabled while nothing captures" failure is
this skill's issue #432 bug class):

- **Trust gate:** Codex loads project-local `.codex/` hooks only when that
  config layer is trusted. If the user hasn't trusted this project's `.codex/`
  layer, the hooks sit inert until they do.
- **Relative paths:** the entries use project-root-relative script paths — the
  same pattern as the worked Codex example in
  `hooks-setup/references/codex-hooks.md`. Codex hooks fail open (a failing
  hook never blocks the session), so a session started outside the project
  root degrades to no capture, not an error.

**Functional check (both platforms), before claiming success — (1) here,
(2) at the end of Step 3.6:** after writing the config, (1) re-read it
and parse it as JSON (`node -e
'JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"))' <file>` —
malformed config is a silent kill on a fail-open platform). Run this one now,
against the write you just made.

**(2)** dry-run each wired command and confirm exit 0 — this one runs at the
**end of Step 3.6**, not here: Step 3.6 still has to wire its own
failure-capture hook entry into the config file, so at this point in a linear
enable run that entry doesn't exist yet (the *file*
`.ievo/hooks/scripts/failure-capture.sh` is already on disk — Step 3.5.1
copied all five files up front — only its **hook-config entry** is still
missing). Run here, `evo-analysis-nudge.sh`'s own wiring-integrity check
(Step 3.5.3) would reach its hook-entries-wired check and print a spurious
`... the hook wiring is missing or incomplete (drift detected) -- failure-
capture hook entry in <config file> ...` — naming an entry this run is about
to write, against a step whose own rule is "do NOT claim success". Do not
claim success, and do not report drift to the user, until Step 3.6's check
(2) has run and passed there. (The old check (3) — "assert every `.local.sh`
companion is on disk" — no longer applies: skills#552's follow-up made all
five files committed, so their presence is guaranteed by git the moment
`.claude/settings.json`/`.codex/hooks.json` itself is, well before this step
even runs.)

Only hooks Codex/Claude Code fire on a real session boundary can prove
end-to-end delivery — say so in Step 5's confirmation instead of implying the
capture loop was already observed working.

The wired paths (`.ievo/hooks/scripts/correction-capture.sh`,
`evo-analysis-nudge.sh`, `failure-capture.sh`) hold their **full capture
logic directly** — committed by Step 3.5.1, present on every clone, no
per-clone regeneration step. See Step 3.5.1's security note for the
tradeoff this accepts (trust ordinary code review over gitignore-enforced
immutability) and why it replaces the earlier tracked-shim /
gitignored-companion split.

**A note on `security-check`:** a `UserPromptSubmit` hook is one of the patterns
`/ievo:security-check` flags when auditing *third-party* plugins (it can prompt-
inject). This is iEvo's own first-party, flag-gated hook that only injects a
self-assessment nudge and writes solely under `.ievo/` — a known, purpose-built
exception, documented in `security-check/SKILL.md` so iEvo's own tooling does not
self-flag it. The separate question of these now-committed scripts being a
repo-resident exec path — and how to review a diff to one — is covered in
Step 3.5.1's security note.

### 3.6 Write + wire the failure-capture hook (opt-in, `PostToolUseFailure` + `PermissionDenied`; on Codex: `PermissionRequest`)

Unlike the two hooks above, this one needs no agent judgment at all — a tool
call either failed/was denied or it didn't, so the hook script does the whole
capture itself (extract → build a compact record → scrub → append) and never
emits `additionalContext`. It always installs (so flipping `signal:` in the flag
takes effect immediately, no re-run needed) but is a no-op unless
`signal: corrections+failures` is set — mirroring how every other hook here
self-gates on the flag rather than being conditionally wired.

**Platform semantics differ here — disclose, don't paper over.** Claude Code
fires `PostToolUseFailure` (a tool call failed) and `PermissionDenied` (a call
was denied). Codex has **neither** event — its verified catalog
([Codex hooks reference](https://developers.openai.com/codex/hooks)) offers
`PermissionRequest` as the closest true analog, and it fires when a tool call
*needs approval* — BEFORE the allow/deny decision, whose outcome the hook never
sees. So on Codex this signal records "an approval was requested"
(`outcome: requested`), not "a call failed/was denied". That is a real,
narrower signal (approval friction points), captured under the same
fixed-vs-noise review contract — never describe it to the user as
failure/denial capture. The script emits no stdout, so it can never influence
the permission decision itself (Codex only reads a decision from an explicit
`hookSpecificOutput.decision` output, which this script never produces).

Already copied and made executable by Step 3.5.1 above
(`.ievo/hooks/scripts/failure-capture.sh`, source
`plugins/ievo/skills/evo-auto-enable/scripts/failure-capture.sh`) — this
step exists to document its contract, not to write it again.

**CONTRACT:** fail-silent (mode off / signal not opted in / any error ⇒ exit
0, no output), non-blocking, fail-CLOSED for content — a scrub failure or a
missing `scrub.mjs` drops the record; a raw/unscrubbed record must NEVER
reach disk, even transiently. NO `set -e`.

The record is built and scrubbed entirely inside this script, then handed to
the accumulator via `--text-file` at the **fixed** path
`.ievo/hooks/tmp/failure-pending.txt` — never `--text` with the record
interpolated into a Bash argument, so nothing a failing tool printed can break out
of shell quoting (the same CWE-78 class closed in #373 for corrections).

**On Claude Code**, wire it into `.claude/settings.json` with the same Read +
Edit merge mechanics as
Step 3.5.4, under BOTH `hooks.PostToolUseFailure[]` and `hooks.PermissionDenied[]`
(no `matcher` — fires on every tool; the script itself gates on flag + signal):

```json
{
  "hooks": [
    {
      "type": "command",
      "command": "sh",
      "args": [".ievo/hooks/scripts/failure-capture.sh"]
    }
  ]
}
```

**On Codex** (`/ievo:init` Step 1.5's detection rule), wire it into `.codex/hooks.json` with Step
3.5.4's Codex merge mechanics, under `hooks.PermissionRequest[]` (no `matcher`;
same flag + signal self-gating). The script's `PermissionRequest` case records
`outcome: requested` — see the platform-semantics disclosure at the top of this
step:

```json
{
  "hooks": [
    {
      "type": "command",
      "command": "sh .ievo/hooks/scripts/failure-capture.sh"
    }
  ]
}
```

Re-parse the file as JSON after this merge, the same way Step 3.5.4's check (1)
does — this step's edit is the last write either config file receives, and the
dry-run below invokes what it wires.

**Check (2) — run here, now the install is complete.** It is the remaining
part of Step 3.5.4's functional check, deferred to this step because *this*
step wires the last hook entry (`failure-capture`'s). Run from the project
root:

```sh
for f in correction-capture evo-analysis-nudge failure-capture; do
  sh ".ievo/hooks/scripts/$f.sh" < /dev/null
  echo "$f.sh exit=$?"
done
```

Confirm all three print `exit=0`. Run back in 3.5.4 instead, the
`evo-analysis-nudge.sh` line would reach that script's own wiring-integrity
check (Step 3.5.3) and report the failure-capture hook entry as drift — an
entry this step hadn't wired yet. Run *here*, after a complete enable, that
dry-run should be either silent or a plain pending-candidate count: any
`drift detected` line — including a file-presence one, if Step 3.5.1's own
gitignore verification somehow let a stale negation through — is now a real
finding, and enable must not claim success while one prints.

### 4. Offer to gitignore the candidate queue

Captured candidates can contain verbatim conversation snippets. On first enable in
a project, ask via `AskUserQuestion` whether to append `.ievo/evolution-candidates/`
to `.gitignore` (default: yes — keep pre-review candidates local). The flag itself
(`.ievo/evo-auto.flag`, intent only) is fine to commit so teammates share the
setting; reviewed lessons land in the committed `.ievo/evolution/` overlays after
`/ievo:evo`.

### 5. Confirm to user

The hooks block is platform-conditional — never print the other client's file
or events (claiming `.claude/settings.json` hooks from a Codex session is the
exact "says ENABLED, captures nothing" bug this skill shipped — issue #432).

**On Claude Code**, print:

```
🧬 iEvo auto-evolution mode ENABLED

Flag: .ievo/evo-auto.flag (commit to share the setting with teammates)
Signal: <corrections-only | corrections+failures, from Step 2's answer>
Pending queue: .ievo/evolution-candidates/pending.md
Hooks, wired in .claude/settings.json (commit this + the five files below
under .ievo/hooks/scripts/ so a fresh clone never hits "command not found" —
skills#446, and works immediately with no per-clone setup step — skills#552):
  UserPromptSubmit               → .ievo/hooks/scripts/correction-capture.sh (capture corrections)
  SessionStart                    → .ievo/hooks/scripts/evo-analysis-nudge.sh (surface backlog + prune; also verifies wiring is genuinely installed, warning if it drifts — #551)
  PostToolUseFailure/PermissionDenied → .ievo/hooks/scripts/failure-capture.sh
    (installed either way; active only when Signal is corrections+failures)
  Plus their two shared dependencies, also committed alongside them:
  .ievo/hooks/scripts/evolution_candidates.mjs and .../scrub.mjs.
  All five hold their full, real logic directly — committed, identical to the
  plugin's own copies at this version. Because they are committed, they also
  run on every teammate's machine the moment they clone: review any future
  diff to them as executable code, the same way you would review a diff to
  application code that runs unattended.

From now on, corrections you make during a session are captured as evolution
candidates. At the next session start you'll be nudged to review them: unambiguous
project-wide lessons are written to the overlay automatically; ambiguous or
user-level ones are parked in the pending queue for review via /ievo:evo —
never written silently.
```

**On Codex** (`/ievo:init` Step 1.5's detection rule), print instead:

```
🧬 iEvo auto-evolution mode ENABLED (Codex)

Flag: .ievo/evo-auto.flag (commit to share the setting with teammates)
Signal: <corrections-only | corrections+failures, from Step 2's answer>
Pending queue: .ievo/evolution-candidates/pending.md
Hooks, wired in .codex/hooks.json (loads once this project's .codex/ layer is
trusted in Codex — commit this file + the five files below under
.ievo/hooks/scripts/ so a fresh clone never hits "command not found":
skills#446, and works immediately with no per-clone setup step — skills#552):
  UserPromptSubmit  → .ievo/hooks/scripts/correction-capture.sh (capture corrections)
  SessionStart      → .ievo/hooks/scripts/evo-analysis-nudge.sh (surface backlog + prune; also verifies wiring is genuinely installed, warning if it drifts — #551)
  PermissionRequest → .ievo/hooks/scripts/failure-capture.sh
    (installed either way; active only when Signal is corrections+failures.
    Codex has no failed-tool/denied event — this records approval REQUESTS,
    a narrower signal than Claude Code's failure/denial capture)
  Plus their two shared dependencies, also committed alongside them:
  .ievo/hooks/scripts/evolution_candidates.mjs and .../scrub.mjs.
  All five hold their full, real logic directly — committed, identical to the
  plugin's own copies at this version. Because they are committed, they also
  run on every teammate's machine the moment they clone: review any future
  diff to them as executable code, the same way you would review a diff to
  application code tha

…(truncated)
