callout-and-dml-transaction-boundaries
Use when diagnosing, preventing, or refactoring the 'You have uncommitted work pending' CalloutException caused by mixing DML and callouts in the same Apex transaction. Triggers: 'uncommitted work pending', 'callout after DML', 'DML between callouts'. NOT for general HTTP callout construction, Named Credential setup, or async Apex design in isolation.
Best use case
callout-and-dml-transaction-boundaries is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Use when diagnosing, preventing, or refactoring the 'You have uncommitted work pending' CalloutException caused by mixing DML and callouts in the same Apex transaction. Triggers: 'uncommitted work pending', 'callout after DML', 'DML between callouts'. NOT for general HTTP callout construction, Named Credential setup, or async Apex design in isolation.
Teams using callout-and-dml-transaction-boundaries should expect a more consistent output, faster repeated execution, less prompt rewriting, better workflow continuity with your supporting tools.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
- You already have the supporting tools or dependencies needed by this skill.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/callout-and-dml-transaction-boundaries/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How callout-and-dml-transaction-boundaries Compares
| Feature / Agent | callout-and-dml-transaction-boundaries | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Use when diagnosing, preventing, or refactoring the 'You have uncommitted work pending' CalloutException caused by mixing DML and callouts in the same Apex transaction. Triggers: 'uncommitted work pending', 'callout after DML', 'DML between callouts'. NOT for general HTTP callout construction, Named Credential setup, or async Apex design in isolation.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Callout and DML Transaction Boundaries Use this skill when Apex code that mixes outbound HTTP callouts and DML operations fails with `System.CalloutException: You have uncommitted work pending`, or when you need to architect a transaction that requires both callouts and database writes without hitting this restriction. --- ## Before Starting Gather this context before working on anything in this domain: - What is the full execution path? The restriction is transaction-scoped, not method-scoped. DML in any helper, trigger, or utility class earlier in the same transaction counts. - Does the callout result determine what gets saved, or can the callout happen after the DML commits? - Is this running synchronously (trigger, controller) or asynchronously (Queueable, Batch, @future)? --- ## Core Concepts ### The Uncommitted-Work-Pending Rule Is Transaction-Scoped Salesforce enforces a hard rule: if any DML statement (insert, update, delete, upsert, or Database methods) has executed in the current Apex transaction, all subsequent callouts in that same transaction throw `System.CalloutException: You have uncommitted work pending`. This applies to the entire transaction, not just the current method. DML performed in a trigger handler, a utility class, or even a managed package that fires earlier in the same execution context will block a callout later. ### Callout-DML-Callout Is Also Prohibited It is not enough to do the callout first and then DML. If you need a second callout after DML, the same restriction applies. Any pattern like callout -> DML -> callout requires the second callout to move to a separate async boundary, because the DML in the middle creates uncommitted work for the remainder of the transaction. ### Async Boundaries Reset the Transaction The canonical fix is to split the work across transaction boundaries. A Queueable that implements `Database.AllowsCallouts` runs in its own transaction with a clean DML slate. The original transaction performs DML and enqueues the Queueable; the Queueable then executes callouts in its own fresh context. `@future(callout=true)` is an older alternative that also creates a new transaction boundary but cannot accept sObject parameters or be chained. ### Callout-First Is the Simplest Synchronous Fix When the callout result is needed before saving, the simplest approach is to reorder the code: perform the callout first, capture the response, and only then execute DML. This works as long as no DML has occurred anywhere earlier in the transaction — including triggers, before-save flows, or validation-rule side effects. --- ## Common Patterns ### Pattern 1: Callout First, DML Second (Synchronous) **When to use:** The callout response determines what data to save, and no DML has occurred yet in the transaction. **How it works:** 1. Make the HTTP callout and capture the response. 2. Parse and validate the response. 3. Perform DML with the callout-derived data. **Why not the alternative:** This avoids async complexity entirely. It fails only if something else in the transaction (a trigger, flow, or managed package) has already performed DML before your code runs. ### Pattern 2: DML First, Queueable Callout After Commit **When to use:** Business data must be saved first, and the callout can happen after the transaction commits. This is the most common real-world pattern. **How it works:** 1. Perform all DML in the synchronous transaction. 2. Enqueue a Queueable that implements `Database.AllowsCallouts`, passing record IDs. 3. In the Queueable `execute()`, re-query the records and make the callout. 4. Update integration status fields from within the Queueable's own transaction. **Why not the alternative:** `@future(callout=true)` also works but cannot accept sObjects, cannot be chained, and cannot be monitored via AsyncApexJob as easily. Queueable is the modern standard. ### Pattern 3: Split Callout-DML-Callout With Chained Queueables **When to use:** The flow requires callout A, then DML, then callout B. **How it works:** 1. Make callout A synchronously (before any DML). 2. Perform DML. 3. Enqueue a Queueable for callout B, passing the IDs and any state needed from callout A. **Why not the alternative:** Attempting all three operations in a single synchronous execution always fails. The second callout hits uncommitted work from the DML. --- ## Decision Guidance | Situation | Recommended Approach | Reason | |---|---|---| | Callout result needed before DML, no prior DML in transaction | Callout first, then DML (synchronous) | Simplest; no async overhead | | DML must happen first, callout can be deferred | Enqueue Queueable with `Database.AllowsCallouts` | Clean transaction boundary; chainable | | Trigger context needs to fire callout | Enqueue Queueable from trigger handler | Triggers always have pending DML from the triggering record | | Two callouts separated by DML | First callout synchronous, DML, then Queueable for second callout | Only way to satisfy both boundaries | | Legacy code, cannot refactor to Queueable | `@future(callout=true)` | Works but limited: no sObject params, no chaining | | Batch job needs callouts and DML per chunk | Implement `Database.AllowsCallouts` on Batch; callout before DML in each `execute()` | Each `execute()` is its own transaction | --- ## Recommended Workflow Step-by-step instructions for an AI agent or practitioner working on this task: 1. **Map the full transaction path.** Trace every DML and callout in the execution — including triggers, flows, and helper classes — to identify the exact ordering. 2. **Identify which DML blocks the callout.** The culprit is often not in the same class. Check trigger handlers, before-save flows, and utility methods that fire before your callout code. 3. **Decide if the callout can move before all DML.** If yes, reorder synchronously. If no, introduce an async boundary. 4. **Implement the async boundary.** Create a Queueable implementing `Database.AllowsCallouts`. Pass only record IDs, not sObjects. Re-query inside the Queueable. 5. **Add error handling for the async path.** The Queueable runs in a separate transaction. If the callout fails, the original DML is already committed. Design retry or status-tracking logic. 6. **Test both success and failure.** Use `Test.setMock()` and `Test.startTest()`/`Test.stopTest()` to force Queueable execution. Verify that the callout fires and that failures are logged. --- ## Review Checklist Run through these before marking work in this area complete: - [ ] No DML occurs before any callout in the same synchronous transaction - [ ] Queueable classes that make callouts implement `Database.AllowsCallouts` - [ ] Record IDs (not sObjects) are passed to async boundaries - [ ] The callout-dependent path has error handling for async failure scenarios - [ ] Trigger handlers do not attempt direct callouts - [ ] Test classes use `Test.setMock()` and verify both sync DML and async callout behavior --- ## Salesforce-Specific Gotchas Non-obvious platform behaviors that cause real production problems: 1. **DML in managed packages counts.** If an installed package performs DML in a trigger or subscriber handler before your code runs, your callout will fail even though your code has no DML. The only fix is to move your callout to an async boundary. 2. **Before-save flows can cause hidden DML.** A before-save record-triggered flow that creates or updates a related record introduces DML before your after-save logic runs. This is invisible in the Apex call stack. 3. **System.enqueueJob counts toward the Queueable limit.** In synchronous transactions, you can enqueue up to 50 Queueable jobs. In a Queueable's own execute, you can enqueue exactly 1 (for chaining). Plan your splitting accordingly. 4. **@future cannot call @future.** If your code is already inside an @future method, you cannot call another @future to split the transaction. Use Queueable for chainable async work. --- ## Output Artifacts | Artifact | Description | |---|---| | Refactored transaction design | Diagram or description showing DML and callout separated into safe boundaries | | Queueable callout class | Apex class implementing Queueable and Database.AllowsCallouts | | Integration status field recommendation | Custom field to track whether the async callout succeeded or needs retry | --- ## Related Skills - callouts-and-http-integrations — use for HTTP callout construction, Named Credentials, and mock testing patterns - apex-queueable-patterns — use for Queueable design, chaining, error handling, and monitoring - async-apex — use for choosing between @future, Queueable, Batch, and Scheduled Apex - trigger-framework — use when the callout boundary problem originates in trigger execution context --- ## Official Sources Used - Apex Developer Guide: Invoking Callouts Using Apex — https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_callouts.htm - Apex Developer Guide: Callout Limits and Limitations — https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_callouts_timeouts.htm - Salesforce Help: "You have uncommitted work pending" — https://help.salesforce.com/s/articleView?id=000389332 - Apex Developer Guide: Queueable Apex — https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_queueing_jobs.htm
Related Skills
callouts-and-http-integrations
Use when building, reviewing, or debugging outbound Apex HTTP callouts, Named Credentials, request/response handling, timeout behavior, or mock-based tests. Triggers: 'HttpRequest', 'Named Credential', 'callout exception', 'uncommitted work pending', 'HttpCalloutMock'. NOT for inbound Apex REST service design or non-HTTP integration architecture.
apex-queueable-patterns
Use when designing, implementing, reviewing, or debugging Queueable Apex jobs that chain, use the Finalizer interface, pass state across transactions, or need controlled async depth. Trigger keywords: 'Queueable', 'System.enqueueJob', 'Finalizer', 'QueueableContext', 'AsyncOptions', 'stack depth', 'chained queueable'. NOT for basic async Apex mechanism selection (use async-apex), NOT for large-volume record processing where Batch Apex is the right tool (use batch-apex-patterns).
transaction-security-policies
Transaction Security policy creation and configuration: condition builder, enhanced policies, enforcement actions (block, MFA, notification, end session), real-time monitoring mode, and policy troubleshooting. NOT for Event Monitoring log analysis or Shield Event Monitoring setup (use event-monitoring). NOT for Apex testing or debug-log analysis.
lwc-error-boundaries
Isolate component errors so one failure does not blank an entire page using errorCallback and graceful fallbacks. NOT for server-side Apex exception design.
mutual-tls-callouts
Configure mTLS for Apex callouts using Named Credentials with client certificate authentication. NOT for standard TLS or API key auth.
callout-limits-and-async-patterns
Use when designing or troubleshooting Apex callouts that approach governor limits: choosing between synchronous callouts, @future, Queueable, Continuation, or async chaining strategies. NOT for HTTP request construction or Named Credential setup (use named-credentials-setup).
flow-transactional-boundaries
Reason about when a Flow is inside the caller's transaction vs starts its own. Pick Before-Save vs After-Save vs Async Path vs Pause + Resume when transaction boundaries matter. Covers governor-limit sharing, DML sequencing, recoverability, and the exact semantics of each Flow entry point. NOT for choosing Flow vs Apex (use automation-selection.md). NOT for Flow-to-Flow invocation contracts (use subflows-and-reusability).
flow-transaction-finalizer-patterns
Use when a Flow needs to do work that must survive the triggering transaction — post-commit notifications, callouts, audit rows, or compensating actions. Covers Flow Transaction Control element, scheduled paths, Platform Event + finalizer escalation, and Apex Queueable finalizer bridging. Does NOT cover general Flow async decisions (see async-selection).
flow-http-callout-action
Call external HTTP APIs directly from Flow using HTTP Callout actions (no Apex), handling auth, schema, and errors. NOT for complex Apex-based integration logic.
apex-transaction-finalizers
Use this skill when you need guaranteed post-Queueable cleanup, retry, or failure-logging logic that must run even when the parent Queueable throws an unhandled exception. Trigger keywords: FinalizerContext, System.attachFinalizer, Queueable cleanup on failure, post-job compensation, guaranteed async cleanup. NOT for batch job completion callbacks — use apex-batch-chaining. NOT for platform event publishing on failure — use platform-events-apex.
apex-http-callout-mocking
HttpCalloutMock for Apex tests: HttpCalloutMock interface, StaticResourceCalloutMock, MultiStaticResourceCalloutMock, Test.setMock, multi-call mocks for pagination, error-path mocks. NOT for the callout code itself (use callouts-and-http-integrations). NOT for WSDL callouts (use apex-wsdl2apex-patterns).
apex-callout-retry-and-resilience
Strategy layer for resilient Apex HTTP callouts: bounded retry with backoff, queueable async retry chains, circuit-breaker via Platform Cache, idempotency keys, dead-letter pattern. NOT for callout authentication — see apex-named-credentials-patterns. NOT for transaction-boundary rules — see callout-and-dml-transaction-boundaries. NOT a re-write of the HttpClient template — this is the policy and orchestration layer that calls it.