Skip to content

hardening: git index.lock 강제 삭제가 살아있는 락을 지울 수 있다 — 소유 기반 회수로 대체 #285

Description

@pdw96

문제

src/main/core/workspace/git.tsok() 재시도 루프는 2회차 이후 index.lock무조건 삭제한다.

if (attempt >= 1 && existsSync(lock)) {
  try { rmSync(lock) } catch { /* 다음 시도에서 재확인 */ }
}

발동 조건은 ⓐ락 계열 stderr 매칭 ⓑ2회차 이후(누적 ~450ms 대기) ⓒexistsSync 셋뿐인데,
어느 것도 그 락이 stale 하거나 Fleet 소유임을 증명하지 못한다. 사용자의 IDE 나 터미널에서
돌아가는 git add/git status 는 큰 레포에서 450ms 를 쉽게 넘기고, 그 살아있는 락을 지우면
경쟁 인덱스 writer 가 허용되어 staged 상태가 유실될 수 있다.

Codex 가 PR #282 에서 P1 으로 지적했다(pull/282#discussionworkspace/git.ts:25).
코드 자체는 #282 이전부터 있던 것이고, #282 는 그 사실을 문서화만 했다.

왜 그냥 삭제를 없애면 안 되나

이 회수가 존재하는 이유는 실재한다 — Fleet 이 스폰한 편집 에이전트 CLI 가 취소(kill-tree)나
크래시로 죽으면 index.lock 이 남고, 이후 Fleet 의 모든 git 조작이 영구히 실패한다.
그냥 제거하면 그 시나리오에서 사람이 손으로 지워야 한다.

제안 방향 — 「시각」이 아니라 「소유」로

락 나이·재시도 횟수 같은 간접 신호는 전부 휴리스틱이다(git 은 index.lock 에 소유 PID 를
기록하지 않는다). 대신 소유를 구성적으로 아는 지점에서 회수한다:

  • Fleet 이 자기 자식 프로세스 트리를 죽인 직후, 그 워크트리에 대해서만 잔존 락을 회수한다
    (process/kill-tree.ts 경로 — 그 시점엔 「우리가 방금 죽인 프로세스가 유일한 후보」가 성립).
  • 재시도 루프 안에서는 삭제하지 않고 bounded backoff 후 정직하게 실패한다.
  • 회수하지 못한 잔존 락은 조용히 지우지 말고 사용자에게 보이는 상태로 올린다(진단 가능성).

이 방향은 [[#251]] 워크벤치가 락·리스에서 채택한 원칙(「연령·신원은 소유 증거가 아니다」)과 같다.

완료 조건

  • ok() 의 무조건 rmSync(index.lock) 제거
  • 소유 기반 회수 경로 신설 + 테스트(살아있는 락은 안 지운다 / 우리 자식 사후엔 지운다)
  • workspace/git.tsApprovalGate 예외 근거 절에서 「규칙 미충족 · 추적 중」 표기 해제
    (현재 scripts/approval-gate-exceptions.test.ts 가 강제하는 열거에 이 모듈이 들어 있다)

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:orchestrator오케스트레이터 실행/계획 루프tier:next착수 대기 (다음 백로그주)type:security보안 관련

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions