Skip to content

feat: add debug symbols workflow#756

Open
thymikee wants to merge 1 commit into
mainfrom
codex/debug-symbols
Open

feat: add debug symbols workflow#756
thymikee wants to merge 1 commit into
mainfrom
codex/debug-symbols

Conversation

@thymikee

@thymikee thymikee commented Jun 10, 2026

Copy link
Copy Markdown
Member

Summary

Adds a narrow agent-device debug symbols workflow for crash symbolication in the #694 native diagnostics rollout.

  • Supports Apple .ips, .crash, and log-style crash artifacts by matching UUIDs against local .dSYM bundles via dwarfdump --uuid, then running atos.
  • Keeps debug scoped to crash artifact triage. It does not compete with logs, network, perf, record/trace, React DevTools, or Xcode/LLDB.
  • Returns compact crash context: artifact path, app/thread, exception/termination, top frames, first actionable frame finding, matched dSYM UUIDs, and warnings.
  • Writes the full symbolicated artifact to --out; raw crash bodies are not dumped into default output or agent context.
  • Documents Android Java/R8 and native symbolication as unsupported/deferred with recovery hints.

Refs #699
Parent context: #694

Touched files: 21.

Example Use Case

Use debug symbols when an agent or developer already has an Apple crash artifact plus local dSYMs and needs to answer: "where did this crash land, and what app-owned frame should I inspect first?"

This is better than pasting raw crash logs into agent context because it validates the dSYM UUID match, extracts the crashed thread and first symbolicated app frame, and keeps the full report on disk. It is not a replacement for native debugging: use logs for the lead-up/timeline and Xcode/LLDB for live state, breakpoints, variables, stepping, and root-cause debugging.

Example compact report:

Symbolicated 3 frames -> /private/tmp/agent-device-crash-probe/CrashSymbolProbe-symbolicated-final.ips
Crash: CrashSymbolProbe thread 0
Bundle: dev.agentdevice.CrashSymbolProbe
Exception: EXC_BREAKPOINT
Frame 0: CrashSymbolProbe CrashSymbolProbeTrigger (in CrashSymbolProbe) (main.m:8)
Finding: Start with CrashSymbolProbeTrigger ... first symbolicated frame captured on the crashed thread.
Matched: CrashSymbolProbe 76C74FACC6203472951FEBB0DC7833B5 arm64

Validation

Real Apple simulator verification:

  • Simulator: iPhone 16 Plus, iOS 18.6, UDID 11D1F58A-5347-4DD5-8AD9-ED8948774DDF.
  • Probe app/dSYM: real UIKit simulator app CrashSymbolProbe and CrashSymbolProbe.app.dSYM.
  • dSYM UUID: 76C74FAC-C620-3472-951F-EBB0DC7833B5 (arm64).
  • Real crash artifact: /Users/thymikee/Library/Logs/DiagnosticReports/CrashSymbolProbe-2026-06-11-131543.ips.

Verified command:

node bin/agent-device.mjs debug symbols \
  --artifact /Users/thymikee/Library/Logs/DiagnosticReports/CrashSymbolProbe-2026-06-11-131543.ips \
  --dsym /private/tmp/agent-device-crash-probe/CrashSymbolProbe.app.dSYM \
  --out /private/tmp/agent-device-crash-probe/CrashSymbolProbe-symbolicated-final.ips

Trimmed artifact proof:

{
  "frames": [
    { "symbol": "CrashSymbolProbeTrigger (in CrashSymbolProbe) (main.m:8)" },
    { "symbol": "__57-[AppDelegate application:didFinishLaunchingWithOptions:]_block_invoke (in CrashSymbolProbe) (main.m:22)" }
  ],
  "agentDeviceSymbolication": {
    "tool": "agent-device debug symbols",
    "symbolicatedFrames": 3
  }
}

Automated validation:

  • pnpm exec vitest run src/__tests__/debug-symbols.test.ts src/__tests__/cli-debug-symbols.test.ts src/utils/__tests__/args.test.ts
  • pnpm check:fallow --base origin/main
  • pnpm check:quick
  • pnpm check:unit
  • pnpm format
  • pnpm build
  • git diff --check

Support matrix:

  • Apple .ips, .crash, and Binary Images log-style artifacts: supported with matching .dSYM.
  • Android Java/R8 mapping.txt: deferred; use retrace externally.
  • Android native ndk-stack/addr2line: deferred; use Android NDK tooling externally.

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://callstack.github.io/agent-device/pr-preview/pr-756/

