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.commandortool_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
dcgdetects seven distinct AI agent protocols by inspecting specific JSON envelope fields insrc/hook.rsusing thedetect_protocolfunction (lines 85-167).- The system extracts commands via
extract_command_with_protocol(lines 73-104), handling nested structures like Antigravity'stoolCall.args.CommandLineand standardtool_input.commandfields. - 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →