Skip to main contentSkip to content
Blog
Deep diveCoding agents / Multi-agent / Claude Code / Codex / Cloudflare D1

Several Coding Agents, One Repository: Claims Instead of Locks

Parallel coding agents collide at merge time because none of them knows what the others are editing. Foxl Code's cloud agents are now told to claim the files they are about to change, and hear back which of your other live agents already claimed an overlapping path. A claim is recorded either way and nothing is locked; the agent is told to send the holder the change, wait, or ask you. Real output from the shipped command, the glob rules, the database cost, and the question still open: whether agents follow the rule.

Foxl Team11 min read

An isometric row of five file blocks on a board. Blue flags stand on the files one agent will change and orange flags on the other agent's; on the middle file, which both claimed, a folded note lies between a blue and an orange flag.
On this page
Reviewed against the shipped implementation and retained tests on October 23, 2026.

When several coding agents work in one repository at once, they collide at merge time, because no agent knows what the others are editing. Foxl Code, which runs coding agents such as Claude Code and Codex in the cloud, now gives each agent a claim command. Before editing, the agent is told to run foxl-agents claim <paths>. The answer names any other agent of the same user that is still running (a live agent) and has already claimed an overlapping path, along with the note that agent left. A claim is advisory and never a lock. On a conflict, the agent is told to send the claim's holder the change it needs, wait for the holder to release the claim, or ask the user. Claims end when the agent's run ends. The feature shipped in Foxl v0.9.3-beta.5.

Why do parallel coding agents collide?

Each agent works in its own checkout on its own branch, so nothing stops two of them from changing the same file. Git reports a textual conflict when the second pull request is merged, after both agents have finished and been paid for. A semantic conflict, where two pull requests each pass their checks but break the build once both are merged, is reported by nothing until a person merges both. Claims catch that kind only when the two agents touch the same paths.

We learned what helps from our own setup. We build Foxl with 15 to 20 Claude Code sessions running in parallel on its repository, and in 14 days they sent each other 4,773 messages. What kept them from overwriting each other was a shared ownership list. One coordinator session records which session owns which files (and, for a file many sessions touch, which parts of it). The rule is that a session sends a file's owner the change it wants instead of editing the file itself. Before the list, several sessions edited the same files at once and found out at merge. The list had its own cost: of the 123 times a session was blocked in those 14 days, 33 were waits for the coordinator to answer.

Foxl Code already had the messaging half: a foxl-agents command each cloud agent can use to list the user's other live agents and send them a message. Claims are the ownership list without the coordinator. Each agent says what it will touch, and the answer comes from the database in one request.

Why claims instead of locks?

A lock would need every agent to obey it and every agent to release it. A coding agent that crashes, runs out of budget or is cancelled does not release anything, so a lock needs a timeout, and a timeout long enough for a real run (ours can last over seven hours) blocks everyone for that long. Two agents that each need one line of the other's file would wait on each other forever, a deadlock. And a refusal gives the agent nothing to do next except stop.

Claims handle each of these differently. A crashed holder stops counting as soon as its run is no longer live, so there is no timeout to sit out. No claim is refused because it overlaps another; it is recorded either way, and the overlap is returned to the agent that asked, with the holder's task id, title, the paths it claimed and its note. And the agent that hits a conflict is told first to send the holder the change it needs, so two agents that need each other's files have a move other than waiting, though nothing stops both from choosing to wait. These are the instructions every agent receives, word for word:

- Before you start editing, claim the files and directories you expect to
  change, in one command: `foxl-agents claim <path>... --note "<why>"`. A
  directory claims everything under it; `*`, `?` and `**` globs work. Claim
  what you will change, not the whole repository, and claim again if your
  plan grows.
- The answer names any other live agent that already claimed an overlapping
  path. On a CONFLICT, do not edit those files: send the holder the exact
  change you need with `foxl-agents send <task-id> "<the change>"`, wait until
  it releases them (check `foxl-agents claims` at a natural checkpoint, never
  in a loop or behind a `sleep`), or tell the user. A claim is information,
  not a lock.
- `foxl-agents release <path>...` drops a claim you no longer need. Every
  claim you hold is dropped when your run ends.

These lines are appended to the user-level instructions file each CLI reads: CLAUDE.md for Claude Code, and AGENTS.md for Codex, Kiro, Cursor, Hermes and OpenCode. Because one mechanism has to reach six different CLIs, foxl-agents is a shell command rather than an MCP server. Every coding CLI can run a shell command, so one script works in all six without configuring each one to load a server.

What does an agent run, and what does it see?

Here is real output from the shipped command, run by a script rather than an agent, for two demo tasks in a demo repository: task A, a Codex task adding rate limiting, and task B, a Claude Code task adding pagination to the users endpoint. The server, its database and the command are the same ones the cloud agents get.

Terminal output. Task A claims src/api/middleware.ts and src/api/routes.ts with the note 'adding a token bucket per API key' and is told no other live agent claimed an overlapping path. Task B then claims the directory src/api/ and gets a CONFLICT naming task A, its title, the two files and its note, followed by the instruction to send the holder the change, wait for a release or tell the user. A listing of live claims shows both tasks' claims.
Figure 1. Captured output of the shipped command against the shipped server, running locally in workerd (Cloudflare's Workers runtime), 2026-10-23. Task B's claim is recorded too.

Task B's claim was src/api/ with a trailing slash, which marks a directory, and the server stored it as src/api/**. Without the slash, the command would have added /** on its own, because it runs inside the checkout, the one place that can tell a file from a directory. The command also rewrites every path relative to the repository root, whatever the working directory, and refuses a path outside the checkout before sending anything.

The conflict names task A's two files and its note, and the note is what lets task B decide. Here, task B could leave the middleware alone and send task A the one change it needs there, or start on the endpoint's own handler and check back once task A has released.

How do you tell whether two globs overlap?

Two claims overlap when at least one path matches both. The grammar is deliberately small:

  • a plain path is a glob with no wildcards;
  • * matches within one path segment;
  • ? matches exactly one character;
  • a segment that is exactly ** matches any number of whole segments;
  • every other character is literal, so [id].tsx is a file name, not a character class.

Here is what the shipped matcher returns for some example pairs:

claim Aclaim Boverlap
src/api/**src/api/users.tsyes
src/*.tssrc/api/users.tsno, * stays in one segment
src/**/*.test.tssrc/api/*.tsyes, both match src/api/x.test.ts
docs/**src/**no
src/app/[id]/page.tsxsrc/app/a/page.tsxno, [id] is literal
**/*.mddocs/guide.mdyes

Overlap never causes a refusal, but some claims are refused for their shape:

  • a glob made only of wildcards that includes **, such as ** or **/*, because it would claim the whole repository;
  • a .. path segment, a backslash or a control character;
  • a leading !, because it is not a negation here and should not look like one.

A task holds at most 50 claims, each at most 200 characters, with notes of up to 280 characters.

To bound the CPU an unusual claim set can cost the server, the comparison is capped. Two claims whose leading literal segments differ (docs/** and src/**) are ruled out at once. Otherwise the server does a memoized walk over pairs of segments and, within a segment, pairs of characters. All comparisons in one request share a budget of 50,000 steps, and a pair the budget never reaches is reported as a possible overlap rather than dropped. When the budget runs out, the server errs toward telling the agent.

When does a claim end?

A claim belongs to one run. When the run ends, the task drops every claim it made, in the same step that records the run as finished. Two checks at read time cover a run that never reached that step: a claim whose holder is no longer live is ignored, and so is a claim older than the longest a run can last, 7 hours 20 minutes. Claims have the same visibility as messages: only one user's live agents, working through the same GitHub App installation on the same repository, can see each other's claims.

Two agents and one claims table. Agent A claims two files and is told there is no overlap. Agent B claims the directory around them, its claim is written, and it gets back a conflict naming agent A. Agent B then has three choices: send agent A the change, wait for agent A's release, or ask the person. When a run ends, its claims are removed.
Figure 2. A conflict is returned to the agent that asked. Nothing is refused for overlapping, and nothing waits on a lock.

What does it cost on a free database plan?

Claims live in one table with one index on (user, repository, creation time), and nothing polls it. Every read asks only for claims created in the last 7 hours 20 minutes, and creation time is the index's last column, so older rows fall outside the range the database scans: unreleased stale claims add one row to a read, whether there are 1,000 or 2,000 of them (we measured both). That is the part worth copying if your claims table might grow.

We measure this because Foxl runs on Cloudflare's free plan, where every D1 database on an account shares 5 million rows read and 100,000 rows written a day, and the account gets 100,000 Worker requests a day. These figures come from D1 running locally in workerd, Cloudflare's runtime, and have not yet been confirmed against production D1's own metrics:

callrows readrows written
claim N pathsabout 2 per live claim on the repository, plus about 1 per path and a few fixed reads2 per path
list claims1 + 2 per live claim0
releaseabout 1 per path1 per released row
a run ends that never claimed00

At about 50 cloud tasks a day across all users, with two claim calls each, that is 100 calls a day. Each call is two Worker requests, one at code.foxl.ai and one over an internal hop to the server, so about 200 requests: 0.2% of the free plan's 100,000. Even if every one of those calls cost what a busy user's does at worst (ten agents holding five claims each on one repository), the day reads about 11,000 rows (0.2% of the 5 million) and writes about 830 (0.8% of the 100,000).

Do the agents actually follow the rule?

We do not know yet, and this phase is built to find out. Our own local sessions needed a coordinator to enforce the same rule, so we are not assuming that six different CLIs will claim before editing and step back on a conflict because their instructions say so. Every conflict writes an audit record (each server instance skips a repeat for the same task and holders within ten minutes), and so does every message one agent sends another. Whether an agent messaged the holder shows in the audit log; whether it released its claim shows in the claims table; whether it edited the file anyway shows in the pull request's diff.

The next phase depends on that answer. If agents do respect claims, the plan is for the part of Foxl Code that splits one request into several agents to give each subtask a file list, and to run subtasks that overlap one after another instead of at the same time. As of 2026-10-23 the production table is empty, because no cloud task has started since the feature was deployed.

If you run parallel agents of your own, copy three properties: a claim is always recorded, an overlap comes back with a note and a holder to talk to, and a claim lives only as long as its run. Our post from Foxl Code's launch covers the agent loop itself, which reconciles toward a goal instead of trusting each step.

References and further reading

  1. Cloudflare D1 pricing and Free plan limitsDocumentation
  2. Foxl v0.9.3-beta.5 releaseRelease