Skip to content

feat(teams): automatic startup GC + exit cleanup of empty team dirs (from #8)#30

Merged
tmustier merged 2 commits into
mainfrom
feat/startup-exit-gc
Mar 22, 2026
Merged

feat(teams): automatic startup GC + exit cleanup of empty team dirs (from #8)#30
tmustier merged 2 commits into
mainfrom
feat/startup-exit-gc

Conversation

@tmustier
Copy link
Copy Markdown
Owner

Summary

Incorporates the safe parts of PR #8 (@RensTillmann) — automatic cleanup of team directories that accumulate across sessions.

What's added

Startup GC — on session_start, fire-and-forget call to the existing gcStaleTeamDirs() with a 24h age floor. Reuses the well-tested function that checks age + online workers + in-progress tasks + attach claim freshness. Never blocks startup.

Exit cleanup — on session_shutdown, delete the current session's team directory if it's truly empty:

  • No tasks in any namespace (scans all tasks/*/ subdirs)
  • No teammates were active during this session
  • No fresh attach claim from another session
  • Only applies to the session's own team (not attached teams)

What's NOT included from PR #8

  • No new team-gc.ts file — the existing gcStaleTeamDirs() in cleanup.ts already handles GC with proper safety checks
  • No changes to /team gc command — the existing --max-age-hours command works correctly
  • No state-only GC without age floor — too aggressive (could delete teams with pending tasks)

Review process

Plan was reviewed by GPT-5.4 xhigh which found two P1 data-loss risks in the initial design:

  1. Exit cleanup didn't check attach claims → could delete dir from under an attached session
  2. Exit cleanup only checked one task namespace → could miss tasks under other namespaces

Both fixed in the implementation. See plan v2 for details.

Checks

  • tsc strict typecheck passes
  • ✅ Original author credited via --author

Co-authored-by: Rens Tillmann [email protected]

Two-layer automatic cleanup for team directories that accumulate across sessions:

1. **Startup GC** — on session_start, fire-and-forget call to the existing
   gcStaleTeamDirs() with a 24h age floor. Reuses the well-tested function
   that checks age + online workers + in-progress tasks + attach claim
   freshness. Never blocks startup.

2. **Exit cleanup** — on session_shutdown, delete the current session's team
   directory if it's empty (no tasks in any namespace, no teammates were
   active, no fresh attach claim from another session). Best-effort, never
   blocks shutdown. Only cleans the session's own team (not attached teams).

Based on #8 by @RensTillmann.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7dee95b626

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +729 to +731
void gcStaleTeamDirs({
teamsRootDir: getTeamsRootDir(),
maxAgeMs: 24 * 60 * 60 * 1000,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Stop auto-GC from deleting live leader-owned teams

Running gcStaleTeamDirs() on every session_start can silently remove another team that is still actively led. In extensions/teams/cleanup.ts, gcStaleTeamDirs() explicitly ignores role === "lead" and only treats live attach claims or in_progress tasks as activity, so a team that has been open for more than 24h with pending/completed tasks and no workers will be classified as stale even while its leader session is still running. This was an opt-in /team gc behavior before, but wiring it into startup means opening a fresh session can now delete someone else’s live team directory unexpectedly.

Useful? React with 👍 / 👎.

Comment thread extensions/teams/leader.ts Outdated
Comment on lines +860 to +863
const claim = await readTeamAttachClaim(teamDir);
const claimIsLive = claim !== null && !assessAttachClaimFreshness(claim).isStale;
if (!claimIsLive && !(await teamDirHasAnyTasks(teamDir))) {
await cleanupTeamDir(getTeamsRootDir(), teamDir);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Lock attach claims before deleting an empty team

This shutdown cleanup has a race with /team attach: if another session acquires a fresh attach claim after readTeamAttachClaim(teamDir) returns but before cleanupTeamDir() runs, we will still delete the directory out from under the newly attached session. acquireTeamAttachClaim() serializes claim updates with a lock in extensions/teams/team-attach-claim.ts, but this path only does an unlocked read followed by unconditional deletion, so an empty team can disappear immediately after another leader successfully attaches to it.

Useful? React with 👍 / 👎.

…rs on exit

- Add excludeTeamIds param to gcStaleTeamDirs() and pass the current
  session's team ID to prevent GC of resumed sessions older than 24h.
- Check config.json for online non-lead members before exit cleanup to
  avoid deleting dirs still in use by manual/tmux workers.
@tmustier tmustier merged commit 0f88f93 into main Mar 22, 2026
2 checks passed
@tmustier tmustier deleted the feat/startup-exit-gc branch March 22, 2026 07:51
tmustier added a commit that referenced this pull request Apr 3, 2026
Addresses SYM-41 / GitHub #9 — remaining gaps after PRs #14 and #30:

1. Standalone git worktree prune on startup: adds pruneStaleWorktreeRefs()
   called in session_start to clean up dangling .git/worktrees/ entries
   from sessions whose team directories were already deleted (crash,
   manual rm, or partial cleanup). Lightweight and safe to run every time.

2. Pass repoCwd to startup GC: gcStaleTeamDirs now receives ctx.cwd so
   cleanupWorktrees can find the repo root even when worktree dirs are gone.

3. Smarter session_shutdown cleanup: replaces teamDirHasAnyTasks with
   teamDirHasActiveTasks — team dirs with only completed tasks are now
   cleaned up at session end instead of waiting 24h for startup GC.
   Completed tasks have already been reported back and serve no purpose.

4. Removes hadTeammates guard: session_shutdown now attempts dir cleanup
   regardless of whether RPC teammates were active. The safety checks
   (no live attach claim, no online workers, no active tasks) are
   sufficient guards.

5. Passes repoCwd to cleanupTeamDir in session_shutdown for proper
   worktree metadata cleanup.

Tests: 8 new integration tests covering pruneStaleWorktreeRefs (orphan
cleanup, non-git dir) and gcStaleTeamDirs (completed-only tasks are GC'd).
All 325 smoke + 56 integration-cleanup tests pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant