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

> Debug why a command is blocked or allowed by destructive command guard dcg. Use dcg explain to see rule IDs and trace the evaluation pipeline step-by-step.

- Repository: [Jeff Emanuel/destructive_command_guard](https://github.com/Dicklesworthstone/destructive_command_guard)
- Tags: how-to-guide
- Published: 2026-07-16

---

**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`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/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`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/src/hook.rs) reads up to a configurable byte limit from stdin and parses the JSON.

```bash

# 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`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/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:

```rust
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`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/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`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/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`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/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:

```bash
$ 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:

```bash
$ 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`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/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`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/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`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/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`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/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`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/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`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/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.