Production Debugging skill for Codex

Find the fault in live systems without adding a new one. Codex treats production as read-only until you approve each mutating step, proposes mitigation first when users are affected, and narrows the problem with change history and telemetry before touching a process. When it must look inside, it picks the least intrusive tool (thread dumps, py-spy, pprof, ephemeral debug containers) on one drained instance and keeps a timestamped log for the postmortem.

  • Observability & SRE
  • $production-debugging
  • 141 lines
  • 2 files
  • ~1,800 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- production-debugging

Installs to ~/.agents/skills/production-debugging/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $production-debugging, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Debugs live production systems without making things worse - read-only evidence first from metrics, logs and traces, then low-overhead live inspection (thread dumps, py-spy, async-profiler, pprof, ephemeral debug containers, ss and lsof), dynamic log levels and feature flags, with every mutating step gated on explicit approval and logged. Use when something is broken or slow in production and cannot be reproduced locally, when you need a thread dump, profile or heap evidence from a running process, or when choosing between mitigating now and investigating further.

Use it when

  • A live system is broken or slow and does not reproduce locally
  • You need evidence from a running process: threads, profiles, sockets
  • You must choose between mitigating now and investigating further
  • Temporary debug levers need to be applied and reliably reverted

Use something else when

How to check the result

The signal that showed the problem returns to baseline for a full traffic cycle, and every temporary lever (log level, debug container, cordon, flag) is confirmed reverted.

The mistake it prevents

Restarting pods to see if it helps, which destroys the in-memory evidence and often only postpones the failure.

~/.agents/skills/production-debugging/SKILL.md

--- name: production-debugging description: Debugs live production systems without making things worse - read-only evidence first from metrics, logs and traces, then low-overhead live… license: MIT ---

Done well means: users are protected first (mitigate before diagnosing when impact is ongoing), evidence is gathered with the least intrusive tool that answers the question, nothing in production changes without explicit approval, and every action taken is written down with a timestamp.

Ground rules

  • Read-only by default. Querying metrics, logs, traces and kubectl get/describe/logs is fine. Restarts, scaling, config changes, flag flips, kubectl exec into a production container, attaching debuggers, heap dumps and database writes each require the user’s explicit go-ahead for that specific action.
  • Never paste secrets, tokens or customer data from production into chat, tickets or commits. Summarise or redact.
  • Prefer mitigation over cleverness during user impact: rollback, failover, disable the feature flag, shed load. Diagnose afterwards with the evidence you preserved.
  • Keep a running log (time, command, result) in the incident channel or a scratch file; it becomes the postmortem timeline.
  • In a Codex sandbox, production access usually needs network and credentials that may be unavailable; say so and hand the user exact commands to run instead of guessing.

Workflow

  • 1. State the symptom precisely: what, since when, who is affected, how measured
  • 2. Check what changed: deploys, config, flags, traffic, dependencies, certificates, cron
  • 3. Decide: is impact ongoing and is there a safe mitigation? If yes, propose it now
  • 4. Narrow with telemetry: which service, endpoint, instance, region, tenant
  • 5. Inspect a live process only if telemetry cannot answer, with the least intrusive tool
  • 6. Form one hypothesis, predict what you should see, test it
  • 7. Fix or mitigate with approval; verify with the same signal that showed the problem
  • 8. Preserve evidence and timeline for the postmortem

Narrowing with telemetry

# what changed
git log --since="6 hours ago" --oneline origin/main
kubectl rollout history deploy/checkout -n prod
kubectl get events -n prod --sort-by=.lastTimestamp | tail -30

# is it one instance or all?
kubectl top pods -n prod -l app=checkout
kubectl get pods -n prod -l app=checkout -o wide       # same node? same zone?

Questions that split the space fastest:

QuestionIf yes
Started exactly at a deploy?Roll back first; diff the release
Only some instances/nodes/zones?Instance-level: noisy neighbour, bad node, config drift; cordon or replace
Only some tenants/endpoints?Data-dependent: specific input, large account, hot key
Correlates with traffic?Capacity: saturation, pool exhaustion, rate limits
Correlates with a dependency’s latency?Downstream: check its dashboards and status, timeouts and retries
Periodic?Cron, batch jobs, GC cycles, cache expiry, certificate or token refresh

