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
{{ message }}
Repository navigation
squad_state_list fails for root on adapter-backed backends: StateBackendStorageAdapter.toRelative() returns "." for squadDir #2231
squad_state_list with dir omitted (or "", or ".squad") fails on any backend routed through StateBackendStorageAdapter (observed with stateBackend: "two-layer"):
Failed to list state: State key must not contain . or .. path segments
squad_state_list {dir: "decisions"} works.
Environment
squad CLI v1.0.1 (WinGet bradygaster.Squad), Windows
.squad/config.json → stateBackend: "two-layer"; squad_state_health → State backend storage: StateBackendStorageAdapter
Source references below are from main @ 89d6302 (bug still present)
Minimal repro
Repo with stateBackend: "two-layer" (or orphan / git-notes).
Run squad state-mcp, call squad_state_list {}.
Observe the error above.
Unit-level repro:
constadapter=newStateBackendStorageAdapter(newOrphanBranchBackend(repo),squadDir);adapter.listSync(squadDir);// throws: State key must not contain . or .. path segments
Root cause
packages/squad-sdk/src/tools/index.tsnormalizeStateToolDir() correctly maps an omitted dir to "", then calls this.storage.listSync(path.join(this.squadRoot, "")), i.e. listSync(squadRoot).
Every backend list() runs normalizeKey(relativeDir). normalizeKey explicitly allows "" as root ("Empty string after normalization means root — valid for list() operations"), but "." is non-empty, so validateStateKey('.') throws.
The adapter emits a root sentinel the backends do not accept.
Suggested fix (keeps traversal rejection)
Do not relax validateStateKey. Map squadDir to ""only for list-shaped reads:
list / listSync / isDirectory / isDirectorySync: when the path resolves to squadDir, pass "".
Keep "." (and therefore rejection) for file operations and especially deleteDir/deleteDirSync. Returning "" there would turn deleteDir(squadDir) into a wipe of all state, and read("") on git backends could resolve to a tree object.
For example, add private toRelativeDir(p) { const r = this.toRelative(p); return r === '.' ? '' : r; } and use it in the four list methods.
Regression tests
adapter.listSync(squadDir) returns root entries for the orphan, git-notes, and two-layer backends.
squad_state_list {} and {dir: ""} succeed.
{dir: ".."}, {dir: "a/../b"}, and {dir: "."} still fail with the . or .. error.
adapter.deleteDirSync(squadDir) and adapter.readSync(squadDir) still throw.
Activity