agentleFS
kenjichristopherv-delkenjichristopherv-del/debugmaxxing/skills/debuggable-code/SKILL.md

debuggable-code

Skill0 starsChanged 3 months agoApache-2.0View on GitHub

Patterns that make code diagnosable at 3am, applied while writing or modifying it rather than after an incident. Use this skill whenever writing or editing error handling, `try`/ `catch`/`except` blocks, promise rejection handling, logging, error messages, API error responses, retries, timeouts, fallbacks and default values, background jobs, or any boundary that parses or validates external data. Also use when the user asks to 'add logging', 'add error handling', 'make this more robust', 'clean up these logs', or 'add observability'. Trigger on 'catch', 'swallow', 'silent failure', 'log', 'logger', 'console.log', 'error message', 'stack trace', 'correlation id', 'tracing', 'assert'. Supplies the pattern that turns a future two-hour investigation into a two-minute one.

  • Reads credentials

The file

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 4 are distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    If you catch, either recover meaningfully or re-throw with context added.

    line 79
  2. Distinctive:

    Prefer one obvious source of truth for a piece of state over derived copies that can disagree.

    line 158
  3. Distinctive:

    Keep reducers and business logic pure and separated from I/O — a pure function can be reproduced in a test with the exact inputs from the …

    line 160
  4. Distinctive:

    When fixing any bug, the fix is not finished until there is a test that fails without it.

    line 167

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Debuggable Code

Most debugging time is not spent fixing. It is spent finding out what happened, and that is decided by choices made when the code was written. This skill is the set of choices that make the next failure explain itself.

The one rule

Every failure should tell you what it was doing, what it got, and where.

A failure that produces Error: undefined is not a function and a stack ending in minified vendor code has destroyed the information you needed. A failure that produces Failed to sync invoice inv_8812 for tenant acme: expected line_items to be an array, got null (source: /v2/invoices response, request_id=req_9f3c) has already done the debugging.

Error messages

Include the operation, the actual value, and the identifier. Not the expectation alone.

// ✗ Tells you nothing you didn't know from the stack trace
throw new Error("Invalid input");
if (!user) throw new Error("User not found");

// ✓ Names the operation, the value, and the identity
throw new Error(`parseWebhook: expected event.type to be a string, got ${typeof raw.type} (${JSON.stringify(raw).slice(0, 200)})`);
if (!user) throw new NotFoundError(`user ${userId} not found (tenant=${tenantId})`);

Rules:

  • Include the value that was wrong, truncated and redacted. got null, got "2024-13-45", got array of length 0. Never log secrets, tokens, passwords, full card numbers, or bulk PII — log the shape, the length, the last four, or a hash.
  • Include the identifier that lets someone find the record: user, tenant, order, job, request.
  • Say what was being attempted, in the caller's vocabulary. while applying coupon SUMMER24 beats in handler.
  • Preserve the cause chain. throw new Error("failed to load config", { cause: err }) in JS, raise ConfigError(...) from err in Python, fmt.Errorf("load config: %w", err) in Go. An error that discards the original loses the only stack frame that mattered.
  • Make messages greppable. A stable, unique prefix or code (E_SYNC_SHAPE) means the log search finds every occurrence and the code search finds the one line that raised it.

Never swallow an error

The single most expensive pattern in software. It converts a loud, local, five-minute bug into a silent, distant, five-hour one.

// ✗ The bug still happened; you have deleted the evidence
try { await sync(); } catch (e) {}
try { await sync(); } catch (e) { console.log("sync failed"); }
const data = await fetchData().catch(() => null);

// ✓ Handle deliberately, or let it propagate — either way, keep the error
try {
  await sync();
} catch (err) {
  // Deliberate: a stale cache is acceptable here, a crashed request is not.
  logger.warn({ err, invoiceId, tenantId }, "invoice sync failed, serving cached copy");
  metrics.increment("invoice.sync.failed", { reason: classify(err) });
}
  • If you catch, either recover meaningfully or re-throw with context added. "Log and continue" is only correct when you can state why continuing is safe — write that reason in a comment.
  • Never catch broader than you handle. except Exception around a block that only expects a network timeout will also swallow the TypeError in your own code.
  • Never use a fallback value to hide a failure. ?? 0, || [], ?? "unknown" are fine for genuinely optional data and catastrophic for required data — they turn a crash into a wrong number in a report that nobody notices for a quarter.
  • Handle rejected promises. An unawaited async call in a sync context fails invisibly. Wire up unhandledRejection / uncaughtException handlers that log and exit rather than continuing in an unknown state.

