Tacnode™
Back to Blog
AI Engineering

Agentic TOCTOU: When an Agent Checks, Then Acts on Changed State

Agentic TOCTOU: an AI agent checks state, reasons for seconds, then acts after it changed. The research behind the term, the shared-state case, and the fixes.

Haitao Wang
Haitao Wang
Engineering
11 min read
An agent’s loop drawn as a circle of check, reason and act, with a long reasoning arc and another writer’s change landing inside it, so the act runs on state that no longer matches the check.

TL;DR: Agentic TOCTOU is a time-of-check to time-of-use bug in an AI agent: it checks a condition, reasons, and acts after the condition changed. Studies in 2025 and 2026 found the flaw in tool-calling and browser-use agents, because state can change between when the agent plans and when it acts. The same thing happens on shared state, where another agent changes a balance or a claim mid-reasoning. Re-checking before acting narrows the window. Checking and acting in one transaction closes it, for state the transaction can cover.

Take an illustrative case. Say an agent is told to buy a part if it’s in stock and under $400. It queries inventory: in stock, $380. It reasons about shipping options for a few seconds, picks one, and places the order. By then the last unit sold to another buyer and the price was updated to $420. The order goes through at the new price, or fails halfway, or reserves stock that isn’t there.

The agent did everything right. It checked before it acted. The problem is the gap in time between the two, and with agents that gap is not an instruction or two. It is a model call.

This failure has a name in security engineering, and it now has a name in agent research too. This post covers where the term comes from, why agents make it worse, and the fixes in order of how much of the window each one closes.

What Is Agentic TOCTOU?

Agentic TOCTOU is a time-of-check to time-of-use flaw in an AI agent: the agent observes state, reasons over it, then acts after that state has changed, so the action rests on a check that is no longer true. The window spans the agent’s reasoning, often seconds long, and can be triggered by ordinary concurrency or by an adversary.

The underlying weakness is catalogued as CWE-367: “The product checks the state of a resource before using that resource, but the resource’s state can change between the check and the use in a way that invalidates the results of the check.” The classic example is a program that checks a file’s permissions, then opens it, while an attacker swaps the file for a link in between.

Where the Agent Research Comes From

Two papers are the place to start. The first, Mind the Gap: Time-of-Check to Time-of-Use Vulnerabilities in LLM-Enabled Agents by Derek Lilienthal and Sanghyun Hong, submitted to arXiv in August 2025, describes itself as “the first study of TOCTOU vulnerabilities in LLM-enabled agents.” It looks at tool-calling agents and gets its own section below.

The second, Atomicity for Agents: Exposing, Exploiting, and Mitigating TOCTOU Vulnerabilities in Browser-Use Agents by Linxi Jiang, Zhijie Liu, Haotian Luo and Zhiqiang Lin, submitted to arXiv in February 2026, studies browser agents. They observe a page through the DOM or a screenshot, plan an action, and execute it. In the authors’ words, “web pages often change between planning and execution, causing agents to execute actions based on stale assumptions.” Dynamic or adversarial content can use that window to make the agent click something it never chose.

They tested 10 popular open-source browser agents and report that all 10 fail on at least one manipulation type. Their mitigation is pre-execution validation: while the agent plans, monitors record DOM structure edits, attribute updates and layout shifts, and immediately before the action runs, a validator checks whether anything relevant changed. The full paper reports the trigger ratio dropping to 0% in its tests, with overhead below 0.05 seconds per loop, against the several seconds a single LLM reasoning step typically takes.

The second paper’s title is the useful part for everyone else: atomicity. The fix for TOCTOU has always been to stop the check and the use from being two separate steps.

TOCTOU in Tool-Calling Agents

The 2025 paper looks at agents that call tools in sequence, using tasks from the AgentDojo banking, Slack, travel and workspace environments. Its benchmark, TOCTOU-Bench, has 66 realistic user tasks, and the authors “find that 56 of the 66 tasks contain a possible TOCTOU vulnerability” (full paper). The vulnerable shapes are ordinary pairs of calls. The paper’s examples include reading a web page and then posting what it said to another site, looking up a user’s info and then updating their password, and searching for a file by name and then deleting or sharing it.

