How to Debug Why a Command Is Being Blocked or Allowed by dcg

Use dcg explain "<command>" to see the exact rule ID and denial reason, or trace the evaluation pipeline through eight specific stages from hook input to final JSON response.

The dcg (destructive_command_guard) crate from Dicklesworthstone/destructive_command_guard intercepts shell commands from AI coding agents and evaluates them against a curated set of safety patterns. When a command behaves unexpectedly—either blocking legitimate operations or allowing risky ones—you can debug the decision by stepping through the evaluation pipeline defined in the Rust source.

Understanding the dcg Evaluation Pipeline

Every command passes through a deterministic eight-stage pipeline implemented across src/hook.rs and supporting modules:

  1. Hook Input Parsing – hook::read_hook_input deserializes stdin JSON into HookInput with a configurable byte limit.
  2. Protocol Detection – hook::detect_protocol inspects fields like event, tool_name, hookEventName, and turn_id to identify the client (Claude, Copilot, Gemini, etc.).
  3. Command Extraction – hook::extract_command_with_protocol pulls the shell string from toolCall.args.CommandLine, tool_input.command, or tool_args.command depending on the protocol.
  4. Fast-Reject Filter – quick_reject::is_quick_reject performs substring checks to allow > 99 % of benign commands without regex overhead.
  5. Normalization – normalize::normalize_command strips absolute paths (e.g., /usr/bin/git → git) and expands aliases for stable matching.
  6. Pattern Evaluation – evaluator::evaluate checks against SAFE_PATTERNS (whitelist) and DESTRUCTIVE_PATTERNS (blacklist), returning a MatchResult with pack_id, pattern_name, and MatchSpan.
  7. Denial Formatting – hook::format_denial_message builds the human-readable explanation and rule ID (e.g., core.git:reset-hard).
  8. Response Serialization – hook::write_denial_to emits the JSON payload (permissionDecision: "deny") to stdout and a colorized warning to stderr via print_colorful_warning_to.

When a command is allowed, the process exits silently with code 0 and no output. When blocked, stage 8 produces a structured JSON response containing ruleId, severity, confidence, and remediation fields.

Debugging a Blocked Command Step by Step

If dcg blocks a command you expect to be safe, trace through these verification points in the source code.

Inspect the Raw Hook Input

Start by verifying what dcg actually received. The hook::read_hook_input function in src/hook.rs reads up to a configurable byte limit from stdin and parses the JSON.


# Simulate hook input for debugging

echo '{"toolName":"Bash","toolInput":{"command":"git reset --hard HEAD"}}' | dcg explain "git reset --hard HEAD"

If the JSON structure differs from what the extractor expects, extract_command_with_protocol will fail to locate the command string.

Verify Protocol Detection

Check which HookProtocol variant hook::detect_protocol selected. The function examines field presence to distinguish between Claude, Gemini, Copilot, Codex, Hermes, Grok, and Antigravity protocols. An incorrect detection causes extract_command_with_protocol to look in the wrong JSON path (e.g., toolCall vs. tool_input).

Check Command Extraction

Confirm which extractor succeeded. The hook::extract_command_with_protocol function delegates to:

  • extract_command_from_tool_call (Copilot/Codex)
  • extract_command_from_tool_input (Claude)
  • extract_command_from_tool_args (Legacy formats)

If the command appears in the JSON but extraction returns None, the protocol detection likely selected the wrong variant.

Review the Quick-Reject Filter

Before regex evaluation, src/quick_reject.rs runs is_quick_reject to filter obviously safe commands. If your command contains suspicious substrings but should be allowed, it may be passing this filter when it should not, or vice versa. This stage has no logging by default, so you must test manually:

use dcg::quick_reject::is_quick_reject;

let cmd = "git status";
if is_quick_reject(cmd) {
    println!("Command bypassed regex evaluation");
}

Examine Normalization

The normalize::normalize_command function in src/normalize.rs strips binary paths and canonicalizes aliases. If your pattern expects git checkout but the input is /usr/local/bin/git checkout, normalization ensures matching still succeeds. Debug the normalized form to verify pattern alignment.

Analyze Pattern Matching Results

The core logic resides in src/evaluator.rs. The evaluate function returns a HookResult::Deny containing:

  • pack: The pattern pack ID (e.g., core.git)
  • pattern_name: The specific rule (e.g., reset-hard)
  • severity: Critical, High, Medium, or Low
  • confidence: Float between 0.0 and 1.0

If evaluate returns HookResult::Allow, the command matched a SAFE_PATTERNS entry or triggered no DESTRUCTIVE_PATTERNS hits.

Inspect the Denial Output

Finally, hook::write_denial_to constructs the final payload. For blocked commands, it prints a colorized warning to stderr showing the rule ID and a "Tip: dcg explain ..." hint, while stdout receives the JSON for the AI client. Check src/output/denial.rs for the DenialBox rendering logic if the terminal output appears malformed.

Using the dcg explain CLI Tool

The fastest way to debug is the built-in explain command, which runs stages 1‑7 and prints the denial message without requiring a full IDE hook setup:

$ dcg explain "rm -rf /"
BLOCKED by dcg

Tip: dcg explain "rm -rf /"

Reason: rm -rf / destroys system directories

Explanation: Recursive deletion of root filesystem.
Rule: core.filesystem:rm-rf-root

Command: rm -rf /

If this operation is truly needed, ask the user for explicit permission and have them run the command manually.

The Rule field (e.g., core.git:reset-hard) corresponds to the pack_id and pattern_name returned by the evaluator.

Resolving False Positives with Allowlists

If a block is incorrect, add the rule ID to your project allowlist using the CLI:

$ dcg allowlist add core.git:reset-hard --project
Allowlist entry added for rule core.git:reset-hard (project scope)

This modifies the allowlist storage managed by src/allowlist.rs. You can also report false positives via the link printed in the warning footer, which points to the GitHub issue template.

Summary

  • Trace the pipeline: Input → Protocol → Extraction → Quick-Reject → Normalization → Evaluation → Output.
  • Use dcg explain: Instantly reveals which DESTRUCTIVE_PATTERNS entry triggered the block and displays the ruleId.
  • Check src/evaluator.rs: The evaluate function returns MatchResult with pack_id and pattern_name for precise identification.
  • Normalize first: Commands are stripped of paths in src/normalize.rs before pattern matching.
  • Allowlist locally: Use dcg allowlist add <ruleId> --project to override specific rules without modifying source packs.

Frequently Asked Questions

How do I see exactly which rule blocked my command?

Run dcg explain "<command>" in your terminal. The output includes a Rule field formatted as pack_id:pattern_name (e.g., core.git:reset-hard), which corresponds to the DESTRUCTIVE_PATTERNS definition in src/packs/mod.rs.

Why was my safe command allowed immediately without evaluation?

The command likely triggered the fast-reject filter in src/quick_reject.rs. The is_quick_reject function uses simple substring checks to allow benign commands (like git status) without invoking the regex engine, improving performance for the common case.

Can I debug the hook pipeline without installing the IDE extension?

Yes. Pipe JSON directly into dcg via stdin to simulate the hook environment. The hook::read_hook_input function reads from stdin, so you can test protocol detection and extraction locally without triggering the actual IDE integration.

Where are the destructive patterns defined?

Destructive patterns are defined in src/packs/mod.rs using the destructive! macro, while safe patterns use the SAFE_PATTERNS macro. Each pattern belongs to a pack (e.g., core.git, core.filesystem) and includes metadata for severity, confidence, and remediation suggestions.

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 →