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 viaPermissionRequest— 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'sAskUserQuestion, scrubbed for privacy before it ever touches disk. Off by default (signal: corrections-only); an absent or pre-existing flag with nosignal: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:evoStep 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:evoflow. 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:evoeach 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
(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
(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/*.shor*.mjsas 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 ownskills/evo-auto-enable/scripts/*andscripts/{evolution_candidates, scrub}.mjsat 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.shcarved out,evolution_candidates.mjs/scrub.mjsstill 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 baredir/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.gitignorefirst 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):
{
"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):
{
"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):
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:
{
"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) 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):
{
"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:
{
"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:
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)