ワンクリックで
sdk
Load this skill immediately after a user mentions "@goodfoot/codex-hooks" or Codex hooks.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Load this skill immediately after a user mentions "@goodfoot/codex-hooks" or Codex hooks.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
You must load this skill if you are creating or updating standalone documentation.
This skill should be used when the user asks to "skillify this", "turn this into a skill", "make a skill out of what we just did", "generalize this task into a reusable skill", or otherwise wants to capture a just-completed session task as a new skill or fold it into an existing one.
Browser automation using the puppeteer NPM package. Use when performing tasks on websites as a user would, taking screenshots, filling forms, or navigating web applications.
Load this skill immediately after a user mentions "@goodfoot/claude-code-hooks" or Claude Code hooks.
Load this skill immediately after a user mentions "@goodfoot/codex-hooks" or Codex hooks.
Identify and remove git worktrees (and their branch refs) whose work has already landed in the main branch, including squash- and rebase-merged ones that naive ancestry checks miss. Use when the user says "prune merged worktrees", "clean up worktrees", "remove stale worktrees", "worktree cleanup", or asks which worktrees have no unique commits.
| name | sdk |
| description | Load this skill immediately after a user mentions "@goodfoot/codex-hooks" or Codex hooks. |
Review the authoritative documentation at https://developers.openai.com/codex/hooks.md and the wire schemas under third_party/reference/codex/codex-rs/hooks/schema/generated/ before using @goodfoot/codex-hooks. When the doc and the schemas disagree, the schemas win.
Hooks are compiled executables, not scripts. You must build them before Codex can see them.
The Build Command:
npx -y @goodfoot/codex-hooks -i "src/**/*.ts" -o ".codex/hooks.json"
Parameters Explained:
-i "src/**/*.ts": Input Glob. Tells the compiler where your TypeScript source files are.
"...") to prevent your shell from expanding it before the CLI sees it.-o ".codex/hooks.json": Output Manifest. This is the file Codex loads at startup.
bin/ folder next to this file containing the compiled .mjs executables.--executable <path> (Optional): Executable Prefix. Sets the executable used in generated commands (default: node).--loader .ext=type (Optional, repeatable): Explicit Asset Loader. Registers esbuild loaders for non-code imports. .md=text is enabled by default; opt in for other extensions, e.g. --loader .txt=text.Configuring the log file: Logging is configured at runtime, not at build time. Set the CODEX_HOOKS_LOG_FILE environment variable to write JSON-line logs to a file, or configure programmatically with new Logger({ logFilePath }) / new Logger({ logEnvVar }). See Logging & Debugging.
Loader guidance:
SessionStart and SubagentStart preambles:
import preamble from './prompts/session-start.md';
import { sessionStartHook, sessionStartOutput } from '@goodfoot/codex-hooks';
export default sessionStartHook({}, () => {
return sessionStartOutput({
additionalContext: preamble
});
});
codex-hooks build passes.Here is a complete, working example of a PreToolUse hook. It uses the Factory Pattern (preToolUseHook) and the Output Builder (preToolUseOutput).
Goal: Prevent accidental deletion of the root directory.
// src/block-dangerous.ts
import { preToolUseHook, preToolUseOutput } from '@goodfoot/codex-hooks';
// 1. Export Default is MANDATORY.
// 2. Factory handles input typing and error wrapping.
// 3. tool_name is matched as a string (Codex does not narrow per-tool).
export default preToolUseHook({ matcher: 'shell' }, (input, { logger }) => {
// 4. Input uses wire format (snake_case: tool_input, tool_name).
// 5. tool_input is `unknown` — narrow it with a user-defined type guard.
const command = isShellInput(input.tool_input) ? input.tool_input.command : '';
// 6. Logging uses the context logger, NEVER console.log or console.error.
logger.info('Checking command safety', { command });
if (command.includes('rm -rf /')) {
logger.warn('Blocked dangerous root deletion', { command });
// 7. Return structured output using the builder.
// 8. systemMessage is shown to the user in the UI.
return preToolUseOutput({
systemMessage: 'Safety: Dangerous root deletion command blocked.',
permissionDecision: 'deny',
permissionDecisionReason: 'Safety Policy: Root deletion is forbidden.'
});
}
// 9. Default: Allow execution with a status message.
return preToolUseOutput({
systemMessage: 'Command validated by safety policy.'
});
});
function isShellInput(value: unknown): value is { command: string } {
return typeof value === 'object' && value !== null && typeof (value as { command?: unknown }).command === 'string';
}
tool_inputCodex passes tool_input as unknown because tool schemas are tool-defined and may evolve independently of the hook runtime. Use user-defined type guards to narrow:
function isShellInput(value: unknown): value is { command: string; cwd?: string } {
if (typeof value !== 'object' || value === null) return false;
const candidate = value as { command?: unknown };
return typeof candidate.command === 'string';
}
export default preToolUseHook({ matcher: 'shell' }, (input) => {
if (!isShellInput(input.tool_input)) return preToolUseOutput({});
// input.tool_input is now { command: string; cwd?: string }
return preToolUseOutput({});
});
The package does not ship per-tool predicates or content helpers — Codex tools are defined by the host, not by the hooks SDK. Define narrowing helpers locally in your hook code or in a shared module.
Use the scaffold command when setting up new packages. This generates a complete TypeScript project with tests, linting, and build scripts.
Scaffold Command:
npx @goodfoot/codex-hooks --scaffold ./my-codex-hooks --hooks SessionStart,PreToolUse -o ./.codex/hooks.json
What you get:
src/: Type-safe hook implementations.test/: Vitest tests for your hooks.package.json: Configured with build, test, and lint scripts.tsconfig.json & biome.json: Best-practice configuration.Next Steps:
cd my-codex-hooksnpm installnpm run build (Compiles hooks to the specified output path)npm test (Runs the generated tests)Available Hook Types: PreToolUse, PostToolUse, PermissionRequest, UserPromptSubmit, SessionStart, SubagentStart, Stop, SubagentStop, PreCompact, PostCompact
Monorepo? Use -o to output directly to a plugin directory:
npx @goodfoot/codex-hooks --scaffold ./packages/my-codex-hooks --hooks PreToolUse,PostToolUse -o ../../plugins/my-plugin/.codex/hooks.json
See Installation: Scaffolding for Monorepos.
Different hooks have different capabilities. This table is built from the Codex output schemas (codex-rs/hooks/schema/generated/*.output.schema.json).
| Hook Type | Can Block? | Can Deny? | Can Add Context? | Hook-Specific Output |
|---|---|---|---|---|
| PreToolUse | Yes (decision: 'block', legacy) | Yes (permissionDecision: 'deny') | Yes (additionalContext) | { permissionDecision: 'allow'|'deny'|'ask', permissionDecisionReason, additionalContext, updatedInput } |
| PostToolUse | Yes (decision: 'block') | No | Yes (additionalContext) | { additionalContext, updatedMCPToolOutput } |
| PermissionRequest | No | Yes (decision.behavior: 'deny') | No | { decision: { behavior: 'allow'|'deny', message?, interrupt?, updatedInput?, updatedPermissions? } } |
| UserPromptSubmit | Yes (decision: 'block') | No | Yes (additionalContext) | { additionalContext } |
| SessionStart | No | No | Yes (additionalContext) | { additionalContext } |
| SubagentStart | No | No | Yes (additionalContext) | { additionalContext } |
| Stop | Yes (decision: 'block' requires reason) | N/A | No | none |
| SubagentStop | Yes (decision: 'block' requires reason) | N/A | No | none |
| PreCompact | No | No | No | none (universal only) |
| PostCompact | No | No | No | none (universal only) |
Key distinctions vs. Claude Code:
PreToolUse.permissionDecision accepts allow, deny, and ask (reserved — fail-closed in some Codex builds; see Section 5).PermissionRequest uses a nested decision: { behavior, message? } object, not permissionDecision.Stop / SubagentStop use decision: 'block' with a required reason.PreCompact and PostCompact accept only the universal envelope fields (continue, stopReason, suppressOutput, systemMessage) — no hookSpecificOutput.Universal envelope fields (every hook): continue (default true), stopReason, suppressOutput, systemMessage.
These constraints are unique to Codex (not present in Claude Code):
async hook entries in the configuration, but the runtime does not yet execute them.permissionDecision: 'ask': reserved. The wire schema permits it, but some Codex versions treat it as fail-closed. Treat ask as best-effort and verify against the target Codex build's output_parser.rs unsupported_* helpers.updatedInput (PreToolUse): only honored when permissionDecision: 'allow'. Emitting it with deny or ask is a no-op (and may fail-closed in stricter builds).interrupt: true, updatedInput, updatedPermissions: reserved (fail-closed). Emit them only if you have confirmed the target Codex build supports them; otherwise omit.import { permissionRequestHook, permissionRequestOutput } from '@goodfoot/codex-hooks';
export default permissionRequestHook({ matcher: 'shell' }, (input, { logger }) => {
const cmd = isShellInput(input.tool_input) ? input.tool_input.command : '';
if (cmd.startsWith('echo ')) {
logger.info('Auto-allowing echo', { cmd });
return permissionRequestOutput({
behavior: 'allow'
});
}
return permissionRequestOutput({ behavior: 'deny' });
});
function isShellInput(value: unknown): value is { command: string } {
return typeof value === 'object' && value !== null && typeof (value as { command?: unknown }).command === 'string';
}
import { stopHook, stopOutput } from '@goodfoot/codex-hooks';
export default stopHook({}, (_input, { logger }) => {
const ready = false;
if (!ready) {
logger.info('Blocking stop');
return stopOutput({
decision: 'block',
reason: 'Pending operations must complete first.',
systemMessage: 'Stop blocked: pending operations in progress.'
});
}
return stopOutput({});
});
import { sessionStartHook, sessionStartOutput } from '@goodfoot/codex-hooks';
export default sessionStartHook({ matcher: 'startup' }, () => {
return sessionStartOutput({
additionalContext: 'Project conventions: TypeScript strict, no `any`.'
});
});
When helping a user with hooks, you MUST follow this protocol:
@goodfoot/codex-hooks.npx ... (or npm run build if scaffolded) after every edit..md, .txt, or similar assets, ensure codex-hooks --loader ... and the test runner configuration agree.console.log & console.error: Aggressively correct any code using console.log or console.error to use logger. Stdio is reserved for the protocol; direct writes cause silent failures or UI corruption.export default hookFactory(...).ask, reserved fields).Before debugging hook issues, verify:
@goodfoot/codex-hooks is in package.json dependenciespackage.json (e.g., "build": "codex-hooks -i ...")npm run build)console.log or console.error in hook code (use logger instead)export default hookFactory(...) patternThe CLI picks one of three command-emission modes based on the output path and flags:
| Mode | Trigger | Command form | Filename |
|---|---|---|---|
| plugin | --plugin-root, or a .codex-plugin/ marker found by walking up from the output path | node "${PLUGIN_ROOT}/hooks/<name>.mjs" | stable (no hash) |
| codex-local | Output path contains a .codex/ segment | node "$(git rev-parse --show-toplevel)/.codex/bin/<name>.<hash>.mjs" | hashed |
| absolute | Anything else | node "/abs/path/to/<name>.<hash>.mjs" | hashed |
Standalone Project (codex-local):
Place the compiled manifest at .codex/hooks.json in your project root. Codex auto-discovers it.
npx -y @goodfoot/codex-hooks -i "src/**/*.ts" -o ".codex/hooks.json"
Codex Plugin (Recommended):
Build into the plugin's hooks/ directory and pass --plugin-root (or place a .codex-plugin/ marker so it auto-detects):
npx -y @goodfoot/codex-hooks -i "src/**/*.ts" -o "./hooks/hooks.json" --plugin-root
Plugin mode emits ${PLUGIN_ROOT}-relative commands and stable, hash-free filenames so the built hooks.json is portable inside an installed plugin and Codex's hook trust hash stays valid across rebuilds. Codex injects PLUGIN_ROOT (and CLAUDE_PLUGIN_ROOT for compatibility) into plugin hook environments and substitutes ${PLUGIN_ROOT} before execution.
Monorepo Project:
Output to a sibling plugin directory and let --plugin-root anchor the command form:
npx -y @goodfoot/codex-hooks -i "src/**/*.ts" -o "../../plugins/my-plugin/hooks/hooks.json" --plugin-root
See Monorepo Integration.
Filename overrides: --stable-names forces hash-free names in any mode; --no-stable-names opts back into hashed names (the pre-1.1 default).
tool_input narrowing.