Repository navigation
Description
Bug: two-layer squad upgrade recreates .squad/casting/ state in the working tree, while the installed hooks treat it as orphan-branch-only state
When using the two-layer state backend, squad upgrade --state-backend two-layer installs the Git hooks correctly, but it also recreates the following files in the normal working tree:
.squad/casting/history.json
.squad/casting/policy.json
.squad/casting/registry.json
This appears inconsistent with the pre-commit hook installed by Squad.
Installed pre-commit behavior
The generated pre-commit hook contains:
# WI-1: Guard against accidentally committing two-layer mutable state into the
# working tree. If the user has staged any .squad/ paths that are owned by the
# two-layer/orphan backend (decisions.md, agents/*/history.md, casting/, routing/),
# warn and abort so the state stays on the squad-state orphan branch.
if [ -z "$SQUAD_SYNC_ACTIVE" ]; then
STAGED=$(git diff --cached --name-only 2>/dev/null | grep -E '^\.squad/(decisions\.md|agents/.+/history\.md|casting/|routing/)' || true)
if [ -n "$STAGED" ]; then
echo "⚠ squad pre-commit: refusing to commit two-layer state into the working tree." >&2
echo " These paths belong on the 'squad-state' orphan branch, not in your normal commits:" >&2
echo "$STAGED" | sed 's/^/ /' >&2
echo " Use 'git restore --staged <path>' to unstage, or set SQUAD_SYNC_ACTIVE=1 to bypass." >&2
exit 1
fi
fiThe hook therefore explicitly considers all of .squad/casting/ to belong on the squad-state orphan branch, not in normal commits.
However, immediately after running:
squad upgrade --state-backend two-layerthe upgrade process recreates:
.squad/casting/history.json
.squad/casting/policy.json
.squad/casting/registry.json
in the normal working tree.
This leaves the repository in a confusing state where Squad itself creates files that its own hook says should not exist as normal working-tree state.
There is a second inconsistency in squad.agent.md
The current .squad-templates/squad.agent.md also explicitly lists these files as cast sources:
## Cast sources
- `.squad/casting/registry.json`
- `.squad/casting/history.json`
- `.squad/casting/policy.json`The current source-of-truth documentation similarly identifies these paths as authoritative/derived casting state.
This raises an important question for the two-layer backend:
Where is an agent supposed to read these files from?
If .squad/casting/* is mutable two-layer state and belongs only on the squad-state orphan branch, those files should normally not be present in the checked-out working tree.
But the generated agent instructions currently point directly to working-tree paths such as:
.squad/casting/registry.json
That seems incompatible with the storage semantics enforced by the hook.
Steps to reproduce
- Clone an existing Squad repository configured to use the two-layer backend.
- Ensure the existing
squad-statestate is available. - Run:
squad upgrade --state-backend two-layer- Observe that Squad installs the expected Git hooks.
- Observe that Squad also creates:
.squad/casting/history.json
.squad/casting/policy.json
.squad/casting/registry.json
in the working tree.
- Stage one of those files.
- Attempt to commit.
The Squad pre-commit hook refuses the commit because .squad/casting/ is classified as two-layer state belonging on squad-state.
Expected behavior
The two-layer model should have one consistent source of truth.
If casting/ is mutable state owned by squad-state, then:
squad upgrade --state-backend two-layershould not recreate the runtime casting files in the normal working tree.- State should be read/written through the two-layer
StateBackend. - The generated
squad.agent.mdshould not instruct agents to directly use working-tree paths that are intentionally absent. - Agent/coordinator access to casting state should go through the appropriate state abstraction or materialization mechanism.
Alternatively, if these three casting JSON files are intentionally supposed to exist in the normal working tree, then the pre-commit hook is too broad:
^\.squad/(...|casting/|...)and needs to distinguish between working-tree configuration and mutable orphan-branch state.
Why this matters
This is particularly noticeable when onboarding another team member.
squad upgrade --state-backend two-layer is useful because it correctly installs the local Git hooks that are not transferred by git clone.
However, running the upgrade also recreates mutable casting files locally. The developer is then left with files that:
- Squad itself created,
- the current agent instructions reference,
- but Squad's own pre-commit hook says must not be committed to the working tree.
It is therefore unclear whether the correct action is to delete them, leave them untracked, or retain them as local materialized state.
For a shared two-layer Squad repository, that ambiguity can easily result in different developers having different casting state in their working trees.
Related documentation/code
The current repository appears to acknowledge part of this problem already.
The state-backend documentation notes that files can reappear if code writes directly to disk rather than through StateBackend:
A tool, editor save, or agent code path that writes directly via
fs.writeFile(bypassingStateBackend) will recreate the file on disk.
The upgrade path itself appears to be causing effectively the same situation for the casting files.
Relevant locations in the current dev branch include:
packages/squad-cli/src/cli/commands/install-hooks.ts
docs/src/content/docs/features/state-backends.md
.squad-templates/squad.agent.md
.squad-templates/casting-reference.md
templates/skills/coordinator-source-of-truth/SKILL.md
Suggested resolution
I think the intended model should be clarified first:
If .squad/casting/* is two-layer mutable state
Then preferably:
- Do not create
casting/history.json,casting/registry.json, or runtimecasting/policy.jsonin the working tree when the backend istwo-layerororphan. - Ensure upgrade/init operations use
StateBackendrather than writing these files directly. - Update
squad.agent.mdso agents do not depend on directly reading these working-tree paths. - Keep the existing pre-commit protection.
If some casting files are actually working-tree configuration
Then:
- Clearly separate configuration from runtime state.
- Adjust the pre-commit hook so it does not reject the configuration files.
- Document which casting files are committed and which belong on
squad-state.
At the moment the installed hook, upgrade behavior, and generated agent instructions appear to describe three different ownership models for the same files.
Activity