Built to branch gh-pages at 2026-06-11 12:52 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 1.2 MB 1.2 MB +13.3 kB
JS gzip 392.1 kB 396.8 kB +4.7 kB
npm tarball 510.4 kB 511.2 kB +732 B
npm unpacked 1.7 MB 1.7 MB -2.6 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 29.1 ms 27.7 ms -1.4 ms
CLI --help 44.7 ms 43.7 ms -1.0 ms

Top changed chunks:

Chunk Raw diff Gzip diff
dist/src/9542.js +13.3 kB +4.6 kB
dist/src/2415.js -3.5 kB -1.2 kB
dist/src/args.js +1.4 kB +495 B
dist/src/8173.js +988 B +341 B
dist/src/1352.js +1.1 kB +315 B

@thymikee thymikee force-pushed the codex/debug-symbols branch from a25068d to 073d0cb Compare June 11, 2026 07:10

Copy link
Copy Markdown
Member Author

Code review

Verdict: significant issues — the headline .ips path will fail on real device-produced crash files; the rest of the design is sound.

Findings

  1. Major (arguably blocker)src/debug-symbols.ts:136: real .ips files written by iOS/macOS since iOS 15 are two JSON documents — a single-line JSON header followed by the JSON payload. JSON.parse(text) on the whole file throws, the parser falls through to the text-crash reader, finds no Binary Images: section, and dies with UNSUPPORTED_OPERATION — i.e. the primary advertised input format fails for every real device artifact. The fixture in src/__tests__/debug-symbols.test.ts:91 is a single headerless JSON object, which is exactly why tests don't catch it. Fix: split on the first newline, parse the payload, preserve the header when rewriting output.

  2. Minorsrc/debug-symbols.ts:243: the Binary Images: regex requires an arch token, but legacy macOS .crash files use +com.example.App (1.0 - 1) <UUID> /path with a version instead — those parse to zero images and error as unsupported; arm64_32 (watchOS) is also missing from the alternation.

  3. Minorsrc/debug-symbols.ts:161,182,211,254: readNumber accepts any finite number, then BigInt(base)/BigInt(imageOffset) is called on it — a malformed artifact with a fractional value throws an uncaught RangeError instead of a normalized AppError.

  4. Minorsrc/debug-symbols.ts:307: findDsymBundles calls fs.stat(root) unguarded; a nonexistent --search-path surfaces as a raw ENOENT rather than an INVALID_ARGS AppError with a hint.

  5. Minorsrc/debug-symbols.ts:268-274: text-crash frames are matched to images by name only; when two binary images share a name (app + extension — common in real crashes), the first image's base is used for all frames with that name, producing wrong -l load addresses and silently wrong symbols.

  6. Minorsrc/debug-symbols.ts:429-434: unsymbolicated detection relies on atos echoing the address byte-for-byte; variants that zero-pad or print 0x… (in App) get treated as successful symbols. Also, atos output lines are zipped with input addresses after filtering blank lines — any stray blank line shifts the mapping and mis-attributes symbols across frames.

  7. Minorsrc/debug-symbols.ts:378-386: on no-match, artifactUuids dumps every image UUID into the error details; a real .ips has 300+ usedImages, so the payload is effectively unbounded relative to the "compact summary" goal.

  8. Minorsrc/debug-symbols.ts:482: readTextFile reads the whole artifact with no size cap; a multi-hundred-MB log is loaded fully into memory (twice, via split/join).

  9. Minor (tests) — all five tests mock xcrun/dwarfdump/atos with idealized outputs; no two-document .ips fixture (masking finding 1), no multi-arch/multi-dSYM case, no garbage-input case. At least one fixture captured from a real device .ips should be checked in.

  10. Informationalfeat: add Apple xctrace perf profiling #755, feat: add Android native perf profiling #757, and feat: add perf memory diagnostics #759 touch the same shared files (cli-grammar/observability.ts, client-command-metadata.ts, cli-flags.ts, cli-command-overrides.ts, cli-help.ts, client-types.ts, skillgym smoke suite, docs); whichever lands second conflicts, though the additions here are append-style and should rebase mechanically.

Verified clean

No command injection (spawn with shell: false argv arrays); the atos -l load address is computed correctly (image base as -l, base + imageOffset as absolute addresses — the right convention); UUID normalization symmetric; dSYM search bounded; output is paths + summary only.

Overall

Good architecture — clean parse/match/symbolicate separation, bounded search, well-normalized errors. But the flagship .ips format can't parse real device files at all; that must be fixed and validated against a real crash before merge. The rest is robustness hardening, worth doing but not individually blocking.


Generated by Claude Code

@thymikee

Copy link
Copy Markdown
Member Author

Holding merge readiness on this until real Apple verification is done. Fixture-backed tests and command-construction coverage are useful, but for this workflow we need an actual simulator/device crash artifact plus matching dSYM exercised end-to-end with agent-device debug symbols, or a concrete blocker after attempting it.

Please add validation evidence with the exact commands, simulator/device identity, crash artifact + dSYM provenance, output path, and proof the symbolicated artifact was produced. Until then I no longer consider #756 merge-ready.

@thymikee thymikee force-pushed the codex/debug-symbols branch from 59095db to e03d5c4 Compare June 11, 2026 11:21
@thymikee

Copy link
Copy Markdown
Member Author

Updated validation note: the previous residual risk about no real simulator crash/dSYM evidence is stale. Real Apple verification was completed on branch codex/debug-symbols.

Evidence:

  • Simulator: iPhone 16 Plus, iOS 18.6, UDID 11D1F58A-5347-4DD5-8AD9-ED8948774DDF.
  • Probe app/dSYM: /private/tmp/agent-device-crash-probe/CrashSymbolProbe.app and CrashSymbolProbe.app.dSYM.
  • dSYM UUID: 76C74FAC-C620-3472-951F-EBB0DC7833B5 (arm64).
  • Real crash artifact: CrashSymbolProbe-2026-06-11-131543.ips from ~/Library/Logs/DiagnosticReports.
  • Verified command: agent-device debug symbols --artifact <real .ips> --dsym <matching .dSYM> --out /private/tmp/agent-device-crash-probe/CrashSymbolProbe-symbolicated-final.ips.
  • Result: compact output symbolicated 3 frames and identified CrashSymbolProbeTrigger (main.m:8) as the first actionable crashed-thread frame. The full symbolicated report stayed on disk.

Also aligned product scope: debug symbols is a crash triage lane, not a native debugger. Use logs for lead-up, debug symbols for post-crash artifact triage/dSYM UUID matching/first app frame, and Xcode/LLDB for live debugging.

@thymikee

Copy link
Copy Markdown
Member Author

Updated #756 with the decided direction: debug symbols is a narrow crash-triage lane, not native debugger orchestration.

When to use it:

  • Use logs for the lead-up/timeline.
  • Use debug symbols for post-crash artifact triage: dSYM UUID match, crashed thread, app-owned frame, file/line, and a durable symbolicated artifact path.
  • Use Xcode/LLDB for live debugging: breakpoints, variables, stepping, memory/runtime inspection.

Real simulator verification:

  • Simulator: iPhone 16 Plus, iOS 18.6, UDID 11D1F58A-5347-4DD5-8AD9-ED8948774DDF.
  • App/dSYM: real UIKit simulator app CrashSymbolProbe and CrashSymbolProbe.app.dSYM.
  • dSYM UUID: 76C74FAC-C620-3472-951F-EBB0DC7833B5 (arm64).
  • Crash artifact: /Users/thymikee/Library/Logs/DiagnosticReports/CrashSymbolProbe-2026-06-11-131543.ips.

Verified command:

node bin/agent-device.mjs debug symbols \
  --artifact /Users/thymikee/Library/Logs/DiagnosticReports/CrashSymbolProbe-2026-06-11-131543.ips \
  --dsym /private/tmp/agent-device-crash-probe/CrashSymbolProbe.app.dSYM \
  --out /private/tmp/agent-device-crash-probe/CrashSymbolProbe-symbolicated-final.ips

Example compact report:

Symbolicated 3 frames -> /private/tmp/agent-device-crash-probe/CrashSymbolProbe-symbolicated-final.ips
Crash: CrashSymbolProbe thread 0
Bundle: dev.agentdevice.CrashSymbolProbe
Exception: EXC_BREAKPOINT
Frame 0: CrashSymbolProbe CrashSymbolProbeTrigger (in CrashSymbolProbe) (main.m:8)
Finding: Start with CrashSymbolProbeTrigger ... first symbolicated frame captured on the crashed thread.
Matched: CrashSymbolProbe 76C74FACC6203472951FEBB0DC7833B5 arm64

Trimmed artifact proof:

{
  "frames": [
    { "symbol": "CrashSymbolProbeTrigger (in CrashSymbolProbe) (main.m:8)" },
    { "symbol": "__57-[AppDelegate application:didFinishLaunchingWithOptions:]_block_invoke (in CrashSymbolProbe) (main.m:22)" }
  ],
  "agentDeviceSymbolication": {
    "tool": "agent-device debug symbols",
    "symbolicatedFrames": 3
  }
}

Raw crash bodies remain on disk and are not dumped into default output/agent context.

@thymikee thymikee force-pushed the codex/debug-symbols branch from e03d5c4 to dc9faa0 Compare June 11, 2026 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant