# 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 ``` --- ### 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 --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 schema generate field --object Lead --label "Score" (then edit XML for 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= DebugLevelId=" --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 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 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 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 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 `references/` folder for comprehensive guides: - `best-practices.md` - Debugging best practices - `testing-patterns.md` - Test troubleshooting - `code-review-checklist.md` - Quality checklist **Back to Main**: [SKILL.md](../SKILL.md)