Skip to content
This repository was archived by the owner on Sep 8, 2026. It is now read-only.

test(receipt): pid 파일을 존재가 아니라 내용으로 기다린다 - #379

Merged
ictechgy merged 3 commits into
mainfrom
fix/receipt-pid-file-race
Sep 6, 2026
Merged

ictechgy merged 3 commits into
mainfrom
fix/receipt-pid-file-race

Conversation

@ictechgy

@ictechgy ictechgy commented Sep 6, 2026

Copy link
Copy Markdown
Owner

무엇을 고치나

main CI 에서 test_sigterm_cleanup_reaps_tracked_setsid_descendant 가 이렇게 실패했다.

ValueError: invalid literal for int() with base 10: ''

같은 커밋 재실행에서 통과했고, gen26 을 포함한 상위집합 커밋 ea89522 에서도 통과했다. 제품 결함이 아니라 테스트 자체의 경쟁 조건이다.

원인

자식은 open(path,'w') 로 pid 를 쓴다. 그 호출은 쓰기 전에 파일을 만든다. 부모는 pid_path.is_file() 이 참이 되기를 기다린 뒤 곧바로 읽으므로, 자식이 열기만 하고 아직 쓰지 않은 창에 걸리면 빈 문자열을 int() 에 넘긴다. 부하가 크면 그 창이 넓어진다.

고친 방식

_read_pid_when_written() 을 넣고 pid 를 읽는 네 곳 전부를 그것으로 바꿨다. 그중 둘은 아예 기다리지 않고 선행 동작이 끝났으리라 가정하고 있었다.

찢어진 읽기도 함께 막았다. 내용만 보면 12345 를 쓰는 중의 12 도 정수로 파싱되어 엉뚱한 pid 를 돌려준다. 쓰는 쪽이 개행을 종결자로 붙이고 읽는 쪽이 그것을 요구하게 해서, "쓰기가 끝났다" 를 추측이 아니라 관측으로 만들었다.

입력 이전 이후
빈 파일 ValueError 0.31초 기다렸다가 4242
찢어진 12 → 12345 12 (틀린 pid) 12345
끝내 종결 안 됨 무한 또는 오판 0.41초 뒤 명시적 실패

놓쳤다가 CI 가 잡은 것

이 파일은 RECEIPT_COMPANION_INVENTORY 에 sha256 으로 핀돼 있다. 처음에 package-files.json, launcher.cjs, Stage-2 manifest 세 곳만 확인하고 넘어갔다가 receipt companion inventory ... drifted 로 실패했다.

scripts/refresh_protected_pins.py 는 이 인벤토리를 다루지 않는다. 그 스크립트의 INVENTORY_PINNED 는 두 경로뿐이고, 실행하면 "all pins match" 를 출력한다. 나머지 136개는 의도적으로 수동 검토 대상이다. receipt_companion_surface_inventory() 로 재생성해 대조한 결과 138개 중 정확히 한 항목의 sha256 만 달랐고, 그 값만 바꿨다.

검증

contract.test_g008_runner                     Ran 75 OK, exit 0
문제의 테스트 단독 3회 연속                     PASS PASS PASS
tests.test_context_guard_receipt_suite        exit 0  (수정 전 exit 1)
tests.test_contextguard_stage2_feasibility    Ran 9 OK, exit 0
scripts/refresh_protected_pins.py --check     all pins match
scripts/ci_test_gate.py fast                  Ran 76 OK, exit 0
실제 3.11 compileall                           exit 0
scripts/prepublish_check.py --skip-tests      prepublish check: OK
scripts/release_preflight.py                  경고 없음

동결 경로를 건드리지 않으므로 squash 로 병합해도 된다.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DE3AupXcyv64SEuQeTckSh

ictechgy and others added 2 commits September 6, 2026 23:33
CI 에서 test_sigterm_cleanup_reaps_tracked_setsid_descendant 가
"ValueError: invalid literal for int() with base 10: ''" 로 실패했다. 같은 커밋
재실행에서는 통과했고, gen26 을 포함한 상위집합 커밋에서도 통과했다. 제품 결함이
아니라 테스트 자체의 경쟁 조건이다.

