cleanupStaleAgentWorktrees method

Future<int> cleanupStaleAgentWorktrees(
  1. DateTime cutoffDate
)

Remove stale agent/workflow worktrees older than cutoffDate.

Safety:

  • Only touches slugs matching ephemeral patterns (never user-named worktrees)
  • Skips the current session's worktree
  • Fail-closed: skips if git status fails or shows tracked changes
  • Fail-closed: skips if any commits aren't reachable from a remote

Implementation

Future<int> cleanupStaleAgentWorktrees(DateTime cutoffDate) async {
  final gitRoot = _findCanonicalGitRoot(_getCwd());
  if (gitRoot == null) return 0;

  final dir = _worktreesDir(gitRoot);
  List<FileSystemEntity> entries;
  try {
    entries = await Directory(dir).list().toList();
  } catch (_) {
    return 0;
  }

  final cutoffMs = cutoffDate.millisecondsSinceEpoch;
  final currentPath = currentSession.value?.worktreePath;
  int removed = 0;

  for (final entry in entries) {
    final slug = entry.path.split('/').last;
    if (!_ephemeralWorktreePatterns.any((p) => p.hasMatch(slug))) continue;

    final worktreePath = '$dir/$slug';
    if (currentPath == worktreePath) continue;

    int mtimeMs;
    try {
      final stat = await FileStat.stat(worktreePath);
      mtimeMs = stat.modified.millisecondsSinceEpoch;
    } catch (_) {
      continue;
    }
    if (mtimeMs >= cutoffMs) continue;

    // Both checks must succeed with empty output
    final statusFuture = _execGit([
      '--no-optional-locks',
      'status',
      '--porcelain',
      '-uno',
    ], cwd: worktreePath);
    final unpushedFuture = _execGit([
      'rev-list',
      '--max-count=1',
      'HEAD',
      '--not',
      '--remotes',
    ], cwd: worktreePath);

    final results = await Future.wait([statusFuture, unpushedFuture]);
    final status = results[0];
    final unpushed = results[1];

    if (status.code != 0 || status.stdout.trim().isNotEmpty) continue;
    if (unpushed.code != 0 || unpushed.stdout.trim().isNotEmpty) continue;

    final success = await removeAgentWorktree(
      worktreePath,
      worktreeBranch: worktreeBranchName(slug),
      gitRoot: gitRoot,
    );
    if (success) removed++;
  }

  if (removed > 0) {
    await _execGit(['worktree', 'prune'], cwd: gitRoot);
    _logForDebugging(
      'cleanupStaleAgentWorktrees: removed $removed stale worktree(s)',
    );
  }
  return removed;
}