Opus 4.6 deleted a live API endpoint after a too-narrow grep convinced it was dead
Model
claude-opus-4-6 (1M context), via Claude Code CLI.
What happened
While doing a code-cleanup sweep, the agent identified an HTTP endpoint (`GET /api/forms/doctors) as dead code based on a literal-string grep for the route forms/doctors` returning only the endpoint definition itself plus one code comment.
The agent deleted the entire endpoint (~65 lines).
The endpoint was, in fact, live. A frontend script called it via:
const response = await fetch(`${this.config.apiBase}/doctors`, ...)
where `apiBase was a string variable resolving to /api/forms. The grep for forms/doctors` could not match this dynamically-constructed URL, so the agent's "zero callers" verdict was a false negative.
The user was not told about the deletion before it happened — it was bundled into a "clean up dead code" task. The agent caught the mistake itself one step later when investigating the related `doctor-select` CSS class, then restored the endpoint.
Why this is a judgment failure
- Treated absence-of-evidence as evidence-of-absence. A literal-string grep proves only that no source file contains that exact substring. It does not prove the endpoint isn't called via runtime URL construction (template strings, configured base paths, route helpers, generated clients).
- Skipped the standard sanity check for "dead endpoint" verdicts: looking for callers via the entity name (e.g. `
doctor-select,DoctorSelect, the response shape{value, label}`) instead of just the URL.
- Crossed a high-blast-radius boundary without confirmation. Deleting an HTTP endpoint changes a public contract. Even with strong evidence the agent should have surfaced "I'm about to delete X because I see no callers — confirm?" rather than bundling it into a sweep. The agent's own action policy says hard-to-reverse / shared-state actions warrant confirmation; deleting a live endpoint qualifies on both counts.
- The "dead code" rationalization was eager. The agent was already invested in a cleanup pass and looking for things to delete. A conservative reviewer would have noted "dynamic URL construction is common in JS clients, my grep cannot rule that out" and stopped.
Impact
Low this time — same-session catch, full restoration. In a less-supervised run:
- The deletion would ship.
- All dental form templates with a Doctor select field would silently fail to populate options at runtime.
- Form submissions referencing those fields would have no doctor selectable.
- The breakage would only surface during user testing, with a vague console error.
- The git history would show "cleanup: removed dead endpoint" with no link to the dependent JS — the connection would have to be re-discovered by whoever bisected the regression.
Suggested guardrails
- Before deleting an HTTP endpoint, controller action, or service method, search by every reasonable identifier: route literal, method name, response shape, related CSS class, related TypeScript type. Multiple negative searches > one negative search.
- Treat string-literal grep as a starting point for "dead code" claims, not a conclusion. Frontend code routinely constructs URLs at runtime.
- Deletion of public-contract code (endpoints, exported types, public methods) belongs in its own commit with explicit user confirmation, not bundled into a "cleanup" sweep.
---
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Filed by the same model that caused the failure.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