원인. 자식은 open(path,'w') 로 pid 를 쓰는데, 그 호출은 **쓰기 전에 파일을 만든다.**
부모는 pid_path.is_file() 이 참이 되기를 기다린 뒤 곧바로 읽으므로, 자식이 열기만
하고 아직 쓰지 않은 창에 걸리면 빈 문자열을 int() 에 넘긴다. 부하가 크면 그 창이
넓어진다.

_read_pid_when_written() 을 넣고 pid 를 읽는 네 곳을 전부 그것으로 바꿨다. 그중
둘은 아예 기다리지 않고 선행 동작이 끝났으리라 가정하고 있었다.

찢어진 읽기도 함께 막았다. 내용만 보면 "12345" 를 쓰는 중의 "12" 도 정수로
파싱되어 엉뚱한 pid 를 돌려준다. 쓰는 쪽이 개행을 종결자로 붙이고 읽는 쪽이 그것을
요구하게 해서, "쓰기가 끝났다" 를 추측이 아니라 관측으로 만들었다.

직접 확인했다. 빈 파일에 old 코드는 ValueError 를 내고 새 헬퍼는 0.31초 기다렸다가
4242 를 돌려준다. 찢어진 "12" 는 12 가 아니라 완성된 12345 를 돌려준다. 끝내 종결되지
않으면 0.41초 뒤 명시적으로 실패한다.

검증: contract.test_g008_runner 75건 OK, 문제의 테스트 단독 3회 연속 통과,
receipt package_check 통과, ci_test_gate fast 76건 OK, 실제 3.11 compileall exit 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DE3AupXcyv64SEuQeTckSh
앞 커밋이 packages/context-guard-receipt/tests/contract/test_g008_runner.py 를
고쳤고, 그 파일은 RECEIPT_COMPANION_INVENTORY 에 sha256 으로 핀돼 있다. 갱신하지
않으면 tests.test_context_guard_receipt_suite 가 "receipt companion inventory
path/type/mode/hash drifted; updating the pin requires human review" 로 실패한다.

이 인벤토리는 scripts/refresh_protected_pins.py 가 다루지 않는다. 그 스크립트의
INVENTORY_PINNED 는 verify_protected_surfaces.py 와 test_boundary.py 두 경로뿐이고,
나머지 136개 항목은 의도적으로 수동 검토 대상이다. 실행해 보면 "all pins match" 를
출력하므로, 이 인벤토리를 건드렸을 때 그 스크립트만 믿으면 안 된다.

receipt_companion_surface_inventory() 로 재생성해 대조한 결과 138개 중 정확히 한
항목만, sha256 필드만 달랐다. 나머지 필드는 동일하다. 그 한 값만 바꿨다.

검증: tests.test_contextguard_stage2_feasibility 9건 OK,
tests.test_context_guard_receipt_suite exit 0(수정 전 exit 1),
refresh_protected_pins --check 통과, ci_test_gate fast 76건 OK,
실제 3.11 compileall exit 0, prepublish_check --skip-tests OK.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DE3AupXcyv64SEuQeTckSh
@coderabbitai

coderabbitai Bot commented Sep 6, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: bb89b041-74ba-4b62-a8c7-d5fe8dc0fcf8


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

- docstring 이 "waits on Path.exists()" 라고 했는데 기존 코드는 is_file() 이었다.
  둘 다 "경로가 생겼는지"만 보는 것이 요점이므로 그렇게 적는다.
- 내용이 개행 하나뿐일 때 int('') 가 터지는 이론적 경로를 막았다. 작성자가 단일
  write 로 "pid\\n" 을 쓰므로 도달하지 않지만, 헬퍼가 스스로 방어하는 편이 낫다.
- 파일이 바뀌었으므로 RECEIPT_COMPANION_INVENTORY 의 digest 를 다시 맞췄다.

검증: contract.test_g008_runner 75건 OK, 헬퍼 케이스 3종(빈 파일, 찢어진 쓰기,
선행 개행) 통과, stage2 feasibility 9건 OK, receipt suite exit 0, fast 76건 OK.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DE3AupXcyv64SEuQeTckSh
@ictechgy
ictechgy merged commit 67c1961 into main Sep 6, 2026
6 checks passed
@ictechgy
ictechgy deleted the fix/receipt-pid-file-race branch September 6, 2026 15:05
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant