You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When multiple AI agents run Spec Kit pipelines concurrently within the same repository checkout (e.g., Antigravity subagents, Claude Code sub-agents, or parallel Cursor Composer tabs), they share a single .specify/feature.json file as their feature context pointer. This creates a write-write race condition:
Timeline:
t0 Agent-A: SPECIFY_FEATURE_DIRECTORY="specs/003-auth" → setup-plan.sh
→ get_feature_paths() persists "specs/003-auth" to feature.json ✓
t1 Agent-B: SPECIFY_FEATURE_DIRECTORY="specs/004-perf" → setup-tasks.sh
→ get_feature_paths() persists "specs/004-perf" to feature.json ← overwrites A's value
t2 Agent-A: (new process, no env var) → check-prerequisites.sh
→ reads feature.json → resolves "specs/004-perf" ← WRONG FEATURE
The root issue: get_feature_paths() in common.sh (L191-192) always persistsSPECIFY_FEATURE_DIRECTORY back to feature.json unless the caller explicitly passes --no-persist. Since most scripts (setup-plan.sh, setup-tasks.sh) call get_feature_paths without --no-persist, every agent invocation silently overwrites the shared singleton.
Impact
Silent cross-contamination: Agent B's plan/tasks get written into Agent A's feature directory (or vice versa) without any error signal.
Non-reproducible failures: The behavior depends on timing — sometimes it works, sometimes it doesn't, making debugging extremely difficult.
We encountered this in TTZip (a macOS archive utility, 525+ tests, 28 design patterns) while running Antigravity subagents to parallelize a sorting-bugfix TDD suite alongside a 7z compression optimization. Both agents used SPECIFY_FEATURE_DIRECTORY correctly in their own processes, but the persist-on-read side effect in get_feature_paths caused each agent to clobber the other's feature.json entry on every script call.
Root Cause Analysis
The feature resolution chain in common.shget_feature_paths() (L163-231) has a correct read priority:
1. SPECIFY_FEATURE_DIRECTORY env var (explicit override)
2. .specify/feature.json (persisted fallback)
3. Error (no context)
But it has an unconditional write side effect on the env-var branch (L191-192):
if [[ "$no_persist"!=true ]];then
_persist_feature_json "$repo_root""$SPECIFY_FEATURE_DIRECTORY"fi
The --no-persist guard (added in #3025) is a function-level parameter, not an environment-level control. Scripts that are "just resolving paths" but don't know they should pass --no-persist (like setup-plan.sh, setup-tasks.sh) trigger the persist unconditionally.
What already works
Credit to the maintainers — the infrastructure for multi-agent isolation is already in place:
What's missing is the guidance layer: documentation, agent skill instructions, and an environment-level no-persist toggle.
Proposed Solution
1. Official multi-agent documentation (docs/multi-agent.md)
A new document covering:
The race condition scenario (as above)
The Multi-Agent Isolation Protocol: always inject SPECIFY_FEATURE_DIRECTORY per-process, never rely on feature.json for read
Integration-specific examples (Antigravity subagents, Claude Code sub-agents, Cursor multi-tab, CI matrix)
FAQ: "Do I need git worktrees?" → No, env-var isolation is sufficient for same-checkout concurrency
2. Update agent skill templates to stop instructing direct feature.json writes
Currently, the specify command template (the upstream equivalent of speckit-specify/SKILL.md) instructs agents to:
Persist the resolved path to .specify/feature.json: {"feature_directory": "<resolved feature dir>"}
This instruction should be replaced with:
Pass the resolved feature directory to downstream commands via SPECIFY_FEATURE_DIRECTORY environment variable prefix. Example: SPECIFY_FEATURE_DIRECTORY="specs/003-auth" .specify/scripts/bash/setup-plan.sh --json
The feature.json persistence should remain as an automatic side effect of get_feature_paths() for single-agent backward compatibility, but agents should not be told to write it directly (which bypasses the script's own idempotency guards in _persist_feature_json).
Add an environment-level equivalent of the --no-persist function parameter:
# In get_feature_paths(), after the --no-persist argument check (L167-171):if [[ "${SPECIFY_NO_PERSIST:-}"=="1"||"${SPECIFY_NO_PERSIST:-}"=="true" ]];then
no_persist=true
fi
This allows CI pipelines and agent orchestrators to set SPECIFY_NO_PERSIST=1 globally, ensuring that no script invocation can accidentally write feature.json — even scripts that don't pass --no-persist internally.
Backward Compatibility
This proposal is fully backward compatible:
Scenario
Before
After
Single agent, no env var
Reads feature.json
Identical behavior
Single agent, with env var
Reads env var, persists to feature.json
Identical behavior
Multi-agent, each sets env var
Race on feature.json (bug)
Each agent's reads are short-circuited by env var; persistence is harmless
Multi-agent + SPECIFY_NO_PERSIST=1
N/A
No feature.json writes at all
specify integration upgrade
Overwrites managed files
docs/multi-agent.md is not in manifest; protocol rules live in user-space
No existing scripts, templates, or workflows change behavior. The persist side effect is still there (it's a "last writer wins" overwrite that's harmless when every reader uses env vars). SPECIFY_NO_PERSIST is strictly additive.
Reference Implementation
We've been running this protocol in production at TTZip with Antigravity (Google DeepMind's agentic coding tool) subagents. Our implementation consists of:
Project-level rule file (.agents/rules/speckit-multiagent.md): Instructs all agents to inject SPECIFY_FEATURE_DIRECTORY per-process and never read/write feature.json directly.
Global user rule: Gates (hard state machine gating) that prevent any agent from writing production code before spec/plan/tasks artifacts exist under the declared feature directory.
Concurrent verification: Validated that two agents operating on specs/003-sorting-fix/ and specs/006-7z-conquest/ simultaneously produce zero cross-contamination.
The protocol adds zero overhead to single-agent workflows and requires no upstream code changes to function — it's purely a documentation and guidance contribution. The optional SPECIFY_NO_PERSIST env var is a small, additive improvement to common.sh.
Problem Statement
When multiple AI agents run Spec Kit pipelines concurrently within the same repository checkout (e.g., Antigravity subagents, Claude Code sub-agents, or parallel Cursor Composer tabs), they share a single
.specify/feature.jsonfile as their feature context pointer. This creates a write-write race condition:The root issue:
get_feature_paths()incommon.sh(L191-192) always persistsSPECIFY_FEATURE_DIRECTORYback tofeature.jsonunless the caller explicitly passes--no-persist. Since most scripts (setup-plan.sh,setup-tasks.sh) callget_feature_pathswithout--no-persist, every agent invocation silently overwrites the shared singleton.Impact
/speckit-implement-waves([Feature]: /speckit-implement-waves => run each phase in a subagent to prevent context rot #3507), which propose running phases in parallel subagents, cannot work safely without solving this shared-state problem first.Real-world reproduction
We encountered this in TTZip (a macOS archive utility, 525+ tests, 28 design patterns) while running Antigravity subagents to parallelize a sorting-bugfix TDD suite alongside a 7z compression optimization. Both agents used
SPECIFY_FEATURE_DIRECTORYcorrectly in their own processes, but the persist-on-read side effect inget_feature_pathscaused each agent to clobber the other'sfeature.jsonentry on every script call.Root Cause Analysis
The feature resolution chain in
common.shget_feature_paths()(L163-231) has a correct read priority:But it has an unconditional write side effect on the env-var branch (L191-192):
The
--no-persistguard (added in #3025) is a function-level parameter, not an environment-level control. Scripts that are "just resolving paths" but don't know they should pass--no-persist(likesetup-plan.sh,setup-tasks.sh) trigger the persist unconditionally.What already works
Credit to the maintainers — the infrastructure for multi-agent isolation is already in place:
SPECIFY_FEATURE_DIRECTORYenv var priority--no-persistread-only resolutionCURRENT_BRANCHfallback from feature dir basenameSPECIFY_INIT_DIRfor monorepo project scopingWhat's missing is the guidance layer: documentation, agent skill instructions, and an environment-level
no-persisttoggle.Proposed Solution
1. Official multi-agent documentation (
docs/multi-agent.md)A new document covering:
SPECIFY_FEATURE_DIRECTORYper-process, never rely onfeature.jsonfor read2. Update agent skill templates to stop instructing direct
feature.jsonwritesCurrently, the
specifycommand template (the upstream equivalent ofspeckit-specify/SKILL.md) instructs agents to:This instruction should be replaced with:
The
feature.jsonpersistence should remain as an automatic side effect ofget_feature_paths()for single-agent backward compatibility, but agents should not be told to write it directly (which bypasses the script's own idempotency guards in_persist_feature_json).3. (Optional)
SPECIFY_NO_PERSISTenvironment variableAdd an environment-level equivalent of the
--no-persistfunction parameter:This allows CI pipelines and agent orchestrators to set
SPECIFY_NO_PERSIST=1globally, ensuring that no script invocation can accidentally writefeature.json— even scripts that don't pass--no-persistinternally.Backward Compatibility
This proposal is fully backward compatible:
feature.jsonfeature.jsonfeature.json(bug)SPECIFY_NO_PERSIST=1feature.jsonwrites at allspecify integration upgradedocs/multi-agent.mdis not in manifest; protocol rules live in user-spaceNo existing scripts, templates, or workflows change behavior. The persist side effect is still there (it's a "last writer wins" overwrite that's harmless when every reader uses env vars).
SPECIFY_NO_PERSISTis strictly additive.Reference Implementation
We've been running this protocol in production at TTZip with Antigravity (Google DeepMind's agentic coding tool) subagents. Our implementation consists of:
.agents/rules/speckit-multiagent.md): Instructs all agents to injectSPECIFY_FEATURE_DIRECTORYper-process and never read/writefeature.jsondirectly.specs/003-sorting-fix/andspecs/006-7z-conquest/simultaneously produce zero cross-contamination.The protocol adds zero overhead to single-agent workflows and requires no upstream code changes to function — it's purely a documentation and guidance contribution. The optional
SPECIFY_NO_PERSISTenv var is a small, additive improvement tocommon.sh.Related Issues
/speckit-implement-waves: Needs this isolation protocol as a prerequisite for safe parallel phase executiongit worktreeSupport for Concurrent/Parallel Agent Execution #1476 — Git worktree isolation: Our approach is complementary (env-var isolation within a single checkout vs. filesystem isolation across worktrees)--no-persistfor read-only resolution: Foundation we build onCURRENT_BRANCHfallback: Foundation we build onComponent
Core scripts (
common.sh), Documentation, Agent skill templates