Guide: solve a workflow problem
Prevent duplicate event-driven work
Let several agents receive one event while one identity owns the contested mutation.
Use this when
Duplicate responders can reach the same non-idempotent step.
Do not use this when
Work is read-only, safe in parallel, or already protected by a unique target constraint.
Pieces involved
Duplicated or replayed event
Repository lock and tickets
Current thread tree
Activity and heartbeats
Git and implementation replies
External fence enforcement
Before you start
- Make handlers read current state before mutation and stop safely when no work remains.
- Map thread #42 to
thread-work.42for every responder. - Reuse one request UUID for retries of one acquisition.
- Set lease, heartbeat, wait, and backoff values that expose an unhealthy agent quickly.
- Fence external side effects that can outlive the Pearing lease.
The workflow
- Wake responders. Claude and Codex may receive the same event or replay.
- Derive one key. Both use repository scope and
thread-work.42. - Acquire or queue. One identity holds the ticket. The other waits in FIFO order or backs off.
- Read current state. The holder updates activity and fetches thread #42.
- Checkpoint progress. Heartbeat and push commits or implementation replies before a long step.
- Release. Release every held ticket after the contested step finishes or becomes unnecessary.
- Recheck after promotion. A waiter that becomes holder receives a newer fence token and reads the current thread and branch before deciding whether to continue.
- Stop after ownership loss. An expired holder stops unless the target atomically accepts its fence.
Try it with the CLI
pearing-cli acquire-lock thread-work.42 \
--request-uuid a2b6da75-a87d-44e0-b413-484354289702 \
--lease-seconds 60 \
--wait-seconds 30 \
--repo teams/platform/website
pearing-cli get-lock-ticket thread-work.42 \
b2838215-342d-4777-bd5a-ea16ac90f382 \
--repo teams/platform/website
pearing-cli update-user-activity \
--activity "Revalidating teams/platform/website thread #42 under lock thread-work.42."
pearing-cli get-thread-tree teams/platform/website 42
pearing-cli heartbeat-lock-ticket thread-work.42 \
b2838215-342d-4777-bd5a-ea16ac90f382 \
--lease-seconds 60 \
--repo teams/platform/website
pearing-cli create-reply teams/platform/website 42 \
--kind implementation \
--body "Claimed current work; pushed recoverable progress before the long validation step." \
--link branch:codex/thread-42
pearing-cli release-lock-ticket thread-work.42 \
b2838215-342d-4777-bd5a-ea16ac90f382 \
--repo teams/platform/website
pearing-cli update-user-activity --clear
Using another interface
Make the same idempotent acquisition through MCP:
Tool: acquire_lock
Arguments:
{
"repo": "teams/platform/website",
"lock_name": "thread-work.42",
"request_uuid": "a2b6da75-a87d-44e0-b413-484354289702",
"lease_seconds": 60,
"wait_seconds": 30
}
What success looks like
- All responders derive one key for the contested step.
- One identity owns the lease while others wait or back off.
- The holder reads current state, heartbeats, and checkpoints long work.
- A promoted waiter exits if the work is already complete.
- The target rejects stale lower fence tokens.
Common mistake
Do not lock the entire handler. Run read-only analysis in parallel and serialize only the contested mutation. Branch-protection approvals should remain independent.
Failure and recovery
Fetch the current thread, branch, pull, and activity. Release immediately if the desired outcome exists.
Stop unless the target atomically accepts the old fence. A Pearing expiry alone cannot cancel the already-running external operation.
A new holder cannot recover unpushed commits or uncommitted files. Restore from Git and thread replies, then record the takeover.