Skip to main content

Apex Debug Log Workflow with Claude Code

Overview

Apex debug logs are your primary tool for diagnosing runtime issues in Salesforce — governor limit violations, unexpected trigger behavior, silent failures, and more. This article covers a complete workflow for pulling debug logs from any Salesforce org using the SFDX CLI, then leveraging Claude Code to analyze them and surface root causes. Instead of manually scanning thousands of log lines, you hand the logs to Claude Code, which knows exactly which log events matter and in what order to prioritize them.

Prerequisites

  • Salesforce CLI (sf) installed and authenticated to your target org
  • jq installed for JSON parsing (sudo apt install jq on Linux/WSL)
  • Claude Code running in your project directory

Project Setup

Create this directory structure in your Salesforce project:

<project-root>/
├── debug-logs/ # Pulled log files (gitignored)
├── scripts/
│ ├── debug-pull.sh # Log pull helper script
│ └── apex/ # Anonymous Apex test scripts

Add the log directory to .gitignore:

# Apex debug logs — local only, never commit
debug-logs/

The Log Pull Script

Save this as scripts/debug-pull.sh and make it executable with chmod +x scripts/debug-pull.sh:

#!/bin/bash
# Pull Apex debug logs from Salesforce for Claude Code review
# Usage: ./scripts/debug-pull.sh [org-alias] [number-of-logs]

ORG=${1:-""}
COUNT=${2:-5}
LOG_DIR="./debug-logs"

if [ -z "$ORG" ]; then
echo "ERROR: Org alias required."
echo "Usage: ./scripts/debug-pull.sh <org-alias> [log-count]"
exit 1
fi

if ! command -v jq &> /dev/null; then
echo "ERROR: jq is required. Install with: sudo apt install jq"
exit 1
fi

mkdir -p $LOG_DIR

echo ""
echo "=== Fetching last $COUNT logs from [$ORG] ==="
echo ""

LOGS=$(sf apex list log --target-org "$ORG" --json 2>/dev/null \
| jq -r ".result | sort_by(.StartTime) | reverse | .[:$COUNT] | .[].Id")

if [ -z "$LOGS" ]; then
echo "No logs found. Ensure tracing is active and transactions have run."
echo "Tip: Run 'sf apex tail log --target-org $ORG' to start a trace session."
exit 0
fi

PULLED=0
for LOG_ID in $LOGS; do
OUTPUT_FILE="$LOG_DIR/$LOG_ID.log"

if [ -f "$OUTPUT_FILE" ]; then
echo " [skip] $LOG_ID — already exists"
continue
fi

echo " [pull] $LOG_ID"
sf apex get log --log-id "$LOG_ID" --target-org "$ORG" > "$OUTPUT_FILE" 2>/dev/null

if [ ! -s "$OUTPUT_FILE" ]; then
echo " [warn] $LOG_ID — empty log, skipping"
rm "$OUTPUT_FILE"
continue
fi

PULLED=$((PULLED + 1))
done

echo ""
echo "=== Done. $PULLED new log(s) saved to $LOG_DIR/ ==="
echo ""

ls -lht "$LOG_DIR"/*.log 2>/dev/null | head -10

Pulling Logs

Quick pull (last 5 logs)

./scripts/debug-pull.sh my-sandbox

Pull a specific count

./scripts/debug-pull.sh my-sandbox 10

Tail logs live during manual testing

sf apex tail log --target-org my-sandbox --color

This streams log output in real time as you interact with the org. It also automatically creates a trace flag for your session.

Run anonymous Apex and capture the log inline

sf apex run \
--file ./scripts/apex/test-something.apex \
--target-org my-sandbox \
--log-level DEBUG \
--json | jq '.result.logs'

Other Useful Log Commands

List available logs without pulling them

sf apex list log --target-org my-sandbox --json

Pull a specific log by ID

sf apex get log --log-id <logId> --target-org my-sandbox

Trace Flag Management

Trace flags tell Salesforce to capture detailed logs for your user. They auto-expire after 30 minutes by default. Running sf apex tail log automatically creates one for your session.

To check active trace flags manually:

sf data query \
--query "SELECT Id, LogType, StartDate, ExpirationDate, DebugLevelId \
FROM TraceFlag \
WHERE TracedEntityId = '<your-user-id>'" \
--target-org my-sandbox \
--use-tooling-api

Debug Log Levels

From least to most verbose:

LevelUse Case
ERRORProduction — errors only
WARNNear-production monitoring
INFOGeneral flow tracking
DEBUGStandard debugging
FINEDetailed execution flow
FINERVery detailed tracing
FINESTMaximum detail — produces large logs

Asking Claude Code to Analyze Logs

Once logs are in ./debug-logs/, ask Claude Code to review them directly:

"Review the latest log in ./debug-logs/ and tell me what's wrong"

For targeted analysis:

"Review ./debug-logs/07LxxxxID.log — I'm seeing a CPU time limit error. Find the root cause."

What Claude Code looks for (in priority order)

  1. FATAL_ERROR / unhandled exceptions — root cause first
  2. Governor limit warnings — CPU time, heap size, SOQL row counts, DML statement counts
  3. USER_DEBUG entries — intentional breadcrumbs left by the developer
  4. Slow SOQL — queries returning 100+ rows or taking 200ms+
  5. Unexpected null references

Log format reference

Debug logs follow a timestamp | event_type | detail structure. The key prefixes Claude Code focuses on:

PrefixMeaning
FATAL_ERRORUnhandled exception — always the starting point
EXCEPTION_THROWNException with stack trace
LIMIT_USAGE_FOR_NSGovernor limit consumption snapshot
USER_DEBUGDeveloper-placed System.debug() output
SOQL_EXECUTE_BEGIN/ENDQuery execution with row counts
DML_BEGIN/ENDDML operation markers

Verbose noise that can typically be ignored: VARIABLE_SCOPE_BEGIN, STATEMENT_EXECUTE, METHOD_ENTRY/EXIT — unless you're tracing a specific execution path.

Wiring This Into Your CLAUDE.md

To make Claude Code automatically aware of this workflow in your Salesforce projects, add a block to your project's CLAUDE.md:

## Apex Debug Log Workflow

For Apex runtime issues, governor limit errors, or unexpected behavior in org:

1. Pull logs first: `./scripts/debug-pull.sh <org-alias>`
2. Logs land in `./debug-logs/` — read the most recent `.log` file(s)
3. For detailed workflow reference: see docs/APEX_DEBUG_WORKFLOW.md

You can also create a path-scoped rule at .claude/rules/apex-debug.md that auto-activates when Claude Code touches .cls, .trigger, .apex, or debug-logs/ files. This rule can instruct Claude Code to check for logs before suggesting fixes for runtime errors.