# Apex Troubleshooting Guide

> Comprehensive guide to debugging Apex code, LSP validation, dependency management, and common deployment issues.

- Skill: `tools-only/apex-troubleshooting-guide-2` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add tools-only/apex-troubleshooting-guide-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tools-only/apex-troubleshooting-guide-2/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tools-only (https://skillmd.com/u/tools-only)
- Updated: 2026-09-29
- Page: https://skillmd.com/skills/tools-only/apex-troubleshooting-guide-2

---

# Apex Troubleshooting Guide

Comprehensive guide to debugging Apex code, LSP validation, dependency management, and common deployment issues.

---

## Table of Contents

1. [LSP-Based Validation (Auto-Fix Loop)](#lsp-based-validation-auto-fix-loop)
2. [Cross-Skill Dependency Checklist](#cross-skill-dependency-checklist)
3. [Common Deployment Errors](#common-deployment-errors)
4. [Debug Logs and Monitoring](#debug-logs-and-monitoring)
5. [Governor Limit Debugging](#governor-limit-debugging)
6. [Test Failures](#test-failures)

---

## LSP-Based Validation (Auto-Fix Loop)

The sf-apex skill includes Language Server Protocol (LSP) integration for real-time syntax validation. This enables Claude to automatically detect and fix Apex syntax errors during code authoring.

### How It Works

1. **PostToolUse Hook**: After every Write/Edit operation on `.cls` or `.trigger` files, the LSP hook validates syntax
2. **Apex Language Server**: Uses Salesforce's official `apex-jorje-lsp.jar` (from VS Code extension)
3. **Auto-Fix Loop**: If errors are found, Claude receives diagnostics and auto-fixes them (max 3 attempts)
4. **Two-Layer Validation**:
   - **LSP Validation**: Fast syntax checking (~500ms)
   - **150-Point Validation**: Semantic analysis for best practices

---

### Prerequisites

For LSP validation to work, users must have:

| Requirement | How to Install |
|-------------|----------------|
| **VS Code Salesforce Extension Pack** | VS Code → Extensions → "Salesforce Extension Pack" |
| **Java 11+ (Adoptium recommended)** | https://adoptium.net/temurin/releases/ |

**Verify Installation:**
```bash
# Check VS Code extensions
code --list-extensions | grep salesforce

# Check Java version
java -version
# Should output: openjdk version "11.x.x" or higher
```

---

### Validation Flow

```
User writes Apex code → Write/Edit tool executes
                              ↓
                    ┌─────────────────────────┐
                    │   LSP Validation (fast) │
                    │   Syntax errors only    │
                    └─────────────────────────┘
                              ↓
                    ┌─────────────────────────┐
                    │  150-Point Validation   │
                    │  Semantic best practices│
                    └─────────────────────────┘
                              ↓
                    Claude sees any errors and auto-fixes
```

---

### Sample LSP Error Output

```
============================================================
🔍 APEX LSP VALIDATION RESULTS
   File: force-app/main/default/classes/MyClass.cls
   Attempt: 1/3
============================================================

Found 1 error(s), 0 warning(s)

ISSUES TO FIX:
----------------------------------------
❌ [ERROR] line 4: Missing ';' at 'System.debug' (source: apex)

ACTION REQUIRED:
Please fix the Apex syntax errors above and try again.
(Attempt 1/3)
============================================================
```

---

### Common LSP Errors

| Error | Cause | Fix |
|-------|-------|-----|
| Missing ';' at ... | Statement not terminated | Add semicolon at end of line |
| Unexpected token ... | Syntax error | Check brackets, quotes, keywords |
| Unknown type ... | Class/type not found | Ensure class exists, check spelling |
| Method does not exist ... | Method call on wrong type | Verify method name and signature |
| Variable not found ... | Undeclared variable | Declare variable before use |

**Example Auto-Fix Loop:**

**Attempt 1 (ERROR):**
```apex
public class MyClass {
    public void doSomething() {
        System.debug('Hello')  // Missing semicolon
    }
}
```

**LSP Output:**
```
❌ [ERROR] line 3: Missing ';' at '}'
```

**Attempt 2 (SUCCESS):**
```apex
public class MyClass {
    public void doSomething() {
        System.debug('Hello');  // Fixed!
    }
}
```

**LSP Output:**
```
✅ VALIDATION PASSED
```

---

### Graceful Degradation

If LSP is unavailable (no VS Code extension or Java), validation silently skips - the skill continues to work with only 150-point semantic validation.

**Detection Logic:**
```python
# hooks/scripts/post-tool-validate.py
try:
    result = run_lsp_validation(file_path)
    if result.has_errors:
        print_errors(result)
except LSPNotAvailableException:
    # Silent fallback - continue without LSP
    pass
```

---

### Manual LSP Validation

**Run LSP validation manually from VS Code:**

1. Open Apex class in VS Code
2. View → Problems panel (`Cmd+Shift+M` / `Ctrl+Shift+M`)
3. See syntax errors highlighted in real-time

**Run from CLI (if available):**
```bash
# Apex compilation happens automatically during deploy (no standalone compile command)
sf project deploy start --metadata ApexClass:MyClass --target-org <alias> --dry-run --json
```

---

## Cross-Skill Dependency Checklist

**Before deploying Apex code, verify these prerequisites:**

| Prerequisite | Check Command | Required For |
|--------------|---------------|--------------|
| **TAF Package** | `sf package installed list --target-org alias` | TAF trigger pattern |
| **Custom Fields** | `sf sobject describe --sobject Lead --target-org alias` | Field references in code |
| **Permission Sets** | `sf org list metadata --metadata-type PermissionSet` | FLS for custom fields |
| **Trigger_Action__mdt** | Check Setup → Custom Metadata Types | TAF trigger execution |
| **Named Credentials** | Check Setup → Named Credentials | External callouts |
| **Custom Settings** | Check Setup → Custom Settings | Bypass flags, configuration |

---

### Common Deployment Order

```
1. sf-metadata: Create custom fields
   └─> sf sobject create --sobject-name Lead --fields "Score__c:Number(3,0)"

2. sf-metadata: Create Permission Sets
   └─> Grant FLS on custom fields

3. sf-deploy: Deploy fields + Permission Sets
   └─> sf project deploy start --metadata-dir force-app/main/default/objects

4. sf-apex: Deploy Apex classes/triggers
   └─> sf project deploy start --metadata-dir force-app/main/default/classes

5. sf-data: Create test data
   └─> sf data create record --sobject Account --values "Name='Test'"
```

---

### Verifying Prerequisites

**Check TAF Package:**
```bash
sf package installed list --target-org myorg --json
```

**Output:**
```json
{
  "result": [
    {
      "Id": "04t...",
      "SubscriberPackageName": "Trigger Actions Framework",
      "SubscriberPackageVersionNumber": "1.2.0"
    }
  ]
}
```

**If not installed:**
```bash
sf package install --package 04tKZ000000gUEFYA2 --target-org myorg --wait 10
```

---

**Check Custom Metadata Records:**
```bash
sf data query --query "SELECT DeveloperName, Object__c, Apex_Class_Name__c FROM Trigger_Action__mdt" --target-org myorg
```

**Expected Output:**
```
DeveloperName          Object__c  Apex_Class_Name__c
─────────────────────────────────────────────────────
TA_Account_SetDefaults  Account    TA_Account_SetDefaults
TA_Lead_CalculateScore  Lead       TA_Lead_CalculateScore
```

**If missing, create via sf-metadata skill.**

---

## Common Deployment Errors

### Error: "Field does not exist"

**Cause**: Apex references a custom field that doesn't exist in target org.

**Example:**
```
Error: Field Account.Custom_Field__c does not exist
```

**Fix:**
1. Verify field exists:
   ```bash
   sf sobject describe --sobject Account --target-org myorg | grep Custom_Field__c
   ```

2. Deploy field first:
   ```bash
   sf project deploy start --metadata CustomField:Account.Custom_Field__c --target-org myorg
   ```

3. Then deploy Apex

---

### Error: "Invalid type: TriggerAction"

**Cause**: TAF package not installed in target org.

**Example:**
```
Error: Invalid type: TriggerAction.BeforeInsert
```

**Fix:**
```bash
# Install TAF package
sf package install --package 04tKZ000000gUEFYA2 --target-org myorg --wait 10

# Verify
sf package installed list --target-org myorg
```

---

### Error: "Insufficient access rights"

**Cause**: Deploy user lacks permissions.

**Example:**
```
Error: Insufficient access rights on object id
```

**Fix:**
1. Verify user has "Modify All Data" or is System Administrator
2. Or add specific permissions to user's profile:
   ```bash
   sf org assign permset --name "Deploy_Permissions" --target-org myorg
   ```

---

### Error: "Test coverage less than 75%"

**Cause**: Production deployment requires 75% test coverage.

**Example:**
```
Error: Average test coverage across all Apex Classes and Triggers is 68%, at least 75% required
```

**Fix:**
1. Identify uncovered classes:
   ```bash
   sf apex run test --code-coverage --result-format human --target-org myorg
   ```

2. Add missing test classes

3. Ensure tests have assertions:
   ```apex
   Assert.areEqual(expected, actual, 'Message');
   ```

---

### Error: "FIELD_CUSTOM_VALIDATION_EXCEPTION"

**Cause**: Apex code violates validation rule.

**Example:**
```
Error: FIELD_CUSTOM_VALIDATION_EXCEPTION: Annual Revenue must be greater than 0
```

**Fix:**
1. Check validation rules:
   ```bash
   sf data query --query "SELECT ValidationName, ErrorDisplayField, ErrorMessage FROM ValidationRule WHERE EntityDefinition.QualifiedApiName = 'Account'" --target-org myorg
   ```

2. Update Apex to satisfy validation logic:
   ```apex
   acc.AnnualRevenue = 1000000;  // Ensure > 0
   ```

---

## Debug Logs and Monitoring

### Enable Debug Logs

**Via Setup:**
1. Setup → Debug Logs
2. Click "New"
3. Select User
4. Set expiration (max 24 hours)
5. Set log levels:
   - Apex Code: `DEBUG`
   - Database: `INFO`
   - Workflow: `INFO`

**Via CLI:**
```bash
# Create trace flag
sf data create record --sobject TraceFlag --values "StartDate=2025-01-01T00:00:00Z EndDate=2025-01-02T00:00:00Z LogType=USER_DEBUG TracedEntityId=<USER_ID> DebugLevelId=<DEBUG_LEVEL_ID>" --target-org myorg

# Tail logs in real-time
sf apex tail log --target-org myorg
```

---

### Reading Debug Logs

**Structure:**
```
HH:MM:SS.SSS|EXECUTION_STARTED
HH:MM:SS.SSS|CODE_UNIT_STARTED|AccountService
HH:MM:SS.SSS|USER_DEBUG|[3]|DEBUG|Processing account: Test
HH:MM:SS.SSS|SOQL_EXECUTE_BEGIN|[5]|SELECT Id FROM Account
HH:MM:SS.SSS|SOQL_EXECUTE_END|[5]|Rows:10
HH:MM:SS.SSS|DML_BEGIN|[8]|Op:Update|Type:Account|Rows:10
HH:MM:SS.SSS|DML_END|[8]
HH:MM:SS.SSS|LIMIT_USAGE_FOR_NS|(default)|SOQL:1/100|DML:1/150
HH:MM:SS.SSS|EXECUTION_FINISHED
```

**Key Events:**
- `USER_DEBUG`: Your `System.debug()` statements
- `SOQL_EXECUTE_*`: SOQL queries
- `DML_BEGIN/END`: DML operations
- `LIMIT_USAGE_FOR_NS`: Governor limit consumption

---

### Strategic Debug Statements

```apex
public static void processAccounts(List<Account> accounts) {
    System.debug(LoggingLevel.INFO, '=== START processAccounts ===');
    System.debug(LoggingLevel.INFO, 'Input size: ' + accounts.size());

    // Log limits BEFORE expensive operation
    System.debug('SOQL before: ' + Limits.getQueries() + '/' + Limits.getLimitQueries());

    List<Contact> contacts = [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds];

    // Log limits AFTER
    System.debug('SOQL after: ' + Limits.getQueries() + '/' + Limits.getLimitQueries());
    System.debug('Contacts retrieved: ' + contacts.size());

    System.debug(LoggingLevel.INFO, '=== END processAccounts ===');
}
```

---

### Log Levels

| Level | When to Use | Example |
|-------|-------------|---------|
| `ERROR` | Critical failures | `System.debug(LoggingLevel.ERROR, 'DML failed: ' + e.getMessage())` |
| `WARN` | Potential issues | `System.debug(LoggingLevel.WARN, 'No contacts found for account')` |
| `INFO` | Key milestones | `System.debug(LoggingLevel.INFO, 'Processing 251 accounts')` |
| `DEBUG` | Detailed traces | `System.debug(LoggingLevel.DEBUG, 'Variable value: ' + var)` |
| `FINE`/`FINER`/`FINEST` | Very detailed | Rarely used |

---

## Governor Limit Debugging

### Monitoring Limits in Code

```apex
public static void expensiveOperation() {
    System.debug('=== LIMIT CHECK ===');
    System.debug('SOQL Queries: ' + Limits.getQueries() + '/' + Limits.getLimitQueries());
    System.debug('DML Statements: ' + Limits.getDmlStatements() + '/' + Limits.getLimitDmlStatements());
    System.debug('DML Rows: ' + Limits.getDmlRows() + '/' + Limits.getLimitDmlRows());
    System.debug('CPU Time: ' + Limits.getCpuTime() + '/' + Limits.getLimitCpuTime());
    System.debug('Heap Size: ' + Limits.getHeapSize() + '/' + Limits.getLimitHeapSize());
}
```

---

### Common Limit Exceptions

**SOQL Limit (100 queries):**
```
System.LimitException: Too many SOQL queries: 101
```

**Fix**: Query BEFORE loops, use Maps for lookups.

**DML Limit (150 statements):**
```
System.LimitException: Too many DML statements: 151
```

**Fix**: Collect records in List, DML AFTER loop.

**CPU Time Limit (10 seconds):**
```
System.LimitException: Maximum CPU time exceeded
```

**Fix**: Optimize loops, move expensive operations to async, reduce complexity.

**Heap Size Limit (6 MB):**
```
System.LimitException: Apex heap size too large
```

**Fix**: Process in batches, clear collections when done, avoid storing large objects in memory.

---

### Using Limits Class for Alerts

```apex
public static void monitoredOperation() {
    // Warn if approaching 80% of limit
    Integer queriesUsed = Limits.getQueries();
    Integer queriesLimit = Limits.getLimitQueries();

    if (queriesUsed > queriesLimit * 0.8) {
        System.debug(LoggingLevel.WARN, 'Approaching SOQL limit: ' + queriesUsed + '/' + queriesLimit);
    }

    // Expensive operation
    List<Account> accounts = [SELECT Id FROM Account];
}
```

---

## Test Failures

### Common Test Failure Patterns

**Pattern 1: No assertions**
```apex
@IsTest
static void testCreateAccount() {
    Account acc = new Account(Name = 'Test');
    insert acc;
    // PASSES even if logic is broken!
}
```

**Fix**: Add assertions
```apex
@IsTest
static void testCreateAccount() {
    Account acc = new Account(Name = 'Test', Industry = 'Tech');
    insert acc;

    Account inserted = [SELECT Id, Industry FROM Account WHERE Id = :acc.Id];
    Assert.areEqual('Tech', inserted.Industry, 'Industry should be set');
}
```

---

**Pattern 2: Order dependency**
```apex
@IsTest
static void test1() {
    insert new Account(Name = 'Shared');
}

@IsTest
static void test2() {
    // Assumes test1 ran first - BRITTLE!
    Account acc = [SELECT Id FROM Account WHERE Name = 'Shared'];
}
```

**Fix**: Use @TestSetup or create data in each test
```apex
@TestSetup
static void setup() {
    insert new Account(Name = 'Shared');
}

@IsTest
static void test2() {
    Account acc = [SELECT Id FROM Account WHERE Name = 'Shared'];  // Safe
}
```

---

**Pattern 3: Insufficient permissions**
```apex
@IsTest
static void testRestrictedUser() {
    User u = TestDataFactory.createStandardUser();

    System.runAs(u) {
        // Fails if user lacks permission
        insert new Account(Name = 'Test');
    }
}
```

**Fix**: Grant necessary permissions
```apex
@TestSetup
static void setup() {
    User u = TestDataFactory.createStandardUser();
    insert new PermissionSetAssignment(
        AssigneeId = u.Id,
        PermissionSetId = [SELECT Id FROM PermissionSet WHERE Name = 'Account_Create'].Id
    );
}
```

---

### Running Tests

**VS Code:**
1. Open test class
2. Click "Run Test" above `@IsTest` method
3. View results in Output panel

**CLI:**
```bash
# Run specific test class
sf apex run test --tests AccountServiceTest --result-format human --code-coverage --target-org myorg

# Run all tests
sf apex run test --test-level RunLocalTests --result-format human --code-coverage --target-org myorg

# Run tests and generate coverage report
sf apex run test --test-level RunLocalTests --code-coverage --result-format json --output-dir test-results --target-org myorg
```

**Output:**
```
Test Summary
════════════
Outcome              Passed
Tests Ran            12
Pass Rate            100%
Fail Rate            0%
Skip Rate            0%
Test Run Coverage    92%
Org Wide Coverage    85%
Test Execution Time  1234 ms

Coverage Warnings
═════════════════
AccountService.cls  Line 45 not covered by tests
```

---

## Debugging Strategies

### 1. Binary Search for Errors

When unsure where error occurs, add debug statements at midpoints:

```apex
public static void complexOperation() {
    System.debug('START');

    // Part 1
    List<Account> accounts = [SELECT Id FROM Account];
    System.debug('CHECKPOINT 1: Retrieved ' + accounts.size() + ' accounts');

    // Part 2
    for (Account acc : accounts) {
        acc.Industry = 'Tech';
    }
    System.debug('CHECKPOINT 2: Updated accounts');

    // Part 3
    update accounts;
    System.debug('CHECKPOINT 3: DML complete');

    System.debug('END');
}
```

Run and check logs to see which checkpoint fails.

---

### 2. Isolate in Anonymous Apex

**Execute in Developer Console:**
```apex
Account acc = new Account(Name = 'Debug Test', Industry = 'Tech');
insert acc;

System.debug('Account ID: ' + acc.Id);
System.debug('Industry: ' + acc.Industry);
```

Open Execute Anonymous Window (`Ctrl+E`), paste code, check logs.

---

### 3. Unit Test in Isolation

**Create minimal test case:**
```apex
@IsTest
static void debugIssue() {
    Account acc = new Account(Name = 'Test', AnnualRevenue = null);

    Test.startTest();
    AccountService.calculateScore(acc);  // Isolated method
    Test.stopTest();

    System.debug('Score: ' + acc.Score__c);
}
```

Easier to debug than full integration test.

---

## Reference

**Full Documentation**: See `docs/` folder for comprehensive guides:
- `best-practices.md` - Debugging best practices
- `testing-guide.md` - Test troubleshooting
- `code-review-checklist.md` - Quality checklist

**Back to Main**: [SKILL.md](../SKILL.md)

