Skip to content

Squad state destroyed when .squad/ is gitignored and agents switch branches #643

Closed

Description

@seligj95

Problem

When .squad/ is in .gitignore (common when using Squad in a repo you do not own), agent-triggered git checkout commands silently destroy all Squad state files (dashboards, agent history, decisions, orchestration logs).

Root Cause

.squad/ files are untracked. Git does not preserve untracked files during branch switches. Agents that run git checkout <branch> in the main worktree — instead of git worktree add — cause the deletion.

How We Hit This

We used Squad to triage a 78-issue backlog in Azure/azure-cli. Since we cannot commit .squad/ to that repo, we gitignored it. During live validation, an agent ran git checkout fix/ssl-branch in the main worktree. All .squad/ files were wiped — dashboards, agent charters, history, logs. This happened twice in one session.

Suggested Improvements

  1. Detect gitignored .squad/ at init — If .squad/ matches a .gitignore rule, warn the user: "⚠️ .squad/ is gitignored. State will not survive branch switches. Consider using --external-state or committing .squad/."

  2. Enforce worktree usage in agent prompts — The coordinator already instructs agents to use git worktree add, but there is no enforcement. Consider adding a pre-flight check or a git hook that prevents git checkout when .squad/ is untracked.

  3. Support external state directory — For "contributor" scenarios (using Squad in repos you do not own), allow .squad/ state to live outside the repo, e.g. ~/.squad/projects/<repo-name>/. This would be immune to checkout/worktree issues.

  4. Auto-backup before risky git ops — Before any branch switch, snapshot .squad/ to a temp location. Restore if files are missing after the operation.

Environment

  • Squad v0.9.1
  • Copilot CLI
  • macOS, Azure/azure-cli repo
  • .squad/ in .gitignore

