diff --git a/hooks/hooks-codex.json.example b/hooks/hooks-codex.json.example
new file mode 100644
index 00000000..f621cdb8
--- /dev/null
+++ b/hooks/hooks-codex.json.example
@@ -0,0 +1,15 @@
+{
+ "hooks": {
+ "SessionStart": [
+ {
+ "hooks": [
+ {
+ "type": "command",
+ "command": "bash \"/ABSOLUTE/PATH/TO/superpowers/hooks/session-start-codex\"",
+ "timeout": 30
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/hooks/session-start-codex b/hooks/session-start-codex
new file mode 100755
index 00000000..c9e5a6ce
--- /dev/null
+++ b/hooks/session-start-codex
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+# Codex SessionStart hook for the superpowers plugin.
+#
+# Codex re-fires SessionStart with source:"compact" after every context
+# compaction (verified on codex-cli 0.145.0). Compaction replaces the live
+# context with a summary, which sheds the using-superpowers bootstrap and any
+# active skill's instructions — the measured cause of mid-session dispatch
+# drift in long multi-agent runs. This hook re-injects the bootstrap at
+# exactly that moment, restoring the same re-injection Claude Code performs
+# via its "startup|clear|compact" SessionStart matcher.
+#
+# On source:"startup" it emits nothing: the native Codex plugin path owns
+# session-start injection, and duplicating it here would recreate the
+# redundancy that led to the original session-start-codex hook's removal.
+#
+# Codex injects raw hook stdout into the model's context (verified with
+# sentinel probes), so output is plain text — not the JSON envelopes other
+# harnesses require of hooks/session-start.
+#
+# A hook failure must never break a session: every path fails open to empty
+# output and exit 0.
+
+set -u
+
+payload="$(cat 2>/dev/null || true)"
+
+# Act only on post-compaction re-fires. Tolerate arbitrary whitespace around
+# the JSON colon; anything unparseable falls through to a silent no-op.
+if ! printf '%s' "$payload" | grep -qE '"source"[[:space:]]*:[[:space:]]*"compact"'; then
+ exit 0
+fi
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
+
+using_superpowers_content="$(cat "${PLUGIN_ROOT}/skills/using-superpowers/SKILL.md" 2>/dev/null)" || using_superpowers_content=""
+if [ -z "$using_superpowers_content" ]; then
+ exit 0
+fi
+
+# printf instead of heredocs throughout: heredocs hang on bash 5.3+.
+# See: https://github.com/obra/superpowers/issues/571
+printf '%s\n' ""
+printf '%s\n\n' "You have superpowers."
+printf '%s\n\n' "**Below is the full content of your 'superpowers:using-superpowers' skill - your introduction to using skills. For all other skills, use the 'Skill' tool:**"
+printf '%s\n' "$using_superpowers_content"
+printf '%s\n\n' ""
+printf '%s\n' ""
+printf '%s\n' "Your context was just summarized (compacted). The summary preserves your progress but not your working instructions — the files are authoritative."
+printf '%s\n' ""
+printf '%s\n' "Before your next tool call:"
+printf '%s\n' "- Re-read the SKILL.md of any skill you are mid-way through executing. If you are executing subagent-driven-development, re-read skills/subagent-driven-development/SKILL.md."
+printf '%s\n' "- On Codex, also re-read skills/using-superpowers/references/codex-tools.md and follow its dispatch rules on every spawn_agent call."
+printf '%s\n' ""
+
+exit 0
diff --git a/tests/hooks/test-session-start-codex.sh b/tests/hooks/test-session-start-codex.sh
new file mode 100755
index 00000000..002d1d75
--- /dev/null
+++ b/tests/hooks/test-session-start-codex.sh
@@ -0,0 +1,110 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
+HOOK_UNDER_TEST="$REPO_ROOT/hooks/session-start-codex"
+EXAMPLE_UNDER_TEST="$REPO_ROOT/hooks/hooks-codex.json.example"
+
+FAILURES=0
+
+pass() {
+ echo " [PASS] $1"
+}
+
+fail() {
+ echo " [FAIL] $1"
+ FAILURES=$((FAILURES + 1))
+}
+
+# run_hook — echoes hook stdout; fails the calling test on
+# non-zero exit. env -i mirrors the codex hook executor's clean environment.
+run_hook() {
+ printf '%s' "$1" | env -i PATH="${PATH:-}" bash "$HOOK_UNDER_TEST"
+}
+
+echo "Codex SessionStart hook tests"
+
+startup_payload='{"session_id":"s","hook_event_name":"SessionStart","model":"gpt-5.6-terra","source":"startup"}'
+if output="$(run_hook "$startup_payload")" && [ -z "$output" ]; then
+ pass "source=startup emits nothing and exits 0"
+else
+ fail "source=startup emits nothing and exits 0"
+ printf '%s\n' "$output" | head -3 | sed 's/^/ /'
+fi
+
+compact_payload='{"session_id":"s","hook_event_name":"SessionStart","model":"gpt-5.6-terra","source":"compact"}'
+if output="$(run_hook "$compact_payload")"; then
+ ok=1
+ for needle in \
+ "" \
+ "You have superpowers." \
+ "name: using-superpowers" \
+ "" \
+ "subagent-driven-development/SKILL.md" \
+ "references/codex-tools.md"; do
+ if [[ "$output" != *"$needle"* ]]; then
+ ok=0
+ echo " missing: $needle"
+ fi
+ done
+ if [ "$ok" -eq 1 ]; then
+ pass "source=compact emits bootstrap plus re-read addendum"
+ else
+ fail "source=compact emits bootstrap plus re-read addendum"
+ fi
+else
+ fail "source=compact emits bootstrap plus re-read addendum (hook exited non-zero)"
+fi
+
+# Whitespace-tolerant source matching (serializers vary).
+spaced_payload='{"hook_event_name":"SessionStart", "source" : "compact"}'
+if output="$(run_hook "$spaced_payload")" && [[ "$output" == *""* ]]; then
+ pass "whitespace around the source key still triggers injection"
+else
+ fail "whitespace around the source key still triggers injection"
+fi
+
+if output="$(printf '' | env -i PATH="${PATH:-}" bash "$HOOK_UNDER_TEST")" && [ -z "$output" ]; then
+ pass "empty stdin fails open to no output, exit 0"
+else
+ fail "empty stdin fails open to no output, exit 0"
+fi
+
+if output="$(run_hook 'not json at all {{{')" && [ -z "$output" ]; then
+ pass "garbage stdin fails open to no output, exit 0"
+else
+ fail "garbage stdin fails open to no output, exit 0"
+fi
+
+# A compact mention inside some other field must not trigger injection.
+decoy_payload='{"hook_event_name":"SessionStart","source":"startup","cwd":"/tmp/compact"}'
+if output="$(run_hook "$decoy_payload")" && [ -z "$output" ]; then
+ pass "compact appearing outside the source field does not trigger"
+else
+ fail "compact appearing outside the source field does not trigger"
+fi
+
+if node -e '
+const example = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
+const entry = example.hooks.SessionStart[0].hooks[0];
+if (entry.type !== "command") {
+ console.error(`example hook type is ${JSON.stringify(entry.type)}, expected "command"`);
+ process.exit(1);
+}
+if (!/session-start-codex"$/.test(entry.command)) {
+ console.error(`unexpected example command shape: ${entry.command}`);
+ process.exit(1);
+}
+' "$EXAMPLE_UNDER_TEST"; then
+ pass "hooks-codex.json.example parses and invokes session-start-codex"
+else
+ fail "hooks-codex.json.example parses and invokes session-start-codex"
+fi
+
+if [[ "$FAILURES" -gt 0 ]]; then
+ echo "STATUS: FAILED ($FAILURES failure(s))"
+ exit 1
+fi
+
+echo "STATUS: PASSED"