From pm
Atomic task locking patterns to prevent race conditions in multi-agent environments. Use when working with task locks, concurrent operations, acquire_task_lock, update_task_status, or debugging lock conflicts.
How this skill is triggered — by the user, by Claude, or both
Slash command
/pm:task-lockingThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill guides proper use of atomic task locking to prevent race conditions in multi-agent workflows.
This skill guides proper use of atomic task locking to prevent race conditions in multi-agent workflows.
In multi-agent systems, multiple agents can try to work on the same task simultaneously. Without proper locking, you get race conditions:
Agent A reads task (unlocked) ─┐
Agent B reads task (unlocked) ─┤ Race condition!
Agent A acquires lock ─┤ Both think they got it
Agent B acquires lock ─┘ Data corruption!
Atomic locking prevents this with a single database operation that's guaranteed to succeed for only ONE agent.
# GOOD - Atomic single UPDATE (only ONE agent succeeds)
cursor.execute("""
UPDATE tasks SET lock_holder = ?, lock_expires_at = ?
WHERE id = ? AND (lock_holder IS NULL OR lock_expires_at < ?)
""", (agent_id, expires_at, task_id, current_time))
success = cursor.rowcount > 0 # Only 1 agent gets rowcount=1
# BAD - Race condition (don't do this!)
if not is_locked(task_id): # Agent A checks: not locked
set_lock(task_id, agent_id) # Agent B can check before A sets!
The PM Dashboard locking system has been stress-tested:
This is proven to work under extreme concurrency.
Use update_task_status() for simple workflows:
# Automatically acquires lock when moving to IN_PROGRESS
update_task_status(
task_id="42",
status="IN_PROGRESS",
agent_id="claude"
)
# Lock acquired automatically!
# Work on task...
# Automatically releases lock when moving to REVIEW or DONE
update_task_status(
task_id="42",
status="REVIEW",
agent_id="claude"
)
# Lock released automatically!
Benefits:
When to use: Most of the time!
Use explicit lock/unlock for complex coordination:
# Agent explicitly acquires lock
success = acquire_task_lock(
task_id="42",
agent_id="implementation-agent",
timeout=300 # 5 minutes
)
if success:
# Do work...
# Explicitly release when done
release_task_lock(
task_id="42",
agent_id="implementation-agent"
)
Benefits:
When to use: RA-Full mode, multi-agent workflows, complex coordination.
1. Task created (unlocked)
↓
2. update_task_status(IN_PROGRESS) → Auto-acquires lock
↓
3. Agent works on task (lock held)
↓
4. update_task_status(REVIEW) → Auto-releases lock
↓
5. Reviewer validates (can acquire for review)
↓
6. update_task_status(DONE) → Fully unlocked
Default timeout: 300 seconds (5 minutes)
Auto-cleanup:
Custom timeout:
acquire_task_lock(
task_id="42",
agent_id="long-running-agent",
timeout=1800 # 30 minutes for complex work
)
success = acquire_task_lock(task_id="42", agent_id="agent-A")
if not success:
# Check who has the lock
lock_status = get_task_lock_status(task_id="42")
if lock_status["is_locked"]:
print(f"Task locked by: {lock_status['lock_holder']}")
print(f"Expires at: {lock_status['lock_expires_at']}")
# Options:
# 1. Wait for lock to expire
# 2. Work on different task
# 3. Coordinate with lock holder
# Lock acquired with 5-minute timeout
acquire_task_lock(task_id="42", agent_id="slow-agent", timeout=300)
# ... work takes 10 minutes (timeout exceeded!) ...
# Try to update status
update_task_status(task_id="42", status="DONE", agent_id="slow-agent")
# ❌ Fails: "Task must be locked by agent to update status"
# Solution: Re-acquire lock before finishing
success = acquire_task_lock(task_id="42", agent_id="slow-agent")
if success:
update_task_status(task_id="42", status="DONE", agent_id="slow-agent")
RA-Full mode with multiple agents:
# Main orchestrator creates task
create_task(name="Complex feature", ra_mode="ra-full", ra_score="9")
# Deploy survey agent
acquire_task_lock(task_id="99", agent_id="survey-agent", timeout=600)
# Survey agent gathers context...
release_task_lock(task_id="99", agent_id="survey-agent")
# Deploy planning agent
acquire_task_lock(task_id="99", agent_id="planning-agent", timeout=600)
# Planning agent creates plan...
release_task_lock(task_id="99", agent_id="planning-agent")
# Deploy implementation agent
acquire_task_lock(task_id="99", agent_id="impl-agent", timeout=1800)
# Implementation agent codes...
release_task_lock(task_id="99", agent_id="impl-agent")
# Deploy verification agent
acquire_task_lock(task_id="99", agent_id="verify-agent", timeout=600)
# Verification agent reviews...
update_task_status(task_id="99", status="DONE", agent_id="verify-agent")
# Auto-releases on DONE
Only the lock holder can:
# Agent A has the lock
acquire_task_lock(task_id="42", agent_id="agent-A")
# Agent B tries to update
update_task_status(task_id="42", status="DONE", agent_id="agent-B")
# ❌ Fails: "Task is locked by agent-A"
# Only Agent A can update
update_task_status(task_id="42", status="DONE", agent_id="agent-A")
# ✅ Success
update_task_status) for simple workflows# Auto-lock pattern (preferred)
update_task_status(task_id, "IN_PROGRESS", agent_id)
# ... quick work ...
update_task_status(task_id, "DONE", agent_id)
# Manual lock with custom timeout
acquire_task_lock(task_id, agent_id, timeout=1800) # 30 min
try:
# ... long complex work ...
finally:
release_task_lock(task_id, agent_id) # Always release!
# Check if available first
lock_status = get_task_lock_status(task_id)
if not lock_status["is_locked"]:
success = acquire_task_lock(task_id, agent_id)
if success:
# Do work
pass
Cause: Lock expired or never acquired Solution: Check timeout, re-acquire if needed
Cause: Another agent has the lock Solution: Wait for expiration or coordinate
Cause: Agent crashed or forgot to release Solution: Wait for auto-cleanup (timeout expiration)
The atomic locking is implemented with a single UPDATE:
UPDATE tasks
SET lock_holder = ?, lock_expires_at = ?, updated_at = ?
WHERE id = ?
AND (lock_holder IS NULL OR lock_expires_at < ?)
Why this works:
rowcount > 0 tells you if YOU got the lockSimple workflows: Use update_task_status() auto-locking
Complex workflows: Use acquire_task_lock() + release_task_lock() manually
Always:
The PM Dashboard locking system is production-ready and proven under concurrent load.
npx claudepluginhub p/commands-com-pm-claude-pluginPatterns for decomposing complex work into trackable tasks with dependency chains using TaskCreate, TaskUpdate, TaskGet, TaskList. Use for multi-step implementations and parallel coordination.
Manages task claiming and file ownership tracking for parallel agent execution. Agents claim tasks from a shared queue with ownership enforcement to prevent conflicts.
Manage shared task lists for agent teams: create tasks, set dependencies, claim work, track progress. Use when coordinating work items or building task pipelines.