They test three defenses: rewriting prompts to discourage plans built on vulnerable call sequences, monitoring tool-call sequences at runtime for known vulnerable pairs, and a tool fuser that replaces a vulnerable pair with “fused tools that execute atomically as a single operation.” In the Slack environment, the fuser cut the average time between the two calls from 1.70 seconds to 0.07 seconds (full paper). All three together reduced the TOCTOU vulnerabilities in executed trajectories “from 12% to 8%” (abstract).

The fuser is the defense to notice. Turning a check and a use into one operation is the same move as the conditional write later in this post. The paper’s own numbers show what fusion leaves: the window shrank to 0.07 seconds, not to zero, and the example fused tool in its first figure still makes the read call and then the post call. Our read is that as long as a tool calls two APIs in sequence, another writer can land between them, so closing the gap takes the system that holds the state doing the check itself.

Why Agents Widen the Window

In ordinary code, the time between a check and a use is a few instructions. For an agent, the check goes into a prompt and the use comes out after the model has reasoned, possibly called other tools, and generated a plan. The Atomicity for Agents paper notes that a single LLM reasoning step typically takes several seconds.

That changes two things. Ordinary concurrency, with no attacker at all, now regularly lands inside the window: another agent, a human user or a batch job writes during those seconds. And the window is predictable, which makes it easier to aim at.

Agents also tend to separate the check and the act into different tools. get_inventory and place_order are two calls. Nothing ties the second to the result of the first unless you build that in.

Beyond the Browser: TOCTOU on Shared State

The browser is one surface. Any state an agent reads and later acts on has the same exposure. Three common ones:

Here is the budget case with illustrative numbers.

text
t = 0.0 s   Agent A: get_budget(team=42) -> remaining = 500
t = 0.2 s   Agent B: get_budget(team=42) -> remaining = 500
t = 9.4 s   Agent A: submit_purchase(amount=350)   -> ok
t = 11.0 s  Agent B: submit_purchase(amount=300)   -> ok
            Spent 650 against a budget of 500

When the window is opened by another agent rather than an attacker, it’s a race condition between agents, covered in Race Conditions Between AI Agents. In a large agent swarm, it’s the normal case.

StateCheckActWhat changes in between
A spend budgetRemaining budget covers the purchaseSubmit the purchaseAnother agent spends from the same budget
A task claimNo one owns the ticketStart working and mark it ownedAnother agent claims it
An account’s statusAccount is in good standingIssue a credit increaseA fraud flag lands on the account

The Classic Fix: Don’t Check, Then Use

The classic case is in operating systems, and their advice carries over unchanged. The Linux access(2) man page warns that using it to check permission before calling open(2) “creates a security hole, because the user might exploit the short time interval between checking and opening the file to manipulate it.” The fix it recommends is not a faster check. It is to “temporarily switch the process’s effective user ID to the real ID and then call open(2)” (access(2)), so the operation enforces the permission itself.

CWE-367 puts it more bluntly: “The most basic advice for TOCTOU vulnerabilities is to not perform a check before the use.” Where a separate check can’t be avoided: “Ensure that locking occurs before the check, as opposed to afterwards, such that the resource, as checked, is the same as it is when in use” (CWE-367).

For agents, that maps directly. Don’t rely on get_budget followed by submit_purchase; make the purchase enforce the budget. Where the check has to be separate, take the hold as part of the check, not after it. The fixes below are those two ideas, applied to shared state.

Three Fixes, From Narrowest to Atomic

Each fix shrinks the gap between check and act. The last two remove it, for state the store holds.

