Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions contributors/emails/seraphine@Seraphines-Mac-Studio.local
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Bartok9
# Seraphine Mac Studio local email on Bartok9 PR tips (per-PR attribution; Teknium/Daniel 2026-08-01)
39 changes: 34 additions & 5 deletions tools/checkpoint_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,36 @@ def _repair_bare_repo_dirs(store: Path) -> None:
)


_STALE_LOCK_SECONDS = 60.0


def _clear_stale_lock(shadow_repo: Path) -> None:
"""Remove a stale ``index.lock`` left behind by a crashed/killed git process.

Checkpoint ops against a shadow repo are strictly serial (one gateway
agent per session), so any ``index.lock`` older than ``_STALE_LOCK_SECONDS``
at entry to a new git call is unambiguously orphaned. Without this, a
single crashed ``git add`` wedges checkpointing for that repo until manual
cleanup (seen in logs: 56+ errors over 6 days on one shadow repo).
"""
lock_path = shadow_repo / "index.lock"
try:
age = time.time() - lock_path.stat().st_mtime
except FileNotFoundError:
return
except OSError:
return
if age < _STALE_LOCK_SECONDS:
return
try:
lock_path.unlink()
logger.warning("Removed stale index.lock (age=%.0fs) at %s", age, lock_path)
except FileNotFoundError:
pass
except OSError as exc:
logger.warning("Failed to remove stale index.lock at %s: %s", lock_path, exc)


def _run_git(
args: List[str],
store: Path,
Expand Down Expand Up @@ -325,7 +355,9 @@ def _run_git(
env = _git_env(store, str(normalized_working_dir), index_file=index_file)
cmd = ["git"] + list(args)
allowed_returncodes = allowed_returncodes or set()

# Clear any orphaned index.lock from a previously crashed git op so this
# serial checkpoint call isn't wedged by a zombie lock.
_clear_stale_lock(store)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This clears store/index.lock, but production checkpoint staging uses GIT_INDEX_FILE=store/indexes/<hash> (_take passes that index_file to git add). Its stale lock is <index_file>.lock, so this does not recover the runtime wedge. Please derive the cleanup target from index_file and test that path.

try:
result = subprocess.run(
cmd,
Expand All @@ -335,9 +367,6 @@ def _run_git(
env=env,
cwd=str(normalized_working_dir),
stdin=subprocess.DEVNULL,
# Checkpoints fire several bare git calls per turn from the
# console-less desktop/gateway backend; suppress the per-call
# conhost flash on Windows (no-op on POSIX).
creationflags=windows_hide_flags(),
)
ok = result.returncode == 0
Expand Down Expand Up @@ -790,7 +819,7 @@ def list_checkpoints(self, working_dir: str) -> List[Dict]:

ref = _ref_name(_project_hash(abs_dir))
ok, stdout, _ = _run_git(
["log", ref, "--format=%H|%h|%aI|%s", "-n", str(self.max_snapshots)],
["log", ref, f"--format=%H|%h|%aI|%s", "-n", str(self.max_snapshots)],
store, abs_dir,
allowed_returncodes={128, 129},
)
Expand Down
Loading