# Conflicts: # .agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml # .agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md # .agents/notes/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.i18n.yaml # .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml # .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md # .agents/notes/implemented/bug-fix/2026-07-29-pnpm-setup-runner-isolation.i18n.yaml # .agents/notes/implemented/bug-fix/2026-07-29-pnpm-setup-runner-isolation.zh.md # .agents/notes/implemented/bug-fix/2026-08-18-request-image-payload-bound.i18n.yaml # .agents/notes/implemented/bug-fix/2026-08-18-request-image-payload-bound.zh.md # .agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml # .agents/notes/implemented/feature/2026-07-06-sandbox.zh.md # .agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.i18n.yaml # .agents/notes/implemented/feature/2026-07-21-cross-session-references.i18n.yaml # .agents/notes/implemented/feature/2026-07-21-cross-session-references.zh.md # .agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.i18n.yaml # .agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.zh.md # .agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.i18n.yaml # .agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.i18n.yaml # .agents/notes/implemented/feature/2026-08-03-web-search-source-scroll.i18n.yaml # .agents/notes/implemented/feature/2026-08-03-web-search-source-scroll.zh.md # .agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.i18n.yaml # .agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.zh.md # .agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml # .agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md # .agents/notes/implemented/feature/2026-08-11-workspace-sidebar-order-and-folding.i18n.yaml # .agents/notes/implemented/feature/2026-08-15-product-subagent-noninteractive-permissions.i18n.yaml # .agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.i18n.yaml # .agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.zh.md # .agents/notes/implemented/process/2026-07-22-evidence-based-larger-hosted-runners.i18n.yaml # .agents/notes/implemented/process/2026-07-22-evidence-based-larger-hosted-runners.zh.md # .agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.i18n.yaml # .agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.zh.md # .agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.i18n.yaml # .agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.zh.md # .agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.i18n.yaml # .agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md # .agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.i18n.yaml # README.i18n.yaml # README.zh.md # docs/architecture.i18n.yaml # docs/architecture.zh.md # docs/development.i18n.yaml # docs/development.zh.md # docs/persistence-catalog.i18n.yaml # docs/persistence-catalog.zh.md # docs/subsystems/README.i18n.yaml # docs/subsystems/README.zh.md # docs/subsystems/agent-team.i18n.yaml # docs/subsystems/agent-team.zh.md # docs/subsystems/client-modules.i18n.yaml # docs/subsystems/client-modules.zh.md # docs/subsystems/commands.i18n.yaml # docs/subsystems/commands.zh.md # docs/subsystems/persistence.i18n.yaml # docs/subsystems/persistence.zh.md # docs/subsystems/session-reference.i18n.yaml # docs/tool-catalog.i18n.yaml # docs/tool-catalog.zh.md # docs/user/guide/providers.i18n.yaml # docs/user/guide/providers.zh.md # packages/README.i18n.yaml # packages/README.zh.md # packages/bundle/web-app/README.i18n.yaml # packages/bundle/web-app/README.zh.md # packages/client/README.i18n.yaml # packages/client/README.zh.md # packages/client/connection/README.i18n.yaml # packages/client/connection/README.zh.md # packages/client/ui-conversation/README.i18n.yaml # packages/client/ui-conversation/README.zh.md # packages/client/ui-primitives/README.i18n.yaml # packages/client/ui-primitives/README.zh.md # packages/client/ui-sidebar/README.i18n.yaml # packages/client/ui-sidebar/README.zh.md # packages/client/ui-workspace/README.i18n.yaml # packages/client/ui-workspace/README.zh.md # packages/context/README.i18n.yaml # packages/context/README.zh.md # packages/core/agent-loop/README.i18n.yaml # packages/credentials/README.i18n.yaml # packages/credentials/README.zh.md # packages/experimental/agent-team/README.i18n.yaml # packages/experimental/agent-team/README.zh.md # packages/experimental/tool-agent-team/README.i18n.yaml # packages/experimental/tool-agent-team/README.zh.md # packages/host/frontend-static/README.i18n.yaml # packages/host/frontend-static/README.zh.md # packages/host/webserver/README.i18n.yaml # packages/host/webserver/README.zh.md # packages/interaction/commands/README.i18n.yaml # packages/interaction/commands/README.zh.md # packages/plan/plan-mode/README.i18n.yaml # packages/plan/plan-mode/README.zh.md # packages/sandbox/sandbox-local/README.i18n.yaml # packages/sandbox/sandbox-local/README.zh.md # packages/session/README.i18n.yaml # packages/session/README.zh.md # packages/session/session-persistence-sqlite/README.i18n.yaml # packages/session/session-persistence-sqlite/README.zh.md # packages/session/session-projection-cache/README.i18n.yaml # packages/session/session-projection-cache/README.zh.md # packages/shell/tool-pwsh/README.i18n.yaml # packages/shell/tool-pwsh/README.zh.md # packages/subagent/subagent-codex/README.i18n.yaml # packages/subagent/subagent-codex/README.zh.md # packages/subagent/subagent/README.i18n.yaml # packages/subagent/subagent/README.zh.md # packages/web/tool-web/README.i18n.yaml # packages/web/tool-web/README.zh.md # scripts/snapshots/translation-prompt-v4/request-response.expected.json
@deepseek-ai/dsh-hooks-codex
English | 中文
A cordis plugin that runs the supported subset of a user's existing Codex hook config on the harness's canonical interception points. The Codex dialect half of the hooks subsystem. The dialect-agnostic primitives come from @deepseek-ai/dsh-hook-protocol; this bridge owns the Codex-shaped payloads, matcher mode, and decision mapping.
This bridge implements a deliberate subset of Codex's current hook protocol:
- Five of ten hook points:
PreToolUse,PostToolUse,SessionStart,UserPromptSubmit, andStop. - Regex-only matchers (no literal fast path; the matcher is always an unanchored regex).
- snake_case stdin payloads with
turn_id/modelextras, written without a trailing newline. - No Codex plugin env injection and no config-time placeholder substitution (the command still receives the executor's environment and runs through its shell).
- No pre-tool approval or rewrite path — a hook can block, but the bridge does not pre-approve or replace tool input.
A native cordis plugin could do everything this bridge does, more powerfully; the bridge exists only as a compatibility path for the mapped Codex subset (see the interception extension-points Agent Note).
Config
import type { Config } from '@deepseek-ai/dsh-hooks-codex'
const config: Config = {
configPath: '/path/to/.codex/hooks.json', // required
model: 'deepseek-v4', // optional: stamped on every payload (Codex includes `model`)
defaultTimeoutMs: 600_000, // optional: per-hook timeout when a hook sets none
stderrSummaryMaxChars: 500, // optional: char cap on the hook/result event's persisted stderr summary
}
In a cordis.yml:
- dsh-hooks-codex:
configPath: ./.codex/hooks.json
model: deepseek-v4
The config is parsed once at load. configPath is process-level — a relative path resolves against the process launch cwd at load time, not per-session (TODO(per-session-hook-config)). A read/parse failure is contained (logs + registers nothing); an invalid regex matcher on an event that consumes matchers is one such failure and reports its pattern and event. Only sync type: 'command' hooks run — a non-command or async: true hook is parsed-and-skipped with a warning. A hook accepts timeout or the timeoutSec alias; one that sets neither runs under the protocol's reference default (DEFAULT_HOOK_TIMEOUT_MS from dsh-hook-protocol, 10 minutes). Events outside the five bridge-supported points are dropped at parse.
The hooks themselves run in the agent's session workspace: for the agent-scoped points the bridge passes the session's cwd as the hook process's working directory, so a hook operates in the user's project tree, not the server launch dir.
Hook points → typed Decisions
| Codex hook | Harness point | Mapping |
|---|---|---|
SessionStart |
agent/session-start (emit) |
a plain-stdout hook's output → additionalContext → agent.inject() |
UserPromptSubmit |
agent/pre-step (waterfall) |
block (exit 2) → PreStepDecision.reject; additionalContext-only → delegate via next() then append a separately sourced message to a downstream enter decision |
PreToolUse |
tools/pre-execute (waterfall) |
block → PreToolDecision.deny (no allow/ask) |
PostToolUse |
tools/post-execute (waterfall) |
block → block with feedback; additionalContext-only → delegate via next() then prepend a separately sourced context to the downstream decision; Code Mode defers sub-call contexts until the outer run_code result |
Stop |
agent/turn-stopping (serial) |
a blocking Stop hook feeds its reason through steer(), forcing another step |
A tool call's payload carries the real tool_name (the same value the matcher tests) and Codex's tool_input: { command } shape (the command arg when present, else ''). The matcher subject is the tool name (PreToolUse/PostToolUse) or the session source (SessionStart); UserPromptSubmit/Stop ignore matchers.
Every agent-scoped stdin payload carries session_id and transcript_path. The bridge resolves the latter through ctx.sessionPersistence.locate(session.header) when available and otherwise sends null, preserving the Codex string | null shape. Lookup does not create or flush the artifact, so a path can be absent before the first turn-end checkpoint or omit the current open turn.
SessionStart — the one emit point — runs detached; each run chain is tracked, and disposing the bridge aborts a still-running hook process, then drains the continuation before the dispose resolves (createDetachedRuns in dsh-hook-protocol).
Context source
Injected context carries an explicit { kind: 'plugin', plugin: 'hooks-codex' } source so the durable message is never mistaken for a user prompt.
Model Experience
Hook-provided context
What the model sees
SessionStart, accepted prompt, and post-tool hooks can add source-attributed context messages; a blocking Stop hook adds its reason as next-step steering.
Token effect
No cost when hooks return no context. Hook text is data-dependent, logged, and resent until compaction.
KV Cache effect
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
Blocked prompt or tool outcome
What the model sees
Provider-supplied reasons pass through verbatim. When absent, a blocked prompt uses exactly blocked by UserPromptSubmit hook, a denied tool becomes Error: blocked by PreToolUse hook, blocked post-tool feedback is exactly blocked by PostToolUse hook, and a blocking stop adds steering exactly continue: blocked by Stop hook. Codex systemMessage is not surfaced.
Token effect
Blocking a prompt removes its request tokens; denial or feedback adds the retained fallback or provider text; forced continuation pays another full request.
KV Cache effect
A blocked prompt sends no request and invalidates nothing. Denial, feedback, and forced-continuation context append after the reusable prefix without rewriting it.
Known Limitations and Deferred Work
- Unsupported hook events (5 of Codex's current 10):
PermissionRequest,PreCompact,PostCompact,SubagentStart, andSubagentStop. Config for these events is silently dropped during parsing. The comparison baseline is Codex's official hook reference. SessionStartis partial: plain stdout and JSONadditionalContextwork, but the hook runs detached, so context can miss the first request (TODO(session-start-gating)).UserPromptSubmitis partial: blocking plus plain-stdout or JSON context work, but the commonsystemMessageand{"continue": false}controls are not enforced.PreToolUseis partial: blocking works, butadditionalContext,permissionDecision: "allow", andupdatedInputare ignored. Every tool is represented astool_input: { command }, so non-shell tool arguments are not faithfully exposed to the hook.PostToolUseis partial: blocking feedback and JSONadditionalContextwork, but{"continue": false}is not enforced, non-shell tool arguments are reduced to{ command }, and structured tool output is flattened to text intool_response.Stopis partial: blocking forces another model turn, butstop_hook_activeis alwaysfalse,last_assistant_messageis alwaysnull, and{"continue": false}is not enforced. An unconditionally blocking hook therefore force-continues every step unless it self-limits (TODO(stop-loop-guard)).- Common payload and output fields are partial: every mapped event reports the statically configured
modelandpermission_mode: "default"instead of current Codex runtime values.systemMessageis logged + warned but not surfaced, and{"continue": false}is recorded but does not apply Codex's event-specific stop behavior (TODO(hook-continue-false)). - Config loading and execution are partial: one process-level
configPathis parsed at load; Codex's active user, project, session, system/managed, and plugin layers, trust controls, and inlineconfig.tomlhook form are not implemented (TODO(per-session-hook-config)). Only synchronouscommandhandlers run, current metadata such asstatusMessageandcommandWindowsis ignored, and matching handlers run serially rather than with Codex's concurrent launch semantics.