1. Re-check before acting. Read the state again right before the act and abort if it changed. This is the shape of the browser-agent paper’s pre-execution validation, and on a browser page it’s the right tool, since you can’t wrap a page in a transaction. On shared state it shrinks the window from seconds to the gap between the re-check and the write, which is small but not zero. Two agents can both re-check, both pass and both write.

2. Make the act conditional. Pass what you checked into the act, and have the act succeed only if it still holds. This is compare-and-set.

sql
UPDATE budgets
SET    remaining = remaining - 350
WHERE  team_id = 42
  AND  remaining >= 350
RETURNING remaining;
-- zero rows: budget no longer covers it; tell the agent, don't retry blindly

The check and the act are now one statement. Agent B’s write finds remaining = 150 and returns nothing.

3. Check and act in one transaction. When the check spans more than one row, such as “this team’s spend plus open holds stays under budget,” put the reads and the write in one transaction at serializable isolation. In PostgreSQL, Serializable makes concurrent transactions behave as if they ran one at a time; when two conflict, one is rolled back with a serialization error and the application retries it.

sql
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT b.limit_amount
       - COALESCE(SUM(h.amount), 0) AS available
FROM   budgets b
LEFT JOIN holds h ON h.team_id = b.team_id AND h.status = 'open'
WHERE  b.team_id = 42
GROUP  BY b.limit_amount;
-- application: proceed only if available >= 350
INSERT INTO holds (team_id, amount, agent_id, status)
VALUES (42, 350, 'agent-a', 'open');
COMMIT;

Keep the model call outside the transaction. Let the agent reason on a plain read, then run the short transaction that re-checks and writes. Holding a transaction open across a 10-second model call makes serialization failures far more likely, and any row locks it holds block other writers for the whole call.

Three rows, one per fix, each showing a check and an act on a timeline. Re-check before acting leaves a short gap where another write can land. A conditional write fuses check and act into one statement. A transaction fuses a multi-row check and the act into one commit.

When the Act Hits a System You Don’t Control

Often the act is an API call to someone else’s system: a payment processor, a cloud provider, a vendor. You can’t put that call inside your transaction. Three patterns help.

Reserve first. Write a hold in a store you control, inside the atomic check above, then make the external call, then confirm or release the hold. Your agents can’t oversubscribe what they’ve reserved, even though the external system knows nothing about the reservation.

Use idempotency keys. If the external call is retried, the receiver should recognize the key and not repeat the action. See idempotency.

Plan the compensation. If the external call succeeds and your confirm fails, you need an undo step. That’s the saga pattern.

None of these make the external system’s own state atomic with yours. They make your agents’ side of it consistent.

Where Tacnode Fits

The fixes above need a store where the check and the act can share a transaction, and where the check sees all the context the decision depends on.

The Tacnode Context Lake™ holds the decision context agents check against and the decisions they write. For state Tacnode holds, such as holds, budgets your agents draw down, per-agent limits, task claims and agent actions, the check and the act run in one ACID transaction with serializable isolation by default. That is the third fix above, applied to the state agents contend on.

For data your own systems hold, such as account records or orders that arrive by change data capture, Tacnode gives the check an internally coherent view: raw rows and derived values like spend this month, computed against the same set of ingested events. It doesn’t make an external ledger atomic with your agent’s act. The reserve-first pattern keeps your agents’ side consistent, with the hold in Tacnode.

Every hold, verdict and action the agent writes becomes context for the next agent’s check, which is what makes the second agent’s check see the first agent’s hold.

FAQ

AI AgentsTOCTOURace ConditionAgent SecurityTransactions
Haitao Wang

Written by Haitao Wang

Nearly two decades at Microsoft, including the SQL Server engine. Leads engineering for the Tacnode Context Lake™.

Fresh Context Newsletter

The argument behind the posts — what breaks when AI acts on stale data, from the team building the Context Lake. About twice a month.

Subscribe on LinkedIn

Ready to see Tacnode Context Lake™ in action?

Book a demo and discover how Tacnode can power your AI-native applications.

Book a Demo