How to Troubleshoot DCG Hook Failures and Parse Errors in Destructive Command Guard

When you troubleshoot DCG hook failures and parse errors, you typically encounter either runtime exits with non-zero codes (hook-runtime failures) or malformed JSON outputs (parse errors), both of which originate in src/hook.rs and src/main.rs and can be diagnosed using RUST_LOG=debug and the --debug-session flag.

The Destructive Command Guard (DCG) from the Dicklesworthstone/destructive_command_guard repository functions as a Claude Code hook that evaluates commands through a JSON protocol read from stdin. When the binary fails to process inputs correctly, it produces specific error codes like DCG-3001 for JSON parse errors or exits silently due to runtime panics in the pattern matching engine. Understanding how to troubleshoot DCG hook failures and parse errors requires tracing the execution flow from ingestion through the evaluator pipeline.

Hook Execution Architecture and Failure Points

DCG processes commands through a strict pipeline defined across several core modules. The entry point in src/main.rs orchestrates the flow: first calling hook::parse_input in src/hook.rs to deserialize the JSON payload using serde_json::from_str, then passing the normalized command through src/normalize.rs for path stripping and alias expansion.

The quick-reject filter in src/evaluator.rs (utilizing memchr against the static QUICK_REJECT_SET) provides an early exit for safe commands. If the command passes this filter, DCG checks against whitelist patterns in the pattern! macro before evaluating destructive patterns defined in the destructive! macro. Finally, src/output.rs constructs the denial JSON via the HookSpecificOutput struct, writing structured output to stdout and colored warnings to stderr (controlled by colored::control::set_override in src/cli.rs).

Runtime failures typically occur when std::panic::catch_unwind in main.rs intercepts panics from downstream modules—most commonly regex compilation errors in src/evaluator.rs or I/O errors during pack loading.

Common DCG Hook Failure Patterns

DCG-3001: JSON Parse Errors

The DCG-3001 error code indicates that hook::parse_input failed to deserialize the incoming JSON. This occurs when the payload is missing required fields like tool_input.command or contains malformed syntax. The function returns an Err that propagates to main.rs, which prints an error JSON and exits with code 1.

Truncated or Malformed Denial Output