Live inspection, least intrusive first

NeedToolOverhead and risk
What is every thread doing (JVM)jcmd <pid> Thread.printBrief safepoint pause; safe
CPU hot spots (JVM)async-profiler: asprof -d 30 -e cpu -f /tmp/cpu.html <pid>Low; needs the binary in the container
What is Python doingpy-spy dump --pid <pid>; py-spy record -d 30 -o /tmp/p.svg --pid <pid>Low; needs SYS_PTRACE capability
Go goroutines / CPU / heapcurl -s localhost:6060/debug/pprof/goroutine?debug=2; go tool pprof http://.../profile?seconds=30Low, if pprof endpoint exists on an internal port
Node.js--cpu-prof on a canary, or kill -USR1 <pid> to open the inspector (local port only)Inspector pauses on breakpoints; never on all replicas
Open connections, socketsss -tanp, ss -s, lsof -p <pid> | wc -lSafe
Syscallsstrace -f -p <pid> -c for a few secondsHigh overhead; seconds only, one instance
Kernel-level latencybpftrace/perfLow but needs privileges; platform team approval
Heap dumpjcmd <pid> GC.heap_dump /tmp/h.hprof, gcorePauses the process for seconds to minutes and writes a file the size of the heap, which contains customer data

On Kubernetes, use an ephemeral debug container instead of installing tools in the app image (requires approval; it attaches to a live pod):

kubectl debug -n prod -it pod/checkout-7d9f -c debugger \
  --image=nicolaka/netshoot --target=app --profile=general -- bash

--target shares the app container’s process namespace so you can see its PIDs. Tools needing ptrace (py-spy, gdb) may need --profile=sysadmin, which the cluster policy may forbid; ask the platform owner rather than working around it.

Take evidence from one instance, ideally one already removed from the load balancer (mark unready, or cordon and let it drain), so a pause does not hurt users.

Dynamic levers

  • Raise log level for one service, one instance or one tenant at runtime if the app supports it (Spring Boot actuator loggers endpoint, a debug header honoured only from internal networks, a flag). Set an expiry and put it back.
  • Feature flags: disabling a new code path is often the fastest safe mitigation; confirm the flag’s blast radius first.
  • Traffic shifting: send a slice of traffic to a canary with extra instrumentation.

Hypothesis discipline

Write each hypothesis as: “If X is the cause, then Y should be true; I will check Z.” One change at a time. If two changes are made together and the problem goes away, you do not know which fixed it, and the postmortem will be guesswork. For deeper method, see $systematic-debugging.

Verification

  • The signal that showed the problem (error rate, latency percentile, failing synthetic) returns to baseline and stays there for at least one full traffic cycle you care about (often 30-60 minutes, or the next batch run).
  • Temporary levers are reverted: log levels, debug containers (kubectl get pod -o yaml shows ephemeral containers until the pod is replaced), cordons, flags.
  • Evidence files (profiles, dumps) are moved to approved storage or deleted; heap dumps contain customer data and need the same handling as a database copy.

Output

## Production debug log: <symptom>
- Impact: <who, what, since>
- Changes in window: <deploys, flags, config>
- Mitigation: <action, approved by, time, effect>
- Evidence: <queries, profiles, dumps (where stored)>
- Hypotheses tested: <hypothesis -> result>
- Root cause (or current best explanation): <...>
- Follow-ups: <fix, test, alert, runbook>
- Temporary changes reverted: <list with times>

Gotchas

  • Restarting “to see if it helps” destroys the evidence (memory state, stuck threads); grab a thread dump or profile from one instance first if it is safe to wait a minute.
  • Debug logging at full volume in production can itself cause an outage (disk, log pipeline, cost); scope it narrowly.
  • kubectl exec and debug containers bypass the normal deploy path and may violate compliance rules; check policy and record what was done.
  • Time zones: align everything to UTC before correlating events from different tools.

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Production Debugging"
  short_description: "Find the fault in live systems without adding a new one"
  default_prompt: "$production-debugging Checkout is timing out in production but only on some pods, help me debug it safely"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc