One Hook first. Explicit deny on policy failure. No assumption that a Hook is a sandbox.
For DeepSeek Harness Hooks configuration, start with one observable PreToolUse rule for a low-risk tool, record every invocation, and test allow, deny, script failure, and timeout before adding more automation. The current DeepSeek Harness source treats Hooks as extension points around the agent and tool pipeline, not as an operating-system isolation boundary. (official Hook architecture)
This guide is for:
- Agent developers who need validation or feedback before and after tool calls.
- Platform engineers who need repeatable project-level and environment-level delivery.
- Remote environment maintainers who must preserve observability and rollback after disconnects or restarts.
[ SECTION_01 ] Start with one control objective
A Hook can serve several different purposes:
- Permission interception — stop or ask before a tool runs.
- Result recording — capture the result after execution.
- Stop gating — decide whether the agent may finish a turn.
- Context injection — add information to a later model request.
The first version should select only one. For most teams, permission interception is the best starting point because the success signal is binary: the tool either reaches execution or it does not.
DeepSeek Harness exposes several extension seams. The official architecture describes tools/pre-execute as a waterfall for pre-tool policy, tools/execute as the execution layer, and tools/post-execute as the result-processing layer. The durable session log records tool calls and results, while Hook bridges add hook/invoked and hook/result records. (official execution architecture)
A Hook does not replace:
- Filesystem permissions.
- Credential isolation.
- Workspace restrictions.
- Process sandboxing.
- Network egress controls.
- Human approval for high-impact operations.
That distinction matters on a remote Mac. A Hook can reject a tool request, but it does not automatically stop a separate process that already escaped the intended workspace.
Decision rule: if the requirement is “never allow this process to access that path,” configure the sandbox or operating-system policy as well. Use the Hook for request-level policy and evidence.
[ SECTION_02 ] Use this deployment decision gate
Before adding a second matcher or another automation action, complete the following gate. The rollout should remain at the current stage if any required item is unchecked.
Stage 1: minimum interception
- [ ] One control objective is documented.
- [ ] One low-risk tool is selected.
- [ ] The active Hook dialect is confirmed from the current source.
- [ ] The configuration path is visible in the loaded profile.
- [ ] A matching call produces a
hook/invokedrecord. - [ ] A non-matching call leaves the unrelated task unaffected.
Move to Stage 2 only when all six items pass.
Stage 2: failure evidence
- [ ] Explicit allow has been observed.
- [ ] Explicit deny has been observed.
- [ ] A controlled script failure has been observed.
- [ ] A controlled timeout has been observed.
- [ ] The team knows whether each failure blocks or permits the tool.
- [ ] The current configuration is saved as a rollback version.
If any failure result is unclear, keep the policy restrictive and do not expand tool permissions.
Stage 3: remote delivery
- [ ] The Hook interpreter exists in the non-interactive environment.
- [ ] The remote working directory is stable.
- [ ] File and directory permissions are verified.
- [ ] Required environment variables are available without exposing secrets.
- [ ] Logs survive disconnect and reconnect.
- [ ] The same allow and deny fixtures pass after restart.
Move to continuous or unattended work only after Stage 3 passes.
This gate is the main decision tool for the deployment. It prevents a parsed configuration file from being mistaken for a proven runtime policy.
[ SECTION_03 ] DeepSeek Harness Hooks configuration starts with the wiring path
There is no single universal Hook directory that should be copied between installations. The official architecture uses ordered configuration layers:
- Bundles loaded by the selected profile.
- The profile’s
cordis.patch.yml. - The home-level patch.
- An optional
--patchoverlay.
The effective tree can be inspected with:
dsh --profile web --dump-config
The Hook bridge then receives a configPath. That path points to either a bare hooks.json event map or a settings file whose hooks key contains the event map. The file is read when the process loads, not rediscovered for every project session. A relative path resolves against the process launch directory. (official configuration and Hook architecture)
Where should the Hook configuration file live? Keep the Hook file beside the versioned deployment configuration, then pass its path through the active profile or patch layer. Do not depend on the current shell directory unless the launch process guarantees it.
A safe repository layout looks like this:
project/
.dsh/
hooks/
pretool-policy.py
hooks.json
cordis.patch.yml
The exact patch row that mounts the bridge must be taken from the current generated configuration catalog or source tree. DeepSeek Harness is still in developer preview, and its compatibility boundaries should be checked against the release being deployed. (official repository)
[ SECTION_04 ] Use one matcher and verify its dialect
The current Hook Protocol separates shared command execution from dialect-specific behavior. The shared protocol defines command hooks, matcher groups, output decoding, restrictive result merging, and durable invocation records. Claude Code and Codex bridges own their own event names, matcher rules, payloads, and decision mapping. (official Hook Protocol source)
The minimum event set differs by bridge:
- Claude Code supports
SessionStart,UserPromptSubmit,PreToolUse,PostToolUse,Stop,SubagentStart, andSubagentStop. - Codex supports
PreToolUse,PostToolUse,SessionStart,UserPromptSubmit, andStop. - Only command hooks are executed by these bridges.
- Unsupported hook types are skipped and warned about.
A minimal event map can use the documented shape without inventing a DeepSeek-specific configuration key:
{
"hooks": {
"PreToolUse": [
{
"matcher": "bash",
"hooks": [
{
"type": "command",
"command": "python3 .dsh/hooks/pretool-policy.py",
"timeout": 10
}
]
}
]
}
}
This example shows the Hook file format. It does not prove that the surrounding profile patch uses a particular row name. That part must be copied from the current source or generated catalog.
Matcher behavior is not interchangeable:
- Claude Code uses literal matching for simple alphanumeric, underscore, and pipe patterns. More complex patterns are treated as regular expressions.
- Codex interprets matchers as regular expressions.
UserPromptSubmitandStopdo not use a matcher subject in the current bridges.- A malformed regular expression can reject the complete Hook configuration before listeners are registered.
Should a first matcher target every tool? No. Start with one low-risk tool name. A broad matcher makes it difficult to separate a policy bug from a normal agent failure.
Record three outcomes during the first run:
- A matching call that invokes the Hook.
- A non-matching call that does not invoke it.
- A matching call whose policy result is visible in the session record.
[ SECTION_05 ] Define input, output, and failure behavior
The Hook process receives a JSON payload on standard input. For PreToolUse, the Claude Code bridge currently includes fields such as:
{
"session_id": "redacted",
"cwd": "/workspace/project",
"hook_event_name": "PreToolUse",
"tool_name": "bash",
"tool_input": {
"command": "safe test command"
},
"tool_use_id": "redacted"
}
Do not log the complete payload by default. Tool arguments can contain customer paths, access tokens, source code, or generated data. Store a redacted tool name, workspace identifier, policy version, and decision reason instead.
The protocol can decode:
- Exit code.
- Standard error.
- Standard output.
continue.- A stop reason.
- A permission decision.
- Additional context.
- A system message.
- An input rewrite request.
The current Claude Code bridge parses updatedInput but does not honor it. It logs a warning instead. A Hook should therefore reject and request a new tool call rather than assuming it can safely rewrite arguments in place. (official Claude Code bridge source)
How should a Hook block a dangerous command? Match PreToolUse, inspect the structured tool input, and return the bridge’s documented blocking result. For a command Hook, the policy script should:
- Parse standard input as JSON.
- Confirm the event name and tool name.
- Extract the command field only if the expected tool schema is present.
- Apply an allowlist or narrowly defined deny rule.
- Return an explicit blocking decision and reason.
- Write diagnostic detail to redacted logs, not to the model-visible result.
Avoid substring-only rules such as blocking one word anywhere in a command. They can block harmless tasks and miss shell variants. Prefer argument-aware checks, workspace checks, and a narrow set of permitted command forms.
[ SECTION_06 ] Test the four failure paths before expansion
A successful configuration load is not a successful Hook deployment. The agent’s actual behavior must be tested from the session event stream or an equivalent runtime record.
Use this order:
- Explicit allow
- The matcher fires.
- The Hook returns an allow result.
- The tool executes.
-
A
hook/invokedandhook/resultpair appears. -
Explicit deny
- The matcher fires.
- The Hook returns a blocking result.
- The tool body is skipped.
-
The session records the Hook decision and reason.
-
Script exception or nonzero exit
- The script fails in a controlled test fixture.
- The resulting decision is recorded.
-
The agent’s next action is observed rather than inferred from the JSON file.
-
Timeout
- The Hook exceeds its configured limit.
- The result includes the observed failure state and duration.
- The tool is checked for execution or non-execution.
Will the Agent continue after a Hook script fails? It depends on how the failure reaches the bridge. The current runner maps a normal process result through the Hook output decoder, but an infrastructure failure such as an unusable working directory or missing shell is converted into an outcome with no exit code. The runner documentation explicitly describes that case as non-blocking, so a team must not assume that every failure fails closed. (official Hook runner source)
This creates an important policy distinction:
- A deliberate Hook decision can deny execution.
- A script that returns a recognized blocking result can deny execution.
- A missing runtime, bad working directory, or runner-level infrastructure fault may allow the turn to proceed under the current implementation.
If the policy requires fail-closed behavior, test the actual bridge and add a separate deployment guard. Do not rely on a comment saying “deny on error.” Keep the first rollout in a restricted workspace until the failure path is proven.
The current shared runner default is 600,000 milliseconds when no Hook-specific timeout is supplied. A per-Hook timeout is expressed in seconds and converted by the runner. This is a protocol detail that should be checked against the current source before changing values.
[ SECTION_07 ] Expand to Bash and file writes in separate releases
Once the first matcher is repeatable, add only one new operation class. A sensible sequence is:
- Read-only tool observation.
- Bash policy.
- File modification policy.
- External tool or network policy.
- Stop-condition policy.
Each release should preserve the previous configuration as a rollback file.
Before allowing Bash or file writes, define:
- The permitted workspace root.
- Whether symlinks are accepted.
- Which environment variables are visible.
- Whether credentials are removed from the child process.
- Which argument types are accepted.
- How long the Hook may run.
- Where redacted results are stored.
- Which person or service owns rollback.
The official tool pipeline places tools/pre-execute before monotonic guards and tool execution. Approval refusal also results in the tool body being skipped. Post-execution Hooks can block, replace, or add context after the tool has run, but they cannot provide the same protection as a pre-execution denial. (official tool execution pipeline)
A broad matcher can create false positives. A narrow matcher can miss aliases, wrapper tools, or alternate tool names. For that reason, every matcher change should include:
- One intended match.
- One harmless non-match.
- One naming variant.
- One malformed or unexpected input.
- One rollback comparison.
A rating of 5/5 should require all five observations. A configuration that only proves the allow path deserves 2/5, even if the Hook file parses correctly.
[ SECTION_08 ] Rebuild execution responsibility on a remote Mac
Moving the same files to a remote Mac does not guarantee the same behavior. The Hook runs inside the session workspace and depends on the runtime visible to that process. The current bridge also reads configuration at process load, so a restart can change which file is active.
Check these items after delivery:
- Hook path
- Confirm the configured
configPathexists on the remote Mac. - Resolve relative paths from the actual launch directory.
-
Confirm the profile or patch loaded the intended bridge.
-
Runtime
- Verify the selected
python3, Node.js, or other interpreter. - Avoid assuming the interactive shell profile is loaded.
-
Test the same command under the service or headless launch mode.
-
Permissions
- Confirm the Hook file is readable and executable where required.
- Confirm its parent directories are traversable.
-
Confirm the session user can write only to the intended log location.
-
Environment
- Check required variables without printing secret values.
- Confirm project-directory substitution points to the session workspace.
-
Remove customer paths and credentials from diagnostic output.
-
Restart behavior
- Stop and restart the Harness process.
- Re-run the allow and deny fixtures.
- Change the working directory and repeat the non-match test.
- Disconnect the remote session, reconnect, and inspect the new Hook records.
Claude Code bridge configuration can substitute ${CLAUDE_PLUGIN_ROOT} and ${CLAUDE_PROJECT_DIR}. The latter is also exported to Hook processes and defaults to the session workspace when no explicit project directory is configured. This behavior should be tested rather than assumed on a service-managed remote Mac. (official Claude Code bridge implementation)
For a remote deployment, the minimum evidence package should contain:
- Harness revision or release identifier.
- Active profile.
- Hook bridge and dialect.
- Config file path.
- Matcher string.
- Tool name.
- Redacted workspace identifier.
- Decision and reason.
- Duration.
- Failure result.
- Rollback version.
- Post-restart result.
The current event model provides hook/invoked and hook/result records. The result record can include the decision, exit code, bounded stderr summary, and wall-clock duration. These records are more useful than a terminal screenshot because they can be correlated with the tool call and reviewed after a disconnect. (official Hook event types)
[ SECTION_09 ] Run the final four-part release acceptance
Before expanding from a trial workspace to unattended work, collect four separate proofs:
Allowed task
The intended tool matches. The Hook permits it. The tool executes. The result is visible.
Rejected task
The policy condition matches. The Hook denies it. The tool body does not execute. The reason is understandable without exposing sensitive input.
Broken Hook
The test script exits abnormally or produces malformed output. The session record shows what happened. The team confirms whether the current bridge continued or stopped.
Recovery task
The Hook is restored to the known-good version. The same allowed task succeeds. A restart or reconnect does not silently load an older file.
Only after these four tests pass should the team decide whether to keep the trial scope or expand it. The acceptance record should be versioned with the Hook file and profile patch. A manual checklist without event evidence is not enough for a tool that can modify files or launch processes.
[ SECTION_10 ] Current setup versus a remote Mac delivery
A local setup is convenient, but it often hides the real operational costs: shell-dependent paths, interactive environment variables, missing runtime packages, and logs that disappear with the terminal session. A local laptop also makes restart testing inconsistent when the Harness process is launched from different directories.
A remote Mac adds an explicit workspace, repeatable runtime, persistent access, and a cleaner place to inspect Hook records. It does not remove the need for policy testing, and it is not the best fit for workloads that require a permanently attached physical device or stable, heavy local compute for months.
Once the local Hook has passed the allow, deny, broken-script, and recovery tests, compare the same evidence against the NOVAKVM remote Mac options. The delivery review should confirm script dependencies, restart recovery, workspace ownership, and log retention before opening continuous tasks. For teams evaluating a fixed Mac mini workflow, the Mac mini M4 order options can serve as a separate comparison point against maintaining the entire machine locally.
The sensible boundary is simple: use Hooks for observable request policy, use system controls for isolation, and move to a remote Mac only after the failure behavior is recorded rather than guessed.