Performance Budgets in dcg: How the 200ms Timeout Enforces Low Latency

The Destructive Command Guard (dcg) enforces a hard 200ms timeout on its PreToolUse hook to guarantee that command validation never introduces perceptible lag, failing open (allowing the command) if the deadline is exceeded.

The dcg (destructive_command_guard) repository provides a synchronous safety layer that inspects shell commands before execution in AI coding agents. Because this check blocks the command from running, the project defines strict performance budgets that each processing stage must respect to maintain a snappy user experience. These budgets culminate in a 200ms hard deadline that ensures the tool never becomes a bottleneck in the development workflow.

What Are Performance Budgets in dcg?

Performance budgets are maximum latency allocations assigned to specific stages of the command evaluation pipeline. Each budget targets a sub-millisecond or low-millisecond threshold to ensure the total processing time remains imperceptible to users.

The budgets are enforced across these key stages:

  • Quick-reject filter (< 50 µs): Implemented in src/evaluator.rs, this stage uses memchr and RegexSet to eliminate over 99% of harmless commands before expensive processing begins.
  • Fast-path normalization (< 500 µs): The src/normalize.rs module handles path stripping, alias expansion, and basic tokenization.
  • Pattern matching (< 1 ms): Also in src/evaluator.rs, compiled static regexes (via lazy_static!) classify commands as safe or destructive.
  • Heredoc extraction (< 2 ms): The src/heredoc.rs parser processes complex multi-line inputs only when necessary.
  • Full pipeline with AST matching (< 20 ms): The src/ast_matcher.rs module provides deep analysis for edge cases, validated by the benchmark suite in benches/heredoc_perf.rs and benches/hook_latency.rs.

How dcg Enforces the 200ms Timeout

The 200ms deadline represents the total time budget for the entire PreToolUse hook execution. If processing exceeds this limit, dcg implements a fail-open policy to prevent workflow disruption.

The enforcement mechanism follows four steps:

  1. Start timer: As soon as dcg reads the JSON hook input in src/hook.rs::run, it records Instant::now().
  2. Execute pipeline: The command flows through quick-reject, normalization, pattern matching, optional heredoc parsing, and suggestion scoring.
  3. Check elapsed time: Immediately before writing the final JSON denial in output/denial.rs, the code verifies:
if start.elapsed().as_millis() > HOOK_TIMEOUT_MS {
    // Fail-open: silently allow the command
    std::process::exit(0);
}
  1. Emit result: If under budget, the normal allow/deny decision outputs as JSON; otherwise, the process exits with code 0, allowing the command to run.

The constant HOOK_TIMEOUT_MS is defined in src/perf.rs as const HOOK_TIMEOUT_MS: u64 = 200;, centralizing the deadline configuration.

Why a 200ms Fail-Open Policy?

A defensive security tool that stalls the developer's workflow would quickly be disabled. The 200ms figure was chosen after profiling real-world workloads on Linux and Windows, sitting comfortably below the typical "thinking" latency of code-assistant UIs.

By failing open—allowing the command rather than blocking indefinitely—dcg balances safety (catching most dangerous commands quickly) with usability (never introducing noticeable lag). This guarantee ensures that even pathological inputs, such as deeply nested heredocs, cannot freeze the agent interface.

Validating Performance with Benchmarks

The project maintains rigorous performance validation through dedicated benchmark suites. The benches/hook_latency.rs file measures end-to-end hook latency to verify the < 200ms target, while benches/heredoc_perf.rs focuses on heredoc extraction and AST matching performance.

Developers can observe the internal timer in debug builds by running with RUST_LOG=debug to verify budget compliance:

// In src/hook.rs (debug builds only)
log::debug!("Hook elapsed: {} ms", start.elapsed().as_millis());

You can also test the timeout behavior directly:


# Normal safe command (fast path)

echo '{"tool_name":"Bash","tool_input":{"command":"git status"}}' \
| cargo run --release

# A deliberately complex heredoc that approaches the budget

cat <<'EOF' > /tmp/large_script.sh
$(yes "echo hello" | head -n 50000)
EOF

echo '{"tool_name":"Bash","tool_input":{"command":"bash /tmp/large_script.sh"}}' \
| cargo run --release

# If parsing the heredoc would exceed 200ms, dcg silently allows the command.

Summary

  • Performance budgets in dcg allocate specific latency limits (< 50 µs to < 20 ms) to each processing stage in src/evaluator.rs, src/normalize.rs, and src/heredoc.rs.
  • The 200ms timeout is enforced in src/hook.rs by checking Instant::elapsed() against HOOK_TIMEOUT_MS defined in src/perf.rs.
  • If the deadline is exceeded, dcg fails open by exiting with code 0, ensuring user workflow never stalls.
  • The benchmark suite in benches/hook_latency.rs validates that real-world processing stays well under the 200ms budget.

Frequently Asked Questions

What happens if a command takes longer than 200ms to evaluate?

If processing exceeds the 200ms limit defined in src/perf.rs, dcg immediately exits with code 0 and empty stdout, allowing the command to run. This fail-open policy ensures that complex heredocs or edge-case inputs never block the developer's terminal.

Why does dcg use a fail-open policy instead of blocking slow commands?

Blocking indefinitely would create a poor user experience and incentivize developers to disable the tool entirely. By failing open, dcg guarantees that its safety checks never introduce perceptible lag, maintaining a balance between security and workflow velocity.

Which file defines the 200ms timeout constant?

The timeout value is defined as const HOOK_TIMEOUT_MS: u64 = 200; in src/perf.rs. This constant is imported and checked in src/hook.rs right before the evaluation result is emitted.

How does the quick-reject filter achieve sub-50-microsecond performance?

The filter in src/evaluator.rs uses memchr and pre-compiled RegexSet patterns via lazy_static! to eliminate over 99% of harmless commands without invoking heavier normalization or AST parsing stages.

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 →