# How dcg Detects and Handles Different AI Agent Protocols: Claude, Codex, Gemini, and More

> Discover how dcg detects and handles AI agent protocols like Claude, Codex, and Gemini. Learn how this PreToolUse hook inspects JSON, env vars, and provides specific denial responses.

- Repository: [Jeff Emanuel/destructive_command_guard](https://github.com/Dicklesworthstone/destructive_command_guard)
- Tags: internals
- Published: 2026-07-22

---

**`dcg` (Destructive Command Guard) acts as a PreToolUse hook that reads JSON from stdin, detects the originating AI agent by inspecting specific envelope fields and environment variables, then emits a protocol-specific denial response to stdout while printing human-readable warnings to stderr.**

`dcg` from the **Dicklesworthstone/destructive_command_guard** repository serves as a universal safety gate for AI coding assistants. To intercept potentially dangerous shell commands across disparate agent architectures, it must understand how each AI agent protocols structure their hook invocations. The system normalizes these varied inputs into a common evaluation pipeline while preserving wire-format compatibility for every supported backend.

## Understanding the Hook Input Structure

All supported agents transmit a JSON envelope on standard input. In [`src/hook.rs`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/src/hook.rs), the `HookInput` struct (lines 19-74) models every field used by Claude Code, Gemini, Codex, Copilot, Hermes, Grok, and Antigravity. This unified structure allows `dcg` to parse heterogeneous payloads without protocol-specific deserialization logic scattered throughout the codebase.

The struct captures fields such as `toolCall`, `event`, `tool_name`, `hook_event_name`, `turn_id`, and nested argument objects. By centralizing field definitions in a single location, the system maintains compatibility with evolving agent formats while providing a consistent internal representation for downstream evaluation.

## Protocol Detection Logic

The `detect_protocol` function in [`src/hook.rs`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/src/hook.rs) (lines 85-167) implements a priority-based inspection system. The order of checks matters because several agents share similar field names. The function returns a `HookProtocol` enum (lines 24-41) that drives all subsequent formatting decisions.

### Antigravity Detection

**Antigravity** protocol triggers when the envelope contains a `toolCall` field with nested `name` and `args` properties. This nested structure is unique to Antigravity's CLI (`agy`) invocation style.

### Hermes Detection

**Hermes** is identified when `hook_event_name == "pre_tool_call"` **or** `tool_name == "terminal"`. These specific string matches distinguish Hermes from other agents that might use similar hook event naming conventions.

### Grok Detection

**Grok** protocol matches when `hookEventName == "pre_tool_use"` **or** `toolName == "run_terminal_cmd"`. The function checks for these specific camelCase field names to differentiate Grok from Claude-compatible agents.

### Copilot Detection

**Copilot** uses a distinct `event` field set to `"pre-tool-use"` or contains `tool_args`. The presence of these specific fields indicates GitHub Copilot's hook invocation format.

### Codex Detection

**Codex** identification relies on shell-tool name validation (`bash`, `powershell`, `pwsh`, `launch-process`) combined with a non-empty `turn_id`. Additionally, any PowerShell tool name alone forces Codex detection, as Codex uses specific tool naming conventions for shell execution.

### Claude-Compatible Fallback

**Claude-compatible** serves as the default fallback when none of the above patterns match, or when the `CLAUDE_CODE` environment variable is set. This ensures backward compatibility with Claude Code's established hook interface.

### Gemini Detection

**Gemini** is recognized when `tool_name == "run_shell_command"` and `hook_event_name == "BeforeTool"`. The function also checks for any combination of Gemini-specific envelope fields to confirm the protocol.

## Command Extraction by Protocol

Once identified, the `extract_command_with_protocol` function (lines 73-104) extracts the actual shell command string using protocol-specific logic:

- **Antigravity**: Accesses `toolCall.args.CommandLine`
- **Standard payloads**: Reads `tool_input.command` or `tool_args.command`
- **JSON normalization**: Handles both JSON-object and JSON-string forms of tool arguments

This extraction occurs before the destructive pattern evaluator inspects the command content.

## Protocol-Specific Response Formatting

The `write_denial_to` function (lines 135-190) selects response shapes based on the detected `HookProtocol`. Each agent requires specific JSON schemas to properly interpret denial responses:

| Protocol | JSON Structure | Key Characteristics |
|----------|---------------|---------------------|
| **ClaudeCompatible** | `HookOutput` with `hookSpecificOutput` | Full ergonomics including `ruleId`, `packId`, `severity`, `remediation`, and `allowOnce*` fields |
| **Codex** | Minimal `HookOutput` | Only `hook_event_name`, `permission_decision`, and `permission_decision_reason`; extra fields omitted because Codex's strict parser discards unknown fields |
| **Copilot** | `CopilotHookOutput` | Top-level `permissionDecision` and `permissionDecisionReason` fields |
| **Gemini** | `GeminiHookOutput` | `decision`, `reason`, optional `systemMessage`, plus ergonomics |
| **Hermes** | `HermesHookOutput` | Both `decision`/`reason` and `action`/`message` pairs (Hermes accepts either format) plus ergonomics |

All human-visible messages are constructed by `format_denial_message` (lines 80-112) and rendered via `print_colorful_warning` (lines 64-78). For agents consuming only JSON payloads (Claude, Gemini, Grok, Hermes), the human-readable warning goes to **stderr** while the JSON response writes to **stdout**.

## Fail-Open Safety Mechanisms

`dcg` implements a "fail-open" philosophy required by several agents (notably Grok's handling of non-zero exit codes). If parsing fails (`HookReadError`) or the input does not represent a supported shell tool (`is_shell_hook_candidate`), the system returns an *Allow* result, permitting the command to execute rather than blocking it incorrectly.

## Practical Integration Examples

**Running under Claude Code (default):**

```bash
echo '{"tool_name":"Bash","tool_input":{"command":"git reset --hard"}}' \
 | dcg

# → JSON on stdout containing full HookOutput with ruleId "core.git:reset-hard"

# → Colored warning on stderr

```

**Using the Gemini client:**

```bash
echo '{
  "toolName":"run_shell_command",
  "hookEventName":"BeforeTool",
  "sessionId":"abc",
  "toolInput":{"command":"rm -rf /"}
}' | dcg

# → GeminiHookOutput JSON: {"decision":"deny","reason":"..."} plus ergonomics

```

**Antigravity CLI invocation:**

```bash
echo '{
  "toolCall":{"name":"run_command","args":{"CommandLine":"docker system prune"}}
}' | dcg

# → Hermes-compatible JSON because Antigravity maps to the Hermes protocol

```

**Codex CLI with turn ID:**

```bash
echo '{"tool_name":"Bash","tool_input":{"command":"git push -f"},"turnId":"12345"}' \
 | dcg

# → Minimal HookOutput (no ruleId etc.) as required by Codex's strict parser

```

**Copilot CLI payload:**

```bash
echo '{"event":"pre-tool-use","tool_args":{"command":"kubectl delete namespace prod"}}' \
 | dcg

# → CopilotHookOutput: {"permissionDecision":"deny","permissionDecisionReason":"..."}

```

## Summary

- `dcg` detects **seven distinct AI agent protocols** by inspecting specific JSON envelope fields in [`src/hook.rs`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/src/hook.rs) using the `detect_protocol` function (lines 85-167).
- The system extracts commands via `extract_command_with_protocol` (lines 73-104), handling nested structures like Antigravity's `toolCall.args.CommandLine` and standard `tool_input.command` fields.
- Response formatting varies by protocol: **Claude** receives full ergonomic metadata, **Codex** receives minimal JSON to avoid parser errors, and **Copilot**, **Gemini**, and **Hermes** each receive protocol-specific envelope structures via `write_denial_to` (lines 135-190).
- Human-readable warnings print to **stderr** while machine-readable JSON responses write to **stdout**, ensuring compatibility with both human users and agent parsers.
- The **fail-open** design ensures that parsing errors or unsupported tool invocations result in command allowance rather than denial, maintaining agent functionality during edge cases.

## Frequently Asked Questions

### What happens if dcg cannot identify the AI agent protocol?

If `detect_protocol` cannot match any specific protocol signatures and the `CLAUDE_CODE` environment variable is not set, `dcg` defaults to the **Claude-compatible** protocol. This fallback assumes the standard Claude Code envelope format, ensuring the tool remains functional even when encountering unknown or future agent variants.

### Why does Codex receive a minimal response compared to Claude?

Codex uses a strict JSON parser that **discards unknown fields**, causing hook failures if extra metadata is present. According to the source code in [`src/hook.rs`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/src/hook.rs) (lines 135-190), Codex responses intentionally omit ergonomic fields like `ruleId`, `packId`, and `remediation`, including only `hook_event_name`, `permission_decision`, and `permission_decision_reason` to ensure successful parsing.

### How does dcg handle Antigravity's nested toolCall structure?

Antigravity sends commands in a deeply nested JSON structure under `toolCall.args.CommandLine`. The `extract_command_with_protocol` function specifically checks for this path when the Antigravity protocol is detected, extracting the command string while mapping Antigravity's output format to the **Hermes** protocol for response generation.

### Can dcg be used with custom or proprietary AI agents?

Yes, provided the custom agent implements one of the supported envelope formats. The system is designed to accept any JSON matching the `HookInput` structure in [`src/hook.rs`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/src/hook.rs). If the custom agent follows Claude Code's format (the default fallback), no additional protocol detection logic is required. For agents with unique field requirements, extending the `detect_protocol` function and adding corresponding response structures in [`src/hook.rs`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/src/hook.rs) would be necessary.