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
bug: the fleet-wide .gitignore merge never runs at agent creation — in-container auto-sync commits .trinity/ runtime state before any Push can migrate #2069
The fleet-wide .gitignore list (_GITIGNORE_PATTERNS, src/backend/services/git_service.py:1269) is never applied at agent creation. It is applied at exactly two later moments, both operator-initiated — so an agent created from an external github: template whose repo ships no .gitignore (or a partial one) starts life with no platform exclusions, while the in-container 15-min auto-sync loop is already committing with git add -A. Runtime state — .trinity/, .claude/projects/, content/, and the credential files — reaches the user's own repo before any Push can migrate the list.
POST /api/agents/{name}/git/initialize — "create a new repo for this agent"
❌ not on the github:-template path
_migrate_workspace_gitignore (git_service.py:1440, called at git_service.py:1112)
sync_to_github — the operator Push button / MCP git_sync
❌ only once a human pushes
Creation itself (services/agent_service/crud.py) calls neither. _materialize_agent_files touches .gitignore only via _append_agent_gitignore (git_service.py:709), and only for data_paths entries (#1169) — opt-in, and unrelated to the canonical list. startup.sh deliberately never writes .gitignore (#953, comments at docker/base-image/startup.sh:239, :446, :561). So a template agent's .gitignore is exactly what the template repo shipped.
Why the Push-time fallback loses the race
The fallback assumed by #1908 ("it depends entirely on the fleet-wide .gitignore merge … at sync time") is not the default writer. The default writer is the in-container auto-sync loop, which never merges the list:
stages with a bare git add -A (agent_server/routers/git.py:485) against whatever .gitignore exists in the container
and it is on from creation for the two paths that push to a repo the user owns — env_vars['GIT_SYNC_AUTO'] = 'true' at crud.py:1567 for non-source-mode (legacy working-branch) and fork-to-own (trinity-enterprise#93) agents. So such an agent auto-commits every 15 minutes from birth, and converges only if and when someone clicks Push.
Silent: nothing fails, and the compatibility check that would surface it (c_g002, services/compatibility/static_checks.py:752) is advisory and per-agent.
Scope — what this is not
#1908 (status-in-dev) fixed the bundled templates and proposes a CI guard scoped to them. Verified locally that this landed: 16 of the 17 bundled templates carrying a .gitignore now list .trinity/, and the remainder are hidden fixtures plus trinity-system. This issue is about the platform-side timing, which no template-side fix can reach — external github: templates are the general case and are outside any bundled-template CI guard.
Proposed fix
Apply the existing merge at creation (and/or at start, before the auto-sync loop can run a cycle) for any agent that has a .git directory — reusing _build_gitignore_merge_command rather than adding a fourth copy of the pattern list.
Acceptance Criteria
An agent created from an external github: template whose repo ships no .gitignore (or one lacking the canonical patterns) has the merged list on disk before the first auto-sync cycle can commit
The first in-container auto-sync of such an agent commits no .trinity/, .claude/projects/, content/, .env or .mcp.json path
The merge reuses _build_gitignore_merge_command — _GITIGNORE_PATTERNS stays the single source of truth (no fourth call site with its own list)
Existing agents keep converging via the Push-time migration — no behaviour change on sync_to_github
Regression test covers the fork-to-own / non-source-mode path, since that is where GIT_SYNC_AUTO is on from creation
Technical Notes
Ordering matters: the merge must land after startup.sh finishes cloning and before the first auto-sync cycle. auto_sync.run_auto_sync_loop sleeps one full interval before its first cycle (auto_sync.py:56), which is the window to use — but the fix should not depend on that sleep for correctness.
_detect_git_dir (git_service.py:1416) already resolves the legacy workspace/ vs $HOME split; reuse it so init, Push-migration and this new call site agree.
Consider whether the untrack half (_build_rm_cached_ignored_command) should run at creation too — probably not, and see the sibling issue on its exemption list before wiring it anywhere new.
Summary
The fleet-wide
.gitignorelist (_GITIGNORE_PATTERNS,src/backend/services/git_service.py:1269) is never applied at agent creation. It is applied at exactly two later moments, both operator-initiated — so an agent created from an externalgithub:template whose repo ships no.gitignore(or a partial one) starts life with no platform exclusions, while the in-container 15-min auto-sync loop is already committing withgit add -A. Runtime state —.trinity/,.claude/projects/,content/, and the credential files — reaches the user's own repo before any Push can migrate the list.Where the canonical list is actually applied
initialize_git_in_containerstep 2 (git_service.py:1545)POST /api/agents/{name}/git/initialize— "create a new repo for this agent"github:-template path_migrate_workspace_gitignore(git_service.py:1440, called atgit_service.py:1112)sync_to_github— the operator Push button / MCPgit_syncCreation itself (
services/agent_service/crud.py) calls neither._materialize_agent_filestouches.gitignoreonly via_append_agent_gitignore(git_service.py:709), and only fordata_pathsentries (#1169) — opt-in, and unrelated to the canonical list.startup.shdeliberately never writes.gitignore(#953, comments atdocker/base-image/startup.sh:239,:446,:561). So a template agent's.gitignoreis exactly what the template repo shipped.Why the Push-time fallback loses the race
The fallback assumed by #1908 ("it depends entirely on the fleet-wide
.gitignoremerge … at sync time") is not the default writer. The default writer is the in-container auto-sync loop, which never merges the list:agent_server/auto_sync.py:44→agent_server/routers/git.py::_run_auto_sync_oncegit add -A(agent_server/routers/git.py:485) against whatever.gitignoreexists in the containerand it is on from creation for the two paths that push to a repo the user owns —
env_vars['GIT_SYNC_AUTO'] = 'true'atcrud.py:1567for non-source-mode (legacy working-branch) and fork-to-own (trinity-enterprise#93) agents. So such an agent auto-commits every 15 minutes from birth, and converges only if and when someone clicks Push.Impact
.trinity/pending-results/(refactor: fire-and-forget dispatch — a hung turn holds zero backend resource #1083),.trinity/pipeline-state/(feat(mcp): introspect agent pipelines via standardized~/.trinity/pipelines/+~/.trinity/pipeline-state/#919),.trinity/operator-queue.json,.trinity/persistent-state.yaml(Persistent state allowlist (S4) #383) — churny content, so the same unbounded-.git-growth class as bug: git-sync agent workspaces grow unboundedly — auto-committed data churn bloats .git (44GB observed on one agent) #1596/bug: .claude/plugins/ is missing from the fleet-wide gitignore — installed plugin caches get auto-committed into agent repos #1702..env,.mcp.jsonlive at the repo root because$HOMEis the repo root — refactor: agent git repo root is $HOME — 22 gitignore patterns exist only to compensate, and every new home-writing feature repeats the dance #1703).c_g002,services/compatibility/static_checks.py:752) is advisory and per-agent.Scope — what this is not
#1908 (
status-in-dev) fixed the bundled templates and proposes a CI guard scoped to them. Verified locally that this landed: 16 of the 17 bundled templates carrying a.gitignorenow list.trinity/, and the remainder are hidden fixtures plustrinity-system. This issue is about the platform-side timing, which no template-side fix can reach — externalgithub:templates are the general case and are outside any bundled-template CI guard.Proposed fix
Apply the existing merge at creation (and/or at start, before the auto-sync loop can run a cycle) for any agent that has a
.gitdirectory — reusing_build_gitignore_merge_commandrather than adding a fourth copy of the pattern list.Acceptance Criteria
github:template whose repo ships no.gitignore(or one lacking the canonical patterns) has the merged list on disk before the first auto-sync cycle can commit.trinity/,.claude/projects/,content/,.envor.mcp.jsonpath_build_gitignore_merge_command—_GITIGNORE_PATTERNSstays the single source of truth (no fourth call site with its own list)M .gitignoredrift againstorigin(the exact regression Freshly deployed agents report M .gitignore against origin/main #953 removed the shell-level append for)sync_to_githubGIT_SYNC_AUTOis on from creationTechnical Notes
startup.shfinishes cloning and before the first auto-sync cycle.auto_sync.run_auto_sync_loopsleeps one full interval before its first cycle (auto_sync.py:56), which is the window to use — but the fix should not depend on that sleep for correctness._detect_git_dir(git_service.py:1416) already resolves the legacyworkspace/vs$HOMEsplit; reuse it so init, Push-migration and this new call site agree._build_rm_cached_ignored_command) should run at creation too — probably not, and see the sibling issue on its exemption list before wiring it anywhere new.$HOME) is the structural fix that would retire this whole compensation layer; this is the bounded fix until then..gitignore— template-side half), bug: .claude/plugins/ is missing from the fleet-wide gitignore — installed plugin caches get auto-committed into agent repos #1702, bug: git-sync agent workspaces grow unboundedly — auto-committed data churn bloats .git (44GB observed on one agent) #1596, bug: UI Push commits runtime state (.cache, .claude/sessions, .claude.json, .sudo_as_admin_successful, etc.) — git add -A against too-narrow .gitignore #462, bug: base image bakes container-only hook paths into ~/.claude/settings.json, git sync commits it, external clones of the agent repo are bricked #2036 (prior instances of runtime state reaching agent repos), refactor(ci): validate every bundled template.yaml declares only what it ships #1655 (bundled-template CI validation).