Skip to content

two-layer/orphan: pre-commit hook says casting/ belongs on squad-state, but state tools and casting writer keep registry/history on disk #2229

Closed

Description

@jpapiez

Summary

With stateBackend: "two-layer", three parts of Squad disagree about where .squad/casting/registry.json and history.json should live. As a result, no supported path persists a casting change.

Evidence (source at indexed commit 89d6302)

  • packages/squad-cli/src/cli/commands/install-hooks.ts: the pre-commit hook refuses any staged path matching ^\.squad/(decisions\.md|agents/.+/history\.md|casting/|routing/) unless SQUAD_SYNC_ACTIVE is set. Its message says: "These paths belong on the 'squad-state' orphan branch, not in your normal commits". The post-commit comment says it syncs "decisions, histories, casting" onto squad-state.
  • packages/squad-sdk/src/tools/index.ts: MUTABLE_STATE_PATHS includes only casting/policy.json. squad_state_write key=casting/history.json is rejected with: "casting registry/history pair must only be changed through the atomic casting protocol".
  • packages/squad-sdk/src/casting/durable-registry.ts: commitCastingRegistryPair(castingDir, …) writes the pair to the on-disk .squad/casting/ directory. cast add and preset scaffold both use it.

Observed in a consumer repo

  • The squad-state branch had README.md, decisions.md, decisions/inbox/* and agents/*/history.md. It had no casting/ path, so nothing had moved the pair there.
  • squad_state_read key=casting/registry.json returned "State key not found". squad_state_list dir=casting returned an empty list.
  • To persist a registry model update, we had to call commitCastingRegistryPair and commit with SQUAD_SYNC_ACTIVE=1 to get past the hook.

Separate small bug

squad_state_list with no dir returns "Failed to list state: State key must not contain . or .. path segments". A root listing should either succeed or report a clearer error.

Questions

  1. Under the two-layer and orphan backends, where should the canonical casting registry/history pair live: on disk in the working tree, or on the squad-state branch?
  2. If on disk, should the pre-commit hook allowlist casting/registry.json, casting/history.json and the commit marker (and fix the hook/post-commit wording)?
  3. If on squad-state, should the atomic writer target the state backend, and is a migration needed?

Versions: the source references above are from the indexed main commit 89d6302. The installed CLI (winget squad.exe) version was not checked against it.

Activity

  1. dat-intellis commented on Nov 8, 2026

    @dat-intellis

    Adding version-pinned ownership evidence for 1.1.0, plus a related documentation/enforcement mismatch. All links below use release commit c24da2aad2ab7576f6b68dec2bc50b2fe2dc7a8a.

    The intended casting placement is explicitly stated in SDK initialization: casting is authoritative identity, belongs on main, and must not be ignored. The migration helper likewise deliberately leaves static casting/* files untouched.

    However, the generated pre-commit hook rejects the entire .squad/casting/ directory and says those files belong on squad-state. The release's own state-backend documentation also calls casting disk-resident static config, then later documents blocking it. Filesystem casting persistence is therefore not itself evidence of a bug; the shipped ownership and commit-enforcement contracts conflict. Related: #2172.

    There is a narrower safeguard mismatch worth resolving with that contract: the coordinator template lists memory and identity as runtime-managed paths under nonlocal backends and says handwritten files will be caught by the pre-commit hook. But the hook's regex does not include memory/ or identity/, and the generated ignore entries do not cover them either.

    This is not a blanket claim that runtime files lack protection: the general ignore entries already cover logs, orchestration logs, decisions inbox, sessions, scratch, and cache. Different hook/ignore lists alone would not establish a bug. The issue is that the coordinator promises enforcement that those paths do not receive.

    Could the intended ownership be reconciled across initialization, atomic casting writes, state-tool permissions, migration, coordinator instructions, and Git safeguards? The evidence does not justify moving the casting registry/history to orphan storage without first settling that contract.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    wave1:P2Wave 1 PR group P2wave:1-nextWave 1 — immediate next sprint

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions