Skip to content
Agentic Control Plane

Your control plane speaks Claude Code. Your agents don't.

David Crowe David Crowe · · 4 min read
agentic-control-plane coding-agents deepseek-harness incident-writeup
Share X HN LinkedIn

Run agents? ACP lets you see, control, and price every tool call they make — free on-device install for Claude Code, Cursor, Codex, and OpenClaw, nothing leaves your machine:

curl -sf https://agenticcontrolplane.com/install.sh | bash -s -- --local

Getting started →  ·  or use the free cloud console →  ·  free for individuals

While wiring the DeepSeek Harness plugin this week, the first live test sent this through our gateway:

{ "tool_name": "bash", "tool_input": { "command": "rm -rf / --no-preserve-root" } }

The answer came back allow.

Our hardline floor — the unconfigurable layer that blocks recursive root deletes, filesystem formats, and fork bombs no matter what any policy says — never fired. Neither did per-binary command classification, domain extraction, exfiltration detection, or any Bash.* policy tier. The call evaluated correctly at the generic tool tier and skipped everything that knows what a shell is.

The bug is one character of case. Claude Code spells its shell tool Bash. DeepSeek Harness spells it bash.

The class, not the incident

Every layer that made our gateway smart about shell commands was keyed on Claude Code’s tool names — Bash, Read, Write, Edit, WebFetch. Reasonable when Claude Code was the only harness we governed. Then came Codex, Cursor, Hermes, opencode, and now dsh, and each spells its tools differently: bash, run_shell_command, run_terminal_cmd, str_replace_editor, web_fetch.

Here’s the part worth being honest about: nothing failed loudly. A lowercase bash call wasn’t rejected — it was governed generically. Policy still ran, the audit row still wrote, denies still denied. Everything looked fine in the dashboard. The name-specific intelligence just silently didn’t apply. A coverage gap that presents as normal operation is the worst kind, because no error budget, no alert, and no user report will ever surface it. Ours surfaced only because we ran the harness’s real spellings against production and checked what the hardline floor did with them.

The fix is one table, applied once

The tempting fix is per-client: teach the dsh plugin to send Bash, teach the opencode plugin to send Bash, and so on. That fixes two harnesses and guarantees the next one reopens the gap. It also makes the audit trail lie — the harness didn’t call a tool named Bash.

The right fix was one conservative alias table at the entry to classification, where every consumer — hardline floor, classifiers, policy tiers, both governance endpoints — already reads:

const HARNESS_TOOL_ALIASES: Record<string, string> = {
  bash: "Bash", pwsh: "Bash", shell: "Bash",
  run_shell_command: "Bash", run_terminal_cmd: "Bash",
  read: "Read", write: "Write", edit: "Edit",
  str_replace_editor: "Edit", apply_patch: "Edit",
  web_fetch: "WebFetch", web_search: "WebSearch",
  // exact matches only — unknown names pass through untouched
};

Exact matches only, deliberately. A fuzzy rule that “helpfully” canonicalizes a tenant’s unrelated custom tool named bash_helper would apply shell-grade policy to something that isn’t a shell. Unknown names keep today’s generic treatment, and policy keys written against native spellings keep working because the lookup falls back to the original name.

One change, every harness covered — including the ones that don’t exist yet, the day someone adds one line to a table instead of re-auditing five layers. The repro that opened this post now comes back deny with the hardline reason attached, and it did before the harness plugin ever shipped to a user.

If you run any name-keyed control layer

This isn’t about our gateway. The same shape lives in every allowlist, SIEM rule, seccomp-style tool filter, and prompt-injection scanner that matches on tool names: the rules encode the dialect of the first agent you governed. Three questions to ask of yours:

  1. What happens to a name your rules don’t match? If the answer is “generic handling” rather than “loud unknown,” you have silent partial coverage by construction.
  2. Have you tested with each harness’s real spellings? Not the docs — the actual wire traffic. Our gap survived until we sent dsh’s genuine payloads at production.
  3. Where does the dialect get normalized? If the answer is “in each client,” every new client is a chance to forget. Normalize once, at the choke point, and record both the native name and the canonical one.

The general principle: a control layer keyed on names is only as covered as its name table is current, and name tables rot silently. Test with real dialects, normalize at the choke point, and treat “we’ve never seen that name” as a signal, not a shrug.

The full coverage picture per harness — what intercepts, what can ask, what can rewrite — lives on the harness coverage matrix.

Share X HN LinkedIn
Get the next data drop
What agents actually cost, new tool-surface captures, and the occasional incident post-mortem — sent when we publish something worth your inbox, not on a schedule. Unsubscribe anytime.
Share: Twitter LinkedIn
Related posts

← back to blog