Preface

In DeepSeek Harness (DSH), many processes need to be attached to specific events: intercepting before tool calls, logging after tool calls, performing supplementary checks upon user submission, and writing status at the start or end of a session. If this logic is scattered across various scripts, subsequent troubleshooting and migration become cumbersome.

dsh-hooks-plugin provides Claude Code-style hooks: running shell commands on agent/tool lifecycle events, with configuration sourced from .dsh/hooks.json.

What is it

dsh-hooks-plugin is maintained by KYinCode and is licensed under the MIT License. It adds a hooks entry point for DSH, allowing developers to configure event-response commands at the project, preset, or global level.

The core positioning is as follows:

Running shell commands on DSH agent/tool lifecycle events;
Configuration uses .dsh/hooks.json;
JSON structure follows the shape of Claude Code hooks.

Core Features

Four-Level Configuration

Hook configuration is managed based on directory lifecycle, supporting four levels of sources:

Global: ~/.dsh/hooks.json
Preset: <preset-dir>/hooks.json
Project: <Project Root>/.dsh/hooks.json
Project Local: .dsh/hooks.local.json

Project configuration changes will be automatically reloaded without needing a restart.

v1 Wired Events

v1 actually wires the following events:

PreToolUse
PostToolUse
PostToolUseFailure
UserPromptSubmit
SessionStart
SessionEnd
Stop
SubagentEnd

Among them, PreToolUse supports outputting a deny decision via stdout. When deny occurs, an official tool failure card appears, and the model sees content similar to this:

Error: <reason>

Hook Types and Fields

v1 implements command and http type hooks; v1 does not implement prompt / agent type hooks.

Common fields for hooks include:

if
timeout
statusMessage
once

command type can configure:

command
shell
async
asyncRewake

http type can configure:

url
headers
allowedEnvVars

The configuration structure uses matcher[] + hooks[]:

{
  "<Event>": [
    {
      "matcher": "<pattern>",
      "hooks": []
    }
  ]
}

Subagent Triggering

Subagents trigger hooks by default, and the input payload carries:

agent_id
agent_type
delegation_depth

If subagent triggering needs to be disabled, you can configure:

{
  "subagents": false
}

Hot Reload and Hot Upgrade

Project configuration changes will be automatically reloaded without requiring a restart.

If dsh-hot-installer is installed, after upgrading the plugin, changes take effect immediately without a restart:

dsh plugin --profile web add dsh-hooks-plugin@<new-version>

Recent Records and Logs

Each hook record is written to the recent records file:

recent.jsonl

Limited to 200 by default, adjustable via environment variable:

DSH_HOOKS_RECENT_MAX

File logs are located at:

~/.dsh/logs/dsh-hooks/dsh-hooks.log

Automatically rolling over when exceeding 1 MiB by default, adjustable via environment variable:

DSH_HOOKS_MAX_LOG_BYTES

Recent records interface:

GET /dsh-hooks/recent

Installation and Activation

npm installation:

dsh plugin --profile web add dsh-hooks-plugin

If using a local tarball, you can pack it first, then hand the generated tarball to dsh for installation:

npm pack
dsh plugin --profile web add ./<tarball generated by npm pack for dsh-hooks-plugin>

After installation, new sessions take effect automatically; existing active sessions will also take effect. After the process restarts to continue the old session, it will automatically reconnect as the agent is rebuilt.

The plugin has peerDependency requirements for the runtime environment:

{
  "peerDependencies": {
    "@deepseek-ai/cordis": "^4.0.1",
    "@deepseek-ai/dsh-shell": "^0.1.0-rc.6",
    "@deepseek-ai/dsh-subprocess": "^0.1.0-rc.6",
    "react": "^18.2.0"
  }
}

Typical Usage

Triggering Commands Before Tool Use

Create at the project root:

<Project Root>/.dsh/hooks.json

Example configuration is as follows:

{
  "PreToolUse": [
    {
      "matcher": "Read|Write|Edit",
      "hooks": [
        {
          "type": "command",
          "command": "echo hook triggered",
          "timeout": 5
        }
      ]
    }
  ]
}

This configuration runs before Read, Write, and Edit tool calls:

echo hook triggered

Denying Access to Private Paths using PreToolUse

The example configures conditional filtering for the Read event:

{
  "PreToolUse": [
    {
      "matcher": "Read",
      "hooks": [
        {
          "type": "command",
          "command": "node -e \"process.stdout.write(JSON.stringify({hookSpecificOutput:{hookEventName:'PreToolUse',permissionDecision:'deny',permissionDecisionReason:'blocked'}}))\"",
          "if": "Read(*private*)",
          "timeout": 5
        }
      ]
    }
  ]
}

When Read matches the *private* condition, the hook outputs a deny decision via stdout. When PreToolUse decides deny, an official tool failure card appears, and the model sees Error: <reason>.

Command Hook stdin / stdout

Command hooks read a single-line JSON input from stdin. Common fields include:

{
  "session_id": "...",
  "cwd": "...",
  "hook_event_name": "PreToolUse",
  "tool_name": "read",
  "tool_input": {
    "path": "..."
  },
  "tool_use_id": "..."
}

If triggered by a subagent, it also includes:

{
  "agent_id": "...",
  "agent_type": "...",
  "delegation_depth": 0
}

Command hooks output JSON decisions via stdout. hookSpecificOutput can include:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "blocked",
    "additionalContext": "..."
  }
}

Development Verification and Viewing

Run tests:

node --test test/

View file logs:

~/.dsh/logs/dsh-hooks/dsh-hooks.log

View recent records:

GET /dsh-hooks/recent

After installation, you can first load the auto-registered skill:

skill dsh-hooks-authoring

For in-depth configuration, see the documentation inside the package:

docs/CONFIGURATION.md

Applicable Scenarios and Notes

Suitable for the following scenarios:

Intercepting or checking before tool calls;
Performing audit, logging, or subsequent actions after tool calls;
Running fixed processes on events such as session start, end, or user submission;
Configuring hooks at different levels: project, preset, or global;
Maintaining a Claude Code-style JSON configuration experience in DSH.

Please note:

Hooks execute shell commands and run with the permissions of the current dsh process;
Check the source code and MIT license before installing;
The lifecycle of the configuration equals the lifecycle of the directory it is in;
If a preset reports "Cannot find package", usually you need to manually remove the corresponding line or delete the preset directory;
v1 does not implement prompt / agent type hooks;
parseHookConfig will reject unknown types;
Does not implement uninstall lifecycle, dangling line warnings, session-level config files, PreCompact / PostCompact, settings page;
Does not inject CLAUDE_PROJECT_DIR, CLAUDE_PLUGIN_ROOT, or other CC-exclusive environment variables;
Does not implement mechanisms such as ${CLAUDE_PLUGIN_ROOT}, CLAUDE_PLUGIN_OPTION_*, CLAUDE_ENV_FILE.

DSH internal decisions directly consume the waterfall return value. The stdout JSON is a protocol mechanism for command hooks to express decisions, not parsed based on Claude Code output.

Links

Community directory page (independent site, not equivalent to DeepSeek or Hypersquare’s official app store):

https://www.skillhub.cn/plugins/KYinCode/dsh-hooks-plugin

GitHub:

https://github.com/KYinCode/dsh-hooks-plugin