Fail fast at boundaries

Validate external data where it enters, once, and reject loudly. Every layer that passes a malformed value along increases the distance between cause and symptom.

// ✓ Parse, don't just cast. The error names the field and the payload.
const parsed = InvoiceSchema.safeParse(await res.json());
if (!parsed.success) {
  throw new Error(`invoice ${id}: response failed schema — ${parsed.error.issues.map(i => `${i.path.join(".")}: ${i.message}`).join("; ")}`);
}
  • as SomeType in TypeScript is a promise to the compiler, not a check. At an I/O boundary, parse (zod/valibot/io-ts, pydantic, serde, a hand-written validator) — anything that fails where the bad data arrived rather than three modules later.
  • Boundaries that deserve parsing: HTTP request bodies, third-party API responses, queue messages, webhook payloads, files, env vars at startup, database rows with JSON columns, localStorage, and URL params.
  • Validate config at boot, not at first use. A missing env var should stop the process with FATAL: STRIPE_SECRET_KEY is not set on line one, not produce a 500 at 4pm on a Friday.
  • Assert invariants you believe are impossible. invariant(items.length > 0, "cart empty after checkout validation"). When the impossible happens, it names itself.

Logging that survives contact with production

  • Structured, not interpolated. logger.info({ orderId, tenantId, durationMs }, "order placed") — queryable fields beat a sentence you have to regex.
  • Attach a correlation/request ID to everything and propagate it across service calls and into the client's error UI. Without it, a distributed failure is unreconstructable. With it, one search returns the whole story.
  • Log at the decision points, not everywhere: what was chosen, why, and the inputs to the choice. A log that says which branch was taken beats twelve that say a function was entered.
  • Levels mean things. error = someone must look; warn = degraded but handled; info = business events; debug = mechanism. If everything is error, nothing is.
  • Log the failure with its inputs, so the log alone reproduces the case.
  • Don't log secrets or PII. Redact at the logger, not at each call site, so one forgotten call site can't leak.
  • Every retry gets a log line with attempt number and the error that caused it. Silent retries hide a service that is failing 40% of the time until it fails 100%.

Timeouts, retries, and background work

  • Every network call gets a timeout. A call with no timeout doesn't fail — it hangs, and hangs are much harder to diagnose than errors.
  • Retries need a cap, backoff, jitter, and idempotency. Retrying a non-idempotent write is a data bug waiting to happen. Log the final failure with the full attempt history.
  • Background jobs and cron must report both success and failure. A job that silently stops running is invisible until someone asks why the numbers are stale. Emit a heartbeat and alert on its absence.
  • Make async work traceable — carry the correlation ID onto the queue, and log the enqueue and the dequeue with the same ID.

Frontend specifics

  • Error boundaries that report, not just ones that render "Something went wrong." Send the component stack and the correlation ID somewhere you can read it.
  • Keep source maps for production builds (uploaded to the error reporter, not served publicly if the source is sensitive). A minified stack trace is a debugging dead end.
  • Show the error identity to the user, minimally: "Something went wrong (ref: req_9f3c)". That string is the difference between a reproducible report and "it broke."
  • Surface failed requests in the UI. A catch that leaves a spinner spinning forever is the frontend's version of swallowing an error.
  • Name things so React DevTools is readable — named function components, displayName on wrapped components, meaningful keys. Anonymous trees are unreadable when it matters.

Make state inspectable

  • Prefer one obvious source of truth for a piece of state over derived copies that can disagree. Most "wrong value on screen" bugs are two sources of truth drifting apart.
  • Keep reducers and business logic pure and separated from I/O — a pure function can be reproduced in a test with the exact inputs from the log.
  • Store enough to reconstruct: the ID and timestamp of what produced a value, not just the value. "Where did this number come from" should be answerable.

The test that would have caught it

When fixing any bug, the fix is not finished until there is a test that fails without it. When writing new code, the equivalent is: write the test for the failure mode you can already imagine — the empty list, the duplicate submit, the expired token, the timezone boundary, the second concurrent request. Those are the bugs that ship.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.