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

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, 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 (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):

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:

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

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

Antigravity CLI invocation:

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:

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:

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 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 (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. 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 would be necessary.

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 →