Activity

  1. github-actions commented on Mar 28, 2026

    @github-actions
    Contributor

    📋 Assigned to EECOM (Core Dev)

    Issue: #643 — Squad state destroyed when .squad/ is gitignored and agents switch branches

    EECOM will pick this up in the next Copilot session.

    For Copilot coding agent: If enabled, this issue will be worked automatically.
    Otherwise, start a Copilot session and say:
    EECOM, work on issue #643

  2. github-actions commented on Mar 28, 2026

    @github-actions
    Contributor

    📋 Assigned to FIDO (Quality Owner)

    Issue: #643 — Squad state destroyed when .squad/ is gitignored and agents switch branches

    FIDO will pick this up in the next Copilot session.

    For Copilot coding agent: If enabled, this issue will be worked automatically.
    Otherwise, start a Copilot session and say:
    FIDO, work on issue #643

  3. github-actions commented on Mar 28, 2026

    @github-actions
    Contributor

    🏗️ Squad Triage — Flight (Lead)

    Issue: #643 — Squad state destroyed when .squad/ is gitignored and agents switch branches
    Assigned to: FIDO (Quality Owner)
    Reason: Issue relates to testing/quality work

    @copilot evaluation: 🔴 Not suitable — issue involves work outside the coding agent's capability profile.

    Team roster:

    • Flight (Lead) → label: squad:flight
    • Procedures (Prompt Engineer) → label: squad:procedures
    • EECOM (Core Dev) → label: squad:eecom
    • FIDO (Quality Owner) → label: squad:fido
    • PAO (DevRel) → label: squad:pao
    • CAPCOM (SDK Expert) → label: squad:capcom
    • CONTROL (TypeScript Engineer) → label: squad:control
    • Surgeon (Release Manager) → label: squad:surgeon
    • Booster (CI/CD Engineer) → label: squad:booster
    • GNC (Node.js Runtime) → label: squad:gnc
    • Network (Distribution) → label: squad:network
    • RETRO (Security) → label: squad:retro
    • INCO (CLI UX & Visual Design) → label: squad:inco
    • GUIDO (VS Code Extension) → label: squad:guido
    • Telemetry (Aspire & Observability) → label: squad:telemetry
    • VOX (REPL & Interactive Shell) → label: squad:vox
    • DSKY (TUI Engineer) → label: squad:dsky
    • Sims (E2E Test Engineer) → label: squad:sims
    • Handbook (SDK Usability) → label: squad:handbook
    • Ralph (Work Monitor) → label: squad:ralph
    • @copilot (Coding Agent) → label: squad:copilot

    To reassign, remove the current squad:* label and add the correct one.

  4. diberry commented on Mar 31, 2026

    @diberry
    Contributor

    🔄 Ralph status: PR #680 is a POC for StateBackend (orphan branch persistence) that would solve the root cause. Marked DO NOT MERGE, has conflicts. Also refs #678 (RFC), #498, #670. No production fix merged yet — issue remains open.

  5. jazz127 commented on Apr 1, 2026

    @jazz127

    I've experienced this a number of times because I didn't want squad in my repo and .gitignored it (matching what i think is coming anyway). I also used the worktrees feature which seemed to make it happen more often. I hit numerous issues with agents force adding/committing and even pushing the commits to origin containing .squad files despite directives, trying to funnel pushes and you name it, i spent a couple of days fighting this.

    I investigated using a global squad but still seemed to hit issues - maybe something to do with my mucking around.

    After various discussions and threats to the coordinator (that didn't work btw), i have found something that's been working for me and wanted to share in case it guides you to a fix or some sort of workaround for people.

    one point noting is that the cause of files being left in random places, for me was identified as Squad agents receiving prompts without a TEAM_ROOT and being left to discover things. They would walk the tree and fail so end up back in the repo.

    anyway, moving the .squad folder outside the repo given it's .gitignore anyway should be a suitable workaround. I wanted to retain the ability to have different squads in projects so global wasn't really a good option for me anyway. my folder structure is ~/project-name-dev/.squad and ~/project-name-dev/gitrepo. i did the squad /init in the ~/project-name-dev folder iirc.

    We came up with Personal Squad Solo Mode added to the squad.agent.md and a revised agent-collaboration SKILL.md which i've added below (it's a long scroll for the agent one sorry)

    ``

    Personal Squad — Sole Squad Mode (External Repo)

    Injected at line 137 just after the **spawn personal agents with: ** section

    When SQUAD_PERSONAL_ROOT is set in the environment, the personal squad operates in Sole Squad Mode:

    • TEAM_ROOT = $SQUAD_PERSONAL_ROOT (the personal squad's git repo path)
    • WORKTREE_PATH = current project repo root (from git rev-parse --show-toplevel)
    • Ghost Protocol is OFF — there is no project .squad/ to protect
    • Scribe commits to the personal squad repo, not the project repo
    • No origin: 'personal' tags — the personal squad IS the squad
    • The project repo remains clean: no .squad/ directory at all

    Revised team root resolution section

    Three strategies for resolving the team root:

    Strategy Team root State scope When to use
    worktree-local Current worktree root Branch-local — each worktree has its own .squad/ state Feature branches that need isolated decisions and history
    main-checkout Main working tree root Shared — all worktrees read/write the main checkout's .squad/ Single source of truth for memories, decisions, and logs across all branches
    external-personal-squad $SQUAD_PERSONAL_ROOT (separate git repo) Shared across all project worktrees — state lives in the personal squad repo, never the project repo Personal squad is the sole squad; project repo has no .squad/

    How the Coordinator resolves the team root (on every session start):

    1. Check SQUAD_PERSONAL_ROOT env var first. If set → use external-personal-squad strategy:
      • TEAM_ROOT = $SQUAD_PERSONAL_ROOT
      • WORKTREE_PATH = git rev-parse --show-toplevel (current project repo)
      • Ghost Protocol: OFF. Skip Ambient Discovery entirely.
      • Stop here. Do not proceed to steps 1–2.
    2. Run git rev-parse --show-toplevel to get the current worktree root.
    3. Check if .squad/ exists at that root (fall back to .ai-team/ for repos that haven't migrated yet).
      • Yes → use worktree-local strategy. Team root = current worktree root.
      • No → use main-checkout strategy. Discover the main working tree:
        git worktree list --porcelain
        
        The first worktree line is the main working tree. Team root = that path.
    4. The user may override the strategy at any time (e.g., "use main checkout for team state" or "keep team state in this worktree").

    Passing the team root to agents:

    • The Coordinator includes TEAM_ROOT: {resolved_path} in every spawn prompt.
    • Agents resolve ALL .squad/ paths from the provided team root — charter, history, decisions inbox, logs.
    • Agents never discover the team root themselves. They trust the value from the Coordinator.

    Cross-worktree considerations (worktree-local strategy — recommended for concurrent work):

    • .squad/ files are branch-local. Each worktree works independently — no locking, no shared-state races.
    • When branches merge into main, .squad/ state merges with them. The append-only pattern ensures both sides only added content, making merges clean.
    • A merge=union driver in .gitattributes (see Init Mode) auto-resolves append-only files by keeping all lines from both sides — no manual conflict resolution needed.
    • The Scribe commits .squad/ changes to the worktree's branch. State flows to other branches through normal git merge / PR workflow.

    Cross-worktree considerations (main-checkout strategy):

    • All worktrees share the same .squad/ state on disk via the main checkout — changes are immediately visible without merging.
    • Not safe for concurrent sessions. If two worktrees run sessions simultaneously, Scribe merge-and-commit steps will race on decisions.md and git index. Use only when a single session is active at a time.
    • Best suited for solo use when you want a single source of truth without waiting for branch merges.

    Cross-worktree considerations (external-personal-squad strategy):

    • The personal squad repo is completely independent of the project repo — no .squad/ in the project at all.
    • Multiple project worktrees all point at the same TEAM_ROOT (the personal squad repo). State is shared across all worktrees without any merging.
    • Safe for single-session use (one Scribe, no concurrent Scribe write races within a session).
    • Concurrent sessions on different machines: same caveat as main-checkout — avoid simultaneous Scribe commits to the same personal squad branch.
    • SQUAD_PERSONAL_ROOT must be set consistently across all worktrees in the session.

    In this mode, the standard Ambient Discovery flow is skipped. All agent state, decisions, and logs live entirely in the external personal squad repo.

    Revised/hacked Agent Collab Skill the cooridinator made (i haven't hit the gate yet):

    `---
    name: "agent-collaboration"
    description: "Standard collaboration patterns for all squad agents — worktree awareness, decisions, cross-agent communication"
    domain: "team-workflow"
    confidence: "high"
    source: "extracted from charter boilerplate — identical content in 18+ agent charters"

    Context

    Every agent on the team follows identical collaboration patterns for worktree awareness, decision recording, and cross-agent communication. These were previously duplicated in every charter's Collaboration section (~300 bytes × 18 agents = ~5.4KB of redundant context). Now centralized here.

    The coordinator's spawn prompt already instructs agents to read decisions.md and their history.md. This skill adds the patterns for WRITING decisions and requesting help.

    Patterns

    Worktree Awareness

    Use the TEAM ROOT path provided in your spawn prompt. All .squad/ paths are relative to this root. Never assume CWD is the repo root.

    TEAM_ROOT Validation Gate ⛔ HARD GATE

    Run this check BEFORE any work that reads or writes .squad/:

    1. Is TEAM_ROOT explicitly present in your spawn prompt?
      • Yes → absolute path (starts with / or drive letter): proceed.
      • No / empty / missing: HALT immediately. Report back:

        "⛔ TEAM_ROOT not set in spawn prompt — refusing to write team state. Coordinator must re-spawn with TEAM_ROOT set."
        Do NOT fall back to git rev-parse --show-toplevel. Do NOT proceed with any .squad/ operations.

    Why the fallback is banned: Inside a worktree, git rev-parse --show-toplevel returns the worktree root (e.g., /Users/test/projectgitrepo/projectgitrepo-1234), NOT the personal squad repo root (/Users/test/testsquadroot). Using it as a fallback silently writes team state to the wrong location and can commit squad artifacts into project branches — which is a hard violation.

    Decision Recording

    After making a decision that affects other team members, write it to:
    .squad/decisions/inbox/{your-name}-{brief-slug}.md

    Format:

    ### {date}: {decision title}
    **By:** {Your Name}
    **What:** {the decision}
    **Why:** {rationale}
    

    Cross-Agent Communication

    If you need another team member's input, say so in your response. The coordinator will bring them in. Don't try to do work outside your domain.

    Reviewer Protocol

    If you have reviewer authority and reject work: the original author is locked out from revising that artifact. A different agent must own the revision. State who should revise in your rejection response.

    Anti-Patterns

    • Don't read all agent charters — you only need your own context + decisions.md
    • Don't write directly to .squad/decisions.md — always use the inbox drop-box
    • Don't modify other agents' history.md files — that's Scribe's job
    • Don't assume CWD is the repo root — always use TEAM ROOT
    • Don't fall back to git rev-parse if TEAM_ROOT is missing — halt and report instead
    • Don't write .squad/ files without TEAM_ROOT being an absolute path — this is how team state ends up in worktrees and project branches
      `
  6. tamirdresher commented on Apr 12, 2026

    @tamirdresher
    Collaborator

    This is addressed by PR #797 (merged to insider Apr 5) which adds \squad externalize\ — moves .squad/ state outside the working tree to a platform-specific global directory.

    With external state, branch switches no longer destroy squad state because the files aren't in the working tree at all.

    To use it: \squad externalize\ (moves state out) / \squad internalize\ (moves it back).

    Closing as resolved by #797. If you're on insider, try \squad externalize\ and let us know if it works for your workflow.

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

    bugSomething isn't workinggo:needs-researchNeeds investigationsquadSquad triage inbox — Lead will assign to a member

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions