| name | trace |
| description | Reverse-engineer the runtime call sequence for a C# symbol by temporarily injecting Diag.FlowStep instrumentation, running a play-mode scenario, querying the resulting flow trace via diag_query, and reverting the instrumentation cleanly. Use when you need to know "what actually runs when X happens" without scattering Debug.Log by hand. |
/trace — Runtime Call Tracer
A safer alternative to "sprinkle Debug.Log, run play mode, scrape console, remove logs" — fully automated, fully reversible.
Arguments
symbol (required): A class or method to trace. Examples:
WheelController.Tick — instrument one method
WheelController — instrument every method on the class
--scenario <name> (optional): Which play-mode scenario to drive. Defaults to physics-smoke. Other valid values: smoke, e2e-smoke. The scenario is what produces the call traffic — without one, the trace will be empty.
--seconds <N> (optional): How long to leave play mode running before tearing down. Default 6.
--no-revert (optional, dangerous): Skip the revert step. Use only if you want to inspect the instrumented files manually.
Workflow
Phase 0 — Preflight
- Verify clean working tree. Run
git status --porcelain. If there are uncommitted changes touching the file(s) we're about to instrument, ABORT and ask the user to commit or stash first. We will not silently mix instrumentation with their work-in-progress.
- Verify Rec #1
diag_query MCP tool exists. Check Assets/Scripts/Editor/MCP/DiagQueryTool.cs exists. Without it, the trace cannot be read back.
- Snapshot HEAD.
git rev-parse HEAD > /tmp/r8eox-trace-snapshot-sha. The revert phase compares against this.
Phase 1 — Locate
Dispatch unity-researcher (read-only) with the symbol name. It must return:
- The exact file path and line numbers of the symbol's declaration
- Every caller (via Grep across
Assets/Scripts/)
- The flow ID we'll use (suggest:
trace-<symbol>-<run-id>)
Phase 2 — Instrument
Run python3 .claude/skills/trace/scripts/instrument.py inject --file <path> --symbol <name> --flow-id <id>. The script:
- Parses C# with regex (no Roslyn dependency — keep it simple)
- Inserts
Diag.FlowStep("<id>", "<File>.<Method>"); at the first line of the method body
- For methods that return early or throw, inserts
Diag.FailFlow("<id>", "throw at <line>"); before each throw statement
- Records every modified file in
/tmp/r8eox-trace-modified-files so revert is exact
After instrumentation, refresh Unity (mcp__UnityMCP__execute_menu_item("Assets/Refresh")) and verify zero compile errors via mcp__UnityMCP__read_console.
If compile fails, immediately invoke Phase 4 (revert) and report the failure.
Phase 3 — Record
- Bring Unity to foreground:
osascript -e 'tell application "Unity" to activate'
- Open the scenario's scene (if
--scenario physics-smoke, that's Assets/Scenes/PhysicsSmokeTrack.unity or whatever the existing /physics-smoke skill uses — read its SKILL.md to mirror)
Diag.BeginFlow("<flow-id>") is invoked automatically the first time FlowStep runs against an unknown flow — verify this by reading Assets/Scripts/Diagnostics/Diag.cs. If begin-on-step is NOT supported, instrument the scenario's entry point with an explicit BeginFlow call too.
- Enter play mode:
mcp__UnityMCP__manage_editor(action="play")
- Wait
--seconds (default 6) — use a sleep, not a polling loop
- Exit play mode:
mcp__UnityMCP__manage_editor(action="stop")
- Query the trace:
mcp__UnityMCP__execute_custom_tool("diag_query", { "include_flows": true, "include_events": false }). The flow with name matching <flow-id> contains the call sequence; each FlowStep was a method entry.
Phase 4 — Revert (always runs, even on failure)
python3 .claude/skills/trace/scripts/instrument.py revert — reads /tmp/r8eox-trace-modified-files and restores each file from git index (git checkout HEAD -- <file>)
git status --porcelain — must show clean working tree
- If status is NOT clean, the script's revert failed: STOP and ask the user to inspect manually. Do not try to "fix" with destructive commands.
- Refresh Unity again to recompile the original code
rm /tmp/r8eox-trace-modified-files /tmp/r8eox-trace-snapshot-sha
Phase 5 — Render
Print the call sequence as an indented tree. Group consecutive same-method calls with (xN):
[trace-WheelController.Tick-20260412-1430]
WheelController.Tick (x4)
└── TireModel.SampleLateral
└── FrictionCurve.Evaluate (x4)
└── TireModel.SampleLongitudinal
└── FrictionCurve.Evaluate (x4)
└── SuspensionForce.Compute (x4)
Cap at 50 lines — if the trace is larger, summarize with method-call counts and offer to dump the full sequence to .tmp/trace-<flow-id>.json.
Failure modes
- Empty trace: scenario didn't exercise the symbol. Suggest a different
--scenario or a more specific symbol.
- Revert dirty: most likely cause is the user edited an instrumented file mid-run. STOP and ask.
- Compile error after inject: the instrumentation script's regex chose a bad insertion point. Revert immediately and report the file:line so we can refine the regex for that case.
Files in this skill
SKILL.md (this file)
scripts/instrument.py — inject/revert helper
scripts/trace_render.py — formats the diag_query JSON into the tree shown above
Safety contract
- This skill must always revert. The revert phase runs in a
try/finally semantically — if anything in Phase 3 fails, Phase 4 still runs.
- If a parallel Claude session is running
/trace simultaneously, the second invocation will find a non-empty /tmp/r8eox-trace-modified-files and refuse to start. Tracing is single-session.