저는 바이브코딩을 하면서 이걸 나중에 컨텐츠화 하고자, 작업 과정을 파일로 기록하는 로깅 스크립트를 하나 달아뒀어요. 그런데 이 스크립트가 매 턴마다 빨간 에러를 뱉기 시작했습니다. 메시지는 이랬어요. 스크립트 Write-DevLog.ps1의 183번째 줄, 파일에 덧붙여 쓰는 Add-Content에서 난 에러였어요.
Stop hook error: Add-Content: Write-DevLog.ps1:183
The process cannot access the file '...2026-06-20.md'
because it is being used by another process.
"파일을 다른 프로세스가 사용 중이라 접근할 수 없다." 에러가 빨갛게 뜨면 일단 심장이 철렁하죠. 저도 그래요. 그런데 원인 진단은 의외였어요 — 코드가 틀려서 난 에러가 아니라는 거예요. 이 오답노트는 그 빨간 에러의 진짜 정체와, "모든 에러를 똑같이 대하면 안 된다"는 수정안을 AI에게 검증시켜 받아들인 기록입니다.
코드는 멀쩡한데 왜 죽지?
진단은 이 세션 밖에서 이미 나와 있었고, 제가 AI에게 넘긴 작업 지시문에 적혀 있었어요. 이 로깅 스크립트는 매 턴 끝날 때마다 그날 날짜의 기록 파일에 내용을 덧붙여 써요. 문제는 그 파일이 있는 폴더를 구글 드라이브가 실시간으로 동기화하고 있었다는 거예요. 게다가 가끔은 다른 작업 창(동시에 켜둔 다른 AI 세션)도 같은 파일을 건드렸고요.
그러니 이런 순간이 생깁니다. 스크립트가 파일에 쓰려는 바로 그 찰나에, 구글 드라이브가 같은 파일을 동기화하려고 잠깐 잠가버리는 거예요. 그럼 스크립트는 "어? 파일에 접근이 안 되네?" 하고 그 자리에서 죽습니다.
이게 레이스 컨디션(race condition)이에요. 두 프로그램이 같은 자원(여기선 파일 하나)을 거의 동시에 쓰려다가, 타이밍이 나쁘게 겹치는 순간 문제가 터지는 상황이죠. 코드 자체는 멀쩡해요. 대부분은 잘 돌아가고, 하필 동기화와 겹친 순간에만 터집니다. 그래서 더 헷갈려요 — 재현이 매번 되는 게 아니거든요.
핵심은 이거예요. 이건 로직 버그가 아니라 순간적인 파일 잠금이라는 것. 코드를 아무리 들여다봐도 틀린 데가 없는 이유가 여기 있어요 — 애초에 고칠 대상이 "잘못된 계산"이 아니라 "겹치는 타이밍"이었으니까요.
첫 번째 유혹 — "그냥 에러를 다 무시할까?"
빨간 에러가 성가시면 이렇게 부탁하고 싶어지기 쉬워요. "에러가 나든 말든 그냥 넘어가게 만들어줘." 즉 어떤 에러가 나도 스크립트가 안 죽게 전부 삼켜버리는 거죠.
그런데 이건 위험하겠더라고요. 지금 문제는 "일시적인 파일 잠금"이지만, 진짜 코드 버그도 언젠가 날 수 있잖아요. 모든 에러를 다 삼켜버리면 그 진짜 버그까지 조용히 묻혀버려요. 나중에 "왜 기록이 이상하지?" 하고 찾을 때 아무 단서도 안 남죠. 안 될 때 원인을 안 보고 그냥 눈 감아버리는 건, 틀린 문제를 채점도 안 하고 넘기는 거랑 같아요.
해법 — 에러를 '종류별로' 다르게 대한다
사실 이 수정안은 제가 AI에게 짜 달라고 한 게 아니에요. 원인 진단과 수정 코드가 이미 밖에서 만들어져 아직 커밋 안 된 채로 올라와 있었고, 저는 AI에게 "네가 직접 코드를 다시 짜지 말고 내용 검증 후 커밋하는 게 목표다"라고 맡겼어요. 그 수정안의 뼈대가 에러를 한 덩어리로 보지 않고, 종류를 나눠서 다르게 대하는 것이었습니다. 구체적으로는 이렇게요.
- 잠금성 에러라면 → 잠깐 기다렸다 다시 시도. 파일 접근 관련 에러(프로그래밍 용어로 IO 예외·접근 거부 예외)이거나, 메시지에 "다른 프로세스가 사용 중"·"액세스가 거부" 같은 말이 있으면, 이건 잠깐 겹친 거예요. 그래서 잠금 에러가 나면 최대 5번까지, 조금씩 대기 시간을 늘려가며 다시 시도하게 돼 있었어요. 다 합쳐도 최대 2.3초 안쪽이라 잠깐이고, 대개 그사이 동기화가 끝나 잠금이 풀립니다.
- 그 외의 에러라면 → 그대로 다시 던진다(재전파). 잠금과 무관한 에러는 진짜 문제일 수 있으니, 삼키지 않고 그대로 위로 올려보내게 돼 있었어요. 진짜 버그를 숨기지 않으려는 거죠.
이 "잠깐 기다렸다 다시"가 왜 통하냐면, 파일 잠금은 아주 짧은 순간이거든요. 동기화가 그 파일을 붙잡고 있는 시간은 길어야 몇백 밀리초예요. 그래서 조금 있다 다시 두드리면 대개 열립니다. 참고로 대기 시간을 매번 조금씩 늘리는 걸 백오프(backoff)라고 해요 — 처음엔 짧게, 안 되면 조금 더 길게 기다리는 방식이죠. 처음부터 길게 기다리면 느리고, 계속 짧게만 두드리면 헛수고라서 점점 늘립니다.
대기 시간표 — 고치기 전과 후
고치기 전 스크립트는 $ErrorActionPreference='Stop' 설정 때문에, 잠금 에러가 한 번만 나도 그 자리에서 비정상 종료했어요. 그게 매 턴 뜨던 빨간 hook error였고요. 수정안은 쓰기가 잠금으로 실패할 때마다 150ms × 회차만큼 기다리게 바꿨습니다.
| 시도 | 고치기 전 | 고친 뒤 대기 | 고친 뒤 누적 |
|---|---|---|---|
| 1회 | 즉시 비정상 종료(빨간 에러) | 150ms | 150ms |
| 2회 | — | 300ms | 450ms |
| 3회 | — | 450ms | 900ms |
| 4회 | — | 600ms | 1,500ms |
| 5회 | — | 750ms | 2,250ms |
| 그래도 잠김 | — | exit 0으로 이번 턴 건너뜀 | — |
누적 2.25초는 지시문에 적힌 값(«누적 ~2.25초»)과 같아요. 잠금 에러인지는 예외 종류(System.IO.IOException, System.UnauthorizedAccessException)나 메시지(being used by another process, 다른 프로세스, denied, 액세스가 거부)로 가렸고요.

최후의 안전판 — 그래도 안 열리면 '조용히 건너뛰기'
그럼 5번을 다 시도했는데도 파일이 계속 잠겨 있으면요? 여기서 마지막 결정이 중요했어요. 그땐 그 턴의 기록을 그냥 조용히 건너뛰게 돼 있었어요.
이게 무책임해 보일 수 있는데, 이유가 있어요. 로깅은 어디까지나 곁다리 작업이거든요. 기록하려다가 정작 본 작업(제가 AI랑 하던 진짜 일)을 멈춰 세우면 그게 더 큰 손해예요. 그래서 "로깅이 본 작업을 방해하지 않는다"를 원칙으로 뒀습니다.
그리고 이걸 마음 편히 건너뛸 수 있는 건, 이 기록 방식이 멱등(idempotent)이기 때문이에요.
멱등(idempotent): 같은 걸 여러 번 해도 결과가 똑같은 성질이에요. 이 로그는 세션 단위로 "블록을 통째로 갈아 끼우는" 방식이라, 이번 턴에 한 번 건너뛰어도 다음 턴에 같은 블록이 다시 통째로 기록돼요. 그래서 한 번 놓쳐도 내용이 유실되지 않아요. 덧붙이기만 했다면 놓친 만큼 영영 사라졌겠지만요.
이 세션에서 AI가 맡은 일이 바로 이 검증이었어요. 일부러 그 파일을 독점으로 잠가놓은 상태를 만들어서, 그런 상황에서도 스크립트가 죽지 않고 조용히 건너뛰는지 확인한 거죠. "고쳤을 것이다"가 아니라 "고쳐진 걸 확인했다"까지 가야 하니까요.
잠금을 일부러 만들어 확인했다 — PR #19
검증은 지시문에 적힌 순서대로 AI가 돌렸어요. 먼저 PowerShell 파서로 구문 에러 0을 확인하고, 임시 출력 파일을 [System.IO.File]::Open(path,'Open','ReadWrite','None')로 독점 잠근 상태에서 스크립트를 돌렸습니다. 결과는 종료 코드 0에 «skipped this turn» 메시지, 파일은 그대로였어요. 잠금을 푼 뒤엔 프롬프트와 세션 태그가 정상으로 기록됐고요.
AI는 이 변경을 파일 하나짜리 커밋으로 올리고 PR #19를 열었어요. gemini 리뷰봇이 한 코멘트에 두 가지를 짚었는데, AI의 대응이 갈렸습니다. 줄 끝 백틱 줄연속은 «trailing whitespace에 취약»하다는 이유로 지웠고, «Start-Sleep -Milliseconds는 PS5.1 미지원» 지적은 사실 오류라 반영하지 않았어요. Windows PowerShell 5.1.19041에서 실제로 돌아가는 걸 확인하고 내린 판단이었죠. 두 번째 커밋 뒤 같은 검증을 다시 통과시키고 머지했습니다.
정리 — 빨간 에러라고 다 같은 게 아니다
| 에러 상황 | 어떻게 판단할까 | 액션 |
|---|---|---|
| "다른 프로세스가 사용 중" | 동기화·동시 접근이 겹친 일시적 잠금 | 🟡 백로그: 잠깐 뒤 재시도 |
| 모든 에러를 다 무시하고 싶다 | 진짜 버그까지 묻힌다 | 🔴 지금: 잠금성만 재시도, 나머지는 재전파 |
| 재시도해도 계속 잠김 | 곁다리 작업이 본 작업을 막으면 안 됨 | 🟢 계속: 멱등이면 이번 턴 조용히 건너뛰기 |
| 재현이 매번 안 됨 | 레이스 컨디션 신호 | 🔴 지금: 코드보다 타이밍·잠금을 의심 |
한마디로, 빨간 에러가 떠도 "종류"부터 나눠보세요. 제가 AI한테 배운 건 "무조건 재시도"도 "무조건 죽기"도 답이 아니라는 거였어요. 일시적인 파일 잠금은 잠깐 기다렸다 다시 하면 풀리고, 진짜 버그는 삼키지 말고 드러내야 하고, 곁다리 작업이라면 최악의 경우 건너뛰어도 됩니다(단 멱등일 때요). 특히 구글 드라이브나 드롭박스처럼 폴더를 실시간 동기화하는 환경에서 스크립트를 돌린다면, 이 "잠금 재시도"는 언젠가 꼭 필요해져요.