When the process aborts mid-write, you receive partial JSON output followed by a crash dump. This happens when an uncaught panic occurs after src/output.rs begins writing the denial but before the buffer flushes. Common causes include invalid regex patterns in the destructive! macro or corrupted pack files in src/packs/*.rs.

Silent Allows on Timeout

Commands containing complex heredocs may trigger the 200ms timeout in src/heredoc.rs. When heredoc::extract hits this deadline, it returns None, causing the hook to fail-open and silently allow the command without evaluation.

Configuration Loading Failures

DCG-2002 errors originate in src/config.rs when config::load fails to parse config.toml using toml_edit. Missing configuration files or syntax errors in user-defined packs trigger this error category.

TTY Detection Issues

If stderr is not a TTY, DCG disables colored warnings via colored::control::set_override, but still writes valid JSON to stdout. Callers that discard both streams may incorrectly assume the hook failed when it actually produced a denial.

Diagnostic Procedures for DCG Hooks

1. Enable Full Debug Logging

Run DCG with RUST_LOG=debug to surface tracing output from src/logging.rs. This reveals the exact processing stage where failures occur.

RUST_LOG=debug echo '{"tool_name":"Bash","tool_input":{"command":"git reset --hard"}}' | dcg

Look for log lines tagged parse_input, normalize, evaluator, and any error entries indicating where the pipeline breaks.

2. Capture Session State with Debug Mode

The --debug-session flag triggers src/session.rs to write a dcg.session.json file containing the raw input, normalized command, and matched rule ID.

echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' \
| dcg --debug-session > /dev/null
cat dcg.session.json

This file reveals whether the command reached the destructive-pattern engine and which specific rule triggered the denial.

3. Validate Input Against Test Schemas

Verify your JSON payload against the reference implementation in tests/codex_hook_protocol.rs, specifically the test_input_parsing test. Common mistakes include stray commas, single quotes instead of double, or missing nested fields under tool_input.

4. Inspect Stack Traces for Pack Errors

When adding custom packs, malformed YAML can cause panics. Run with RUST_BACKTRACE=1 to identify the specific location in src/packs/*.rs causing the failure.

5. Check Quick-Reject Filter Behavior

Review the QUICK_REJECT_SET definition in src/evaluator.rs if commands containing unexpected null bytes or binary data bypass evaluation entirely, leading to silent allows.

Practical Troubleshooting Examples

Reproducing a DCG-3001 JSON Parse Error

Send a payload missing the required command field to trigger the deserialization failure:

echo '{"tool_name":"Bash","tool_input":{}}' | RUST_LOG=debug dcg

Expected Output:

Log: ERROR hook::parse_input: JSON parse error: missing field 'command'

Exit Code: 1

JSON Output:

{
  "error": {
    "code": "DCG-3001",
    "category": "runtime",
    "message": "JSON parse error: missing field `command`",
    "context": {}
  }
}

Diagnosing Truncated JSON from Regex Panics

Introduce an invalid regex (e.g., (?<unclosed) in the destructive! macro within src/evaluator.rs, then rebuild:

cargo build --release
echo '{"tool_name":"Bash","tool_input":{"command":"git reset --hard"}}' | dcg

Result: The process aborts mid-write, producing truncated output like { "hookSpecificOutput": { "hookEventName": "PreToolUse", followed by a panic dump. Correct the regex syntax and rebuild to resolve.

Using Debug Session Files

Generate a complete session trace to verify rule matching:

echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' \
| dcg --debug-session > /dev/null
cat dcg.session.json

Sample Output:

{
  "raw_input": "...",
  "normalized": "rm -rf /",
  "matched_rule": {
    "rule_id": "core.filesystem:rm-rf-root",
    "severity": "critical"
  },
  "decision": "deny"
}

This confirms the command successfully reached the evaluator and was blocked by the specific filesystem rule.

Key Source Files for Troubleshooting

File Role Critical Functions/Items
src/main.rs Entry point and panic handling std::panic::catch_unwind, CLI wiring
src/hook.rs JSON ingestion and validation hook::parse_input, serde_json::from_str
src/evaluator.rs Pattern matching engine destructive! macro, pattern! macro, QUICK_REJECT_SET
src/normalize.rs Command preprocessing Path stripping, alias expansion
src/output.rs Denial JSON construction HookSpecificOutput struct
src/session.rs Debug state persistence Session JSON serialization
src/config.rs Configuration loading config::load, toml_edit dependency
src/heredoc.rs Heredoc extraction 200ms timeout handling
src/cli.rs TTY and color control colored::control::set_override
tests/codex_hook_protocol.rs Input validation tests test_input_parsing

Summary

  • DCG-3001 errors indicate JSON deserialization failures in src/hook.rs, typically from missing tool_input.command fields or malformed syntax.
  • Runtime failures with non-zero exits or truncated output result from panics caught by main.rs, often originating in src/evaluator.rs regex compilation or src/config.rs loading.
  • Silent allows may indicate heredoc timeouts in src/heredoc.rs or quick-reject filter matches in src/evaluator.rs.
  • Use RUST_LOG=debug for real-time tracing and --debug-session to capture intermediate processing state in dcg.session.json.
  • Reference tests/codex_hook_protocol.rs for valid JSON schema examples when constructing hook payloads.

Frequently Asked Questions

Why does DCG exit with code 1 and produce no stdout?

This indicates a hook-runtime failure where main.rs catches a panic via std::panic::catch_unwind or encounters an I/O error before JSON serialization completes. Enable RUST_LOG=debug to identify whether the failure occurs during JSON parsing in src/hook.rs or configuration loading in src/config.rs (DCG-2002).

How do I fix malformed JSON denials that crash my parser?

Malformed output usually stems from uncaught panics after src/output.rs begins writing the denial JSON but before the buffer flushes. Check src/evaluator.rs for invalid regex patterns in the destructive! macro, and ensure any custom packs in src/packs/*.rs contain valid YAML syntax. Running with RUST_BACKTRACE=1 reveals the specific panic location.

Why is my destructive command being allowed when it should be blocked?

First, verify the command is not matching a whitelist pattern in the pattern! macro within src/evaluator.rs. If the command contains complex heredocs, check if the 200ms timeout in src/heredoc.rs is causing a fail-open. Alternatively, the command may contain binary data that triggers the QUICK_REJECT_SET filter, causing early exit. Use --debug-session to confirm whether the evaluator ever processed the command.

How do I enable detailed session tracking for debugging?

Run DCG with the --debug-session flag to generate a dcg.session.json file via src/session.rs. This file contains the raw stdin input, normalized command string, matched rule ID (if any), and final decision. Combine this with RUST_LOG=debug to correlate internal processing steps with the final output state.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →