Stack trace analysis

Teach Agents to find the first business frame from bottom to top, recognize wrapper exceptions and async boundaries, and handle minification and missing source maps.

A SKILL should state: root cause often lies at the earliest throw or innermost Caused by, not the last framework line in logs. Require expanding suppressed and aggregate exceptions.

Give field meanings per stack format (JS, JVM, Go, .NET); production needs commit SHA, build id, and source maps to map to source lines.

Async and thread pools scramble stacks: combine with trace_id, thread names, or reactive context to rebuild the chain; do not change unrelated modules from the top frame alone.

  • Confirm the stack matches the currently deployed version before deep analysis.
  • Distinguish OOM and other truncated or empty stacks—pivot to system logs and metrics.
  • Output should list suspicious frames, files to open, and suggested log points.

Frames: order, causality, noise

Top of stack is usually the last entered call (or async callback entry); walking down approaches the original trigger; JVM / .NET print order may differ from the JS console—state in the SKILL which end is “deeper”.

Async code stack differences — Promise chain vs async/await:

// ===== Promise chain stack (noisy, hard to trace causality) =====
Error: Payment failed
    at chargeCard (/app/src/payment.js:45:13)      // <- actual throw point
    at processTicksAndRejections (node:internal/process/task_queues:95:5)
    // Promise chain loses the handleCheckout frame (async-scheduled, caller exits)

// ===== async/await stack (V8 async stack traces) =====
Error: Payment failed
    at chargeCard (/app/src/payment.js:45:13)
    at async processPayment (/app/src/payment.js:28:5)
    at async handleCheckout (/app/src/checkout.js:15:3)
    at async Router.post (/app/src/routes.js:42:5)

// Stack noise filter (remove framework layers)
function isBusinessFrame(frame) {
  if (frame.includes('node:internal') || frame.includes('node_modules')) {
    return frame.includes('@company/') || frame.includes('sequelize');
  }
  return frame.includes('/src/') || frame.includes('/app/');
}
const businessFrames = stackTrace.split('\n')
  .filter(line => line.trim().startsWith('at '))
  .filter(isBusinessFrame);
  • Causal frames: package/path in your repo, tied to changes or feature flags—check version and branch first.
  • Noise frames: reflection, AOP, test harness, Promise microtasks, webpack / vite runtime—can fold but do not edit library code before reading business frames.
  • Wrapped exceptions: outer RuntimeException or aggregateError may hide inner ones—expand Caused by, errors[], suppressed.

Common wrapped-exception unwrapping (Java getCause / Python __cause__):

// Java: recursively unwrap Caused by chain
// RuntimeException wraps SQLTimeoutException wraps SocketTimeoutException
// Root cause is the innermost: SocketTimeoutException

public static Throwable getRootCause(Throwable throwable) {
    Throwable cause = throwable.getCause();
    while (cause != null && cause.getCause() != null) {
        cause = cause.getCause();
    }
    return cause != null ? cause : throwable;
}

# Python: __cause__ and __context__
try:
    result = db.query("SELECT ...")
except DatabaseError as e:
    raise PaymentError("Payment failed") from e  # explicit chain: __cause__

def get_root_cause(exc):
    while exc.__cause__ is not None:
        exc = exc.__cause__
    return exc

import traceback
traceback.print_exc()  # "The above exception was the direct cause of..."

// JavaScript: AggregateError unwrap (Promise.any)
try {
  await Promise.any([fetchA(), fetchB(), fetchC()]);
} catch (err) {
  if (err instanceof AggregateError) {
    err.errors.forEach((e, i) => console.error('Promise', i, 'failed:', e.stack));
  }
}

Source maps and production symbolication

After bundling, minification, and tree-shaking, stacks often show .min.js or hashed chunks; without sourceMappingURL or uploaded maps, line numbers refer to build artifacts, not TypeScript / Kotlin sources.

  • A SKILL should require errors to carry release / commit / build id bound to CI artifacts or platform symbol files.
  • Front end: distinguish “browser mapped stack” vs “raw stack in server logs”—they may differ.
  • Native and backend: dSYM, ProGuard mapping, Go inline tables, etc.—document per project so Agents do not assume TS line numbers always exist.

Correct handling of Node.js uncaught exceptions and unhandled Promise rejections:

// Node.js global exception handling (register at app entry)
process.on('uncaughtException', (err, origin) => {
  logger.error('Uncaught exception', {
    error: { type: err.constructor.name, message: err.message, stack: err.stack },
    origin,
    trace_id: getCurrentTraceId(),
  });
  gracefulShutdown().finally(() => process.exit(1));
});

process.on('unhandledRejection', (reason, promise) => {
  logger.error('Unhandled promise rejection', {
    reason: reason instanceof Error
      ? { type: reason.constructor.name, message: reason.message, stack: reason.stack }
      : reason,
  });
  // Node.js 15+: crashes by default, consistent with uncaughtException
});

// Better: use async_hooks to track async context
import { AsyncLocalStorage } from 'async_hooks';
const requestContext = new AsyncLocalStorage();
app.use((req, res, next) => {
  requestContext.run({ traceId: req.headers['x-trace-id'] }, next);
});

Analysis flow (skill-flow-block)

  [ Collect: full stack + Caused by / aggregate + deploy version / commit ]
                    │
                    ▼
         [ Identify format: V8 / JVM / Go / .NET; confirm stack growth direction ]
                    │
                    ▼
    [ Strip noise: runtime, framework glue, test fixtures, min paths mappable? ]
                    │
                    ▼
         [ Lock causality: first business frame + innermost root exception ]
                    │
           ┌────────┴────────┐
           ▼                 ▼
   [ Have source map / symbols ]   [ No mapping: artifact line numbers only ]
           │                 │
           ▼                 ▼
  [ Map to source file:line:col ]     [ Use build artifact or chunk name + version ]
           │                 │
           └────────┬────────┘
                    ▼
    [ Hypothesis: input, concurrency, config, deps; list validation steps and log points ]
Agent output should state explicitly whether the stack points at artifacts or source—do not treat .min.js lines as directly editable .ts lines.

Frame line extractor (demo)

A local demo lightweight parser: regex-extract path/class + line from common stack text so a SKILL can say “structure first, then reason”. Not a full language parser.


              

Supports common at … (file:line:col), at file:line:col, and (Foo.java:line); unmatched lines are skipped.

Summary

Stack analysis hinges on direction, causal chain, and version mapping: confirm stack order and runtime, land on business frames and the innermost exception, then use source maps / symbols to map lines to editable source; the demo extractor only helps structure, not replace platform symbolication.

code-preview">--- name: stack-trace-analysis description: Read exception stacks and Caused-by chains model: claude-sonnet-4-5 --- # Steps steps: 1. Collect full stack (with Caused by/aggregate/suppressed) + deploy version/commit 2. Identify stack format (V8/JVM/Go/.NET) and growth direction 3. Strip noise frames (runtime/framework/test fixtures/min paths) 4. Unwrap wrapped exceptions (getCause/errors[]/suppressed recursively) 5. Lock in first business frame and innermost root exception 6. Confirm source map / symbol availability 7. Map to source file line numbers 8. Propose testable hypotheses and suggested log points # Async stack notes async_stack_notes: - async/await preserves full call chain (better than Promise chain) - Promise chain may lose intermediate frames - Node.js: --async-context-frame-depth increases trace depth - Thread pool / Worker: combine with trace_id to associate context # Frame classification rules frame_classification: business: frames with /src/ /app/ @company/ paths noise: node:internal/ node_modules/ .min.js webpack-runtime exception: unwrap Caused by / __cause__ / AggregateError.errors[]

Back to skills More skills