Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

debugging-with-probes

Stop guessing at bugs. Make the running program tell you what it actually did.

A Claude Code skill + command that turns "why is this happening?" into a short evidence loop: the agent writes temporary instrumentation into your code, you reproduce the bug once, and it reads structured runtime data instead of re-reading the source and hoping.

Works with any agent harness that supports Agent Skills — Claude Code, and anything else that reads SKILL.md.


Why

Coding agents are good at reading code and bad at knowing what code did. Given a heisenbug they will read the file, form a confident theory, apply a plausible fix, and tell you it's solved. Sometimes it is.

This skill replaces that with: hypotheses → probes → one reproduction → facts → fix → verified cleanup.

The interesting part isn't the prompt. It's the plumbing that makes runtime data reachable at all — including from a browser tab, a container, or a remote host that can't see your filesystem.

Install

As a plugin (recommended):

/plugin marketplace add MrErikCodes/debugging-with-probes
/plugin install debugging-with-probes@debugging-with-probes

Manually:

git clone https://github.com/MrErikCodes/debugging-with-probes.git
cp -r debugging-with-probes/skills/debugging-with-probes ~/.claude/skills/
cp debugging-with-probes/commands/debug.md ~/.claude/commands/

Then either say "debug the checkout total, it's stale after a qty change" — the skill auto-triggers on debugging language — or invoke /debug <symptom> explicitly.

/debug also exists as a built-in Claude Code command for diagnosing Claude Code itself. A user- or plugin-level command of the same name shadows it. If you'd rather keep both, rename commands/debug.md to commands/probe.md and use /probe.

How it works

hypotheses ──► probes inserted ──► REPRODUCTION STEPS, agent stops
                                            │
                                     you reproduce once
                                            │
              .claude/debug-<sid>.log ◄─────┘
                       │
                  agent reads ──► narrow / re-probe ──► fix ──► cleanup verified

Two transports, chosen per target:

Code under test runs Transport
Browser, container, VM, remote host, CI, mobile Local HTTP ingest server (ingest-server.js)
Local process with filesystem access Direct file append
Mixed (e.g. Next.js SSR + client) Both, interleaved into one timeline

Either way the output is the same append-only NDJSON at .claude/debug-<sid>.log:

{"ts":"2026-07-29T17:52:03.123Z","sid":"a1b2c3","loc":"src/cart.ts:42","tag":"recalc-enter","data":{"qty":0,"total":249.0},"seq":17}

Probe emitters for TypeScript/JavaScript (Node + browser), Python, Go, Rust, plus a language-agnostic recipe, are in emitters.md.

The ingest server

Zero dependencies, stdlib Node only.

node skills/debugging-with-probes/ingest-server.js --sid a1b2c3 --repo .
# {"endpoint":"http://127.0.0.1:7874/ingest/b1d011d8-…","logPath":".claude/debug-a1b2c3.log","port":7874,"sid":"a1b2c3"}
  • Binds 127.0.0.1, auto-allocates a port in 7242–7942
  • Single route POST /ingest/<random-uuid>; the UUID is a capability token, everything else 404s
  • Requires an X-Debug-Session-Id header matching the session — 400 if malformed, 403 if it's another session
  • Access-Control-Allow-Origin: *, so instrumented browser code can post to it
  • Streams the body to disk through a serialized append queue and force-terminates every write with \n, so a client that forgets its trailing newline can't corrupt the log

Cleanup is the hard part

Instrumentation that survives the fix is the failure mode of this whole technique. It gets committed. It ships. It leaks values into production logs.

So every probe call site carries a sentinel:

dbg('src/cart.ts:42', 'recalc-enter', { cartId, qty }); // [dbg:a1b2c3]

which makes removal mechanical rather than remembered:

rg "dbg:a1b2c3"        # must return zero matches before the work is done
rm .claude/debug-a1b2c3.log

The skill treats a non-empty result as a blocker and carries a red-flags table for the usual rationalizations ("I'll leave the logging in, it's useful", "I'll clean it up in a follow-up", "the diff looks clean to me").

Prior art

The architecture is modelled on Cursor's Debug Mode, reverse-engineered from a local install (3.12.29). Cursor ships a bundled cursor-ndjson-ingest extension that serves the same purpose — localhost NDJSON server, workspace-sticky port in 7242–7942, session-scoped log at .cursor/debug-<sid>.log, CORS open for browsers — and passes {log_path, server_endpoint, session_id} to its model as a protobuf alongside a UNIFIED_MODE_DEBUG flag. Their agent signals readiness by emitting a <reproduction_steps> block, which the IDE renders with Proceed / Mark as fixed buttons.

Two deliberate departures:

  • Verifiable cleanup. Cursor relies on the model remembering to strip instrumentation, and its UI actively hides .cursor/debug-*.log from the diff and checkpoint system. The [dbg:<sid>] sentinel here makes leftover probes greppable.
  • No IDE required. The reproduction handshake is a plain text block and a hard stop, so it works in a terminal agent with no custom UI.

Cursor's actual system prompt is server-side and is not reproduced here — none of it appears in the client. This is a clean-room reimplementation of the mechanism from its observable protocol.

Contributing

Issues and PRs welcome. Useful directions:

  • Emitters for languages not yet covered
  • Probe placement heuristics that measurably beat the current guidance
  • Pressure-tested wording for the cleanup section

License

MIT — see LICENSE.

About

Claude Code skill + /debug command that finds a bug's root cause from real runtime data — temporary NDJSON probes, a localhost ingest server for browser/container/remote targets, and verifiable cleanup.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages