docs(document-haness): 관문의 종료 코드 — CASE 한 편을 근거까지 잇고 런 원장을 남긴다
이 저장소 자신의 검사 층을 대상으로 삼았다. 파이프 뒤의 $? 를 읽고 검사기 열넷이 전부 --help 를 받는다고 적었다가, 파이프를 걷고 다시 재니 둘이 exit 1 이었던 일. 관찰·원인·조치가 한 사건으로 닫히고 조치가 capture-evidence.py 다. 증거 여덟 건은 전부 그 도구가 수집했다. proves/doesNotProve 로 「이 출력이 뒷받침하는 것」과 「뒷받침하지 못하는 것」을 증거 쪽에 적어 두었다 — 본문이 그 경계를 넘었는지 대조할 것이 생긴다. 함께 올린 Question 은 「검사할 것이 없을 때 관문은 무엇을 내야 하는가」다. 현상은 재현했고 조치가 없어 Case 로 올리지 않았다. 조치 없이 쓰면 관찰만 있고 결과가 없는 글이 된다. 런 원장은 단계마다 서브에이전트를 하나씩 띄운 기록이다. 넷이 실제로 무언가를 잡았다 — S3 이 「argparse 를 쓰지 않는 셋」이 증거 원문과 어긋나는 것을(넷이다), S1 이 그 뿌리를 SSOT·앵커·증거 메타에서, S5 가 「증거 여섯 개」가 frontmatter 의 다섯과 어긋나는 것을, S6 이 계약의 「한 번밖에 안 써서」가 메타 여덟 건과 어긋나는 것을. 셋 다 한 세션이 일곱 단계를 겸했으면 안 나왔다. 내가 쓴 글을 내가 다시 읽는 것이기 때문이다. style_profile 은 exit 1 로 그대로 적었다. 관문이 아니라 측정이고, 돌리지 않은 값을 0 으로 적는 것이 이 원장이 막으려는 바로 그것이다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
This commit is contained in:
co-authored by
Claude Opus 5
parent
cf3996711f
commit
230e1b20cb
@@ -0,0 +1,2 @@
|
||||
그림의 정본 — <이름>/{context.json, spec.json, prompt.md}.
|
||||
technical-visualizer 스킬이 만든다. 손으로 SVG 를 그리지 않는다.
|
||||
@@ -0,0 +1,3 @@
|
||||
그림. 그림 하나가 폴더 하나다 — <이름>/<이름>.svg 와 편집 형식들.
|
||||
기록의 assets: file: 도 이 폴더를 가리킨다. Studio 에 올릴 사본을 따로 두지 않는다 —
|
||||
사본을 두면 정본이 둘이 되고, 사본 쪽에는 ../../.techviz/<이름>/ 이 없어 다시 만들 수 없다.
|
||||
@@ -0,0 +1,345 @@
|
||||
# document-haness 의 검사 층 — 관문이 무엇을 보고 무엇을 안 보는가
|
||||
|
||||
이 문서는 `document-haness` 저장소 자신의 검사 층을 대상으로 삼는다. 이 저장소는 기술 글을
|
||||
만드는 파이프라인이고, 그 파이프라인이 글을 통과시킬지 말지는 `scripts/` 의 검사기와
|
||||
스킬이 들고 있는 `.mjs` 검사기가 정한다. **그 검사기들이 실제로 무엇을 보는지**를 실행으로
|
||||
확인한 기록이다.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| 대상 저장소 | `document-haness` |
|
||||
| 리비전 | `43e1aadef077ad93c30495df428ee3a71dd73f4a` (`main`) |
|
||||
| 확인한 worktree | `dh-B` (`harness/B-implementation`, 같은 커밋에서 갈라짐) |
|
||||
| 확인한 날 | 2026-09-10 |
|
||||
| 실행 환경 | python 3.12.3 · node v24.14.0 · bash 5.2.21(1)-release |
|
||||
|
||||
---
|
||||
|
||||
## §1 검사기 열넷이 있는 자리
|
||||
|
||||
`scripts/` 아래에 `.py` 파일 열넷이 있다. 리비전 `43e1aad` 에서 센 값이다.
|
||||
|
||||
```
|
||||
audit-records.py
|
||||
build-tech-log-tree.py
|
||||
check-figure-overlap.py
|
||||
check-figure-text.py
|
||||
fold-analysis-into-final.py
|
||||
fold-studio-contract-into-index.py
|
||||
preview-figure.py
|
||||
studio-body.py
|
||||
techlog.py
|
||||
verify-pipeline-run.py
|
||||
verify-pipeline.py
|
||||
verify-project-layout.py
|
||||
verify-refactor-work-item.py
|
||||
verify-tech-log-tree.py
|
||||
```
|
||||
|
||||
이 중 `techlog.py` 는 CLI 가 아니라 공유 라이브러리다. `fold-*.py` 둘은 검사기가 아니라
|
||||
작업 재료를 옮기는 도구다. 나머지 열하나가 관문으로 불린다.
|
||||
|
||||
관문은 **종료 코드로 말한다.** `running-tech-log-pipeline` 의 단계 계약이
|
||||
「관문은 종료 코드가 0 이어야 지난 것이다. 0 이 아니면 그 단계는 `FAILED` 이고 다음 단계로
|
||||
넘어가지 않는다」 라고 적었고, 런 원장 `runs/<프로젝트>/<runId>/run.json` 의
|
||||
`stages[].gates[].exit` 에 그 값이 남는다.
|
||||
|
||||
## §2 관찰한 것
|
||||
|
||||
검사기 목록을 만들려고 열넷에 `--help` 를 돌렸다. 한 줄짜리 셸 반복문이었고, 출력이 길어서
|
||||
`head` 로 잘랐다.
|
||||
|
||||
```bash
|
||||
for s in $(git ls-tree --name-only 43e1aad scripts/ | grep '\.py$'); do
|
||||
timeout 60 python3 $s --help 2>&1 | head -30 >/dev/null
|
||||
code=$?
|
||||
n=$(basename $s)
|
||||
echo "$n exit=$code"
|
||||
done
|
||||
```
|
||||
|
||||
열넷이 전부 `exit=0` 이었다.
|
||||
|
||||
```
|
||||
audit-records.py exit=0
|
||||
build-tech-log-tree.py exit=0
|
||||
check-figure-overlap.py exit=0
|
||||
check-figure-text.py exit=0
|
||||
fold-analysis-into-final.py exit=0
|
||||
fold-studio-contract-into-index.py exit=0
|
||||
preview-figure.py exit=0
|
||||
studio-body.py exit=0
|
||||
techlog.py exit=0
|
||||
verify-pipeline-run.py exit=0
|
||||
verify-pipeline.py exit=0
|
||||
verify-project-layout.py exit=0
|
||||
verify-refactor-work-item.py exit=0
|
||||
verify-tech-log-tree.py exit=0
|
||||
```
|
||||
|
||||
이 저장소를 함께 조사한 다른 세션도 같은 값을 얻어 「`scripts/*.py` 14개 전부 `--help` 가
|
||||
종료 코드 0 으로 돌아온다」 를 확인 등급 **확인함**으로 적었다. 두 사람이 같은 값을 얻었으니
|
||||
맞는 값처럼 보였다.
|
||||
|
||||
## §3 파이프 뒤의 종료 코드
|
||||
|
||||
틀린 것은 검사기가 아니라 재는 방법이었다. 파이프라인의 `$?` 는 **마지막** 명령의 종료
|
||||
코드다. `head` 는 언제나 성공하므로 앞의 `python3` 가 무엇을 반환하든 `$?` 는 0 이 된다.
|
||||
|
||||
```bash
|
||||
set +o pipefail
|
||||
false | head -1; echo "false | head -1 -> exit=$?"
|
||||
false; echo "false -> exit=$?"
|
||||
```
|
||||
|
||||
```
|
||||
false | head -1 -> exit=0
|
||||
false -> exit=1
|
||||
bash 5.2.21(1)-release
|
||||
```
|
||||
|
||||
POSIX 셸의 정의된 동작이고 이 셸의 특이점이 아니다. `pipefail` 을 켜거나
|
||||
`${PIPESTATUS[0]}` 를 읽으면 앞 명령의 값을 얻는다.
|
||||
|
||||
같은 착각이 한 번 더 났다. 파이프를 걷어 내고 다시 잴 때 이렇게 썼다.
|
||||
|
||||
```bash
|
||||
out=$(timeout 60 python3 $s --help 2>&1)
|
||||
echo "$(basename $s) exit=$?"
|
||||
```
|
||||
|
||||
명령 치환 `$(basename $s)` 가 먼저 실행되면서 `$?` 를 덮어썼다. 그래서 두 번째 측정도
|
||||
열넷 전부 0 이었다. `code=$?` 를 명령 바로 다음 줄에 두고서야 값이 갈렸다.
|
||||
|
||||
## §4 다시 잰 값
|
||||
|
||||
```bash
|
||||
for s in $(git ls-tree --name-only 43e1aad scripts/ | grep '\.py$'); do
|
||||
out=$(timeout 60 python3 $s --help 2>&1)
|
||||
code=$?
|
||||
n=$(basename $s)
|
||||
echo "$n exit=$code"
|
||||
done
|
||||
```
|
||||
|
||||
```
|
||||
audit-records.py exit=0
|
||||
build-tech-log-tree.py exit=1
|
||||
check-figure-overlap.py exit=0
|
||||
check-figure-text.py exit=0
|
||||
fold-analysis-into-final.py exit=0
|
||||
fold-studio-contract-into-index.py exit=0
|
||||
preview-figure.py exit=0
|
||||
studio-body.py exit=0
|
||||
techlog.py exit=0
|
||||
verify-pipeline-run.py exit=0
|
||||
verify-pipeline.py exit=0
|
||||
verify-project-layout.py exit=0
|
||||
verify-refactor-work-item.py exit=1
|
||||
verify-tech-log-tree.py exit=0
|
||||
```
|
||||
|
||||
두 측정의 차이는 두 줄뿐이다.
|
||||
|
||||
```
|
||||
2c2
|
||||
< build-tech-log-tree.py exit=0
|
||||
---
|
||||
> build-tech-log-tree.py exit=1
|
||||
13c13
|
||||
< verify-refactor-work-item.py exit=0
|
||||
---
|
||||
> verify-refactor-work-item.py exit=1
|
||||
```
|
||||
|
||||
## §5 argparse 를 쓰지 않는 넷
|
||||
|
||||
열넷 중 넷이 `argparse` 를 쓰지 않는다.
|
||||
|
||||
```
|
||||
ARGPARSE 없음 audit-records.py
|
||||
ARGPARSE 없음 build-tech-log-tree.py
|
||||
argparse check-figure-overlap.py
|
||||
argparse check-figure-text.py
|
||||
argparse fold-analysis-into-final.py
|
||||
argparse fold-studio-contract-into-index.py
|
||||
argparse preview-figure.py
|
||||
argparse studio-body.py
|
||||
ARGPARSE 없음 techlog.py
|
||||
argparse verify-pipeline-run.py
|
||||
argparse verify-pipeline.py
|
||||
argparse verify-project-layout.py
|
||||
ARGPARSE 없음 verify-refactor-work-item.py
|
||||
argparse verify-tech-log-tree.py
|
||||
```
|
||||
|
||||
`techlog.py` 는 CLI 가 아니라서 인자를 안 읽는다. 남은 셋은 `--help` 를 **옵션이 아니라
|
||||
위치 인자로 먹는다.** `audit-records.py` 는 그것을 프로젝트 이름으로 받아 「문제 없음」을
|
||||
찍고, `build-tech-log-tree.py` 와 `verify-refactor-work-item.py` 는 그 이름의 파일을 못 찾아
|
||||
실패한다.
|
||||
|
||||
```
|
||||
$ python3 scripts/audit-records.py --help
|
||||
|
||||
--help — 기록 0건 · 원문 0 · 메타 0 · 렌더 0
|
||||
문제 없음
|
||||
|
||||
합계 0건
|
||||
exit=0
|
||||
|
||||
$ python3 scripts/build-tech-log-tree.py --help
|
||||
--help: tech-log-tree.json 이 없다. 글감을 먼저 적는다
|
||||
exit=1
|
||||
|
||||
$ python3 scripts/verify-refactor-work-item.py --help
|
||||
REFACTOR WORK ITEM VERIFICATION: FAIL
|
||||
- invalid work-item.json: --help/work-item.json
|
||||
exit=1
|
||||
```
|
||||
|
||||
**`--help` 가 exit 0 이라는 것이 usage 가 나왔다는 뜻은 아니다.** `audit-records.py` 는
|
||||
`--help` 라는 이름의 프로젝트를 검사해서 통과시킨 것이다.
|
||||
|
||||
## §6 실재하지 않는 프로젝트 이름
|
||||
|
||||
`audit-records.py --help` 가 「문제 없음」을 찍은 것은 `--help` 가 특별해서가 아니다.
|
||||
**없는 프로젝트를 검사하면 검사할 것이 없어 통과가 된다.** 같은 이름
|
||||
`nonexistent-project` 를 여섯 관문에 주면 이렇게 갈린다.
|
||||
|
||||
```
|
||||
$ python3 scripts/audit-records.py nonexistent-project
|
||||
nonexistent-project — 기록 0건 · 원문 0 · 메타 0 · 렌더 0
|
||||
문제 없음
|
||||
합계 0건
|
||||
exit=0
|
||||
|
||||
$ python3 scripts/verify-tech-log-tree.py nonexistent-project
|
||||
[nonexistent-project] records=0
|
||||
! warn 1 분해 계약 없음
|
||||
· nonexistent-project: tech-log-tree.json 이 없다. 디렉터리가 정본 노릇을 하고 있다
|
||||
TECH LOG TREE: PASS — 프로젝트 1 · error 0 · warn 1
|
||||
exit=0
|
||||
|
||||
$ python3 scripts/check-figure-text.py nonexistent-project
|
||||
FIGURE TEXT: PASS — 그림 0장 · 문장 0건
|
||||
exit=0
|
||||
|
||||
$ python3 scripts/check-figure-overlap.py nonexistent-project
|
||||
볼 그림이 없다
|
||||
exit=0
|
||||
|
||||
$ python3 scripts/verify-project-layout.py nonexistent-project
|
||||
PROJECT LAYOUT: FAIL — 프로젝트 1 · error 1 · warn 0
|
||||
[nonexistent-project] —
|
||||
✗ error 1 final/document.md 가 없다
|
||||
· nonexistent-project
|
||||
exit=1
|
||||
|
||||
$ node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs nonexistent-project --repo
|
||||
nonexistent-project: tech-log-tree.json 이 없다
|
||||
exit=2
|
||||
```
|
||||
|
||||
여섯 중 넷이 아무것도 검사하지 않고 통과를 보고한다. 잡아내는 것은
|
||||
`verify-project-layout.py`(`final/document.md` 를 먼저 찾는다)와
|
||||
`check_evidence.mjs`(`tech-log-tree.json` 을 먼저 찾는다) 둘뿐이다.
|
||||
|
||||
## §7 원장은 종료 코드를 다시 재지 않는다
|
||||
|
||||
런 원장 검사기 `scripts/verify-pipeline-run.py` 가 관문에 대해 보는 것은 두 가지다.
|
||||
|
||||
```python
|
||||
cmds = " ; ".join(str(g.get("cmd") or "") for g in gates)
|
||||
for token in spec["gates"]:
|
||||
if token not in cmds:
|
||||
...
|
||||
for g in gates:
|
||||
if g.get("exit") not in (0, "0"):
|
||||
...
|
||||
```
|
||||
|
||||
관문 명령을 **다시 실행하지 않는다.** `exit` 는 그 런이 스스로 적어 넣은 숫자다. 그리고
|
||||
`cmd` 안에 토큰 문자열(`"check-figure-text.py"`)이 있는지만 보고 **어느 프로젝트에 돌렸는지는
|
||||
보지 않는다.**
|
||||
|
||||
§6 과 이어 붙이면 이렇게 된다. 원장에
|
||||
`{"cmd": "python3 scripts/check-figure-text.py <오타난 이름>", "exit": 0}` 을 적으면 그 명령은
|
||||
실제로도 exit 0 이고, 원장 검사기도 통과시킨다. 그림을 한 장도 검사하지 않은 런이 「절차를
|
||||
지켰다」로 판정된다.
|
||||
|
||||
실제 원장에도 명령이 자리표시자가 든 요약형으로 적혀 있어 그대로 재실행할 수 없다.
|
||||
|
||||
```
|
||||
"cmd": "python3 scripts/studio-body.py <기록.md> -o /tmp/sb-<slug>.md (44건)"
|
||||
```
|
||||
|
||||
## §8 종료 코드를 손으로 적을 수 없게 만든다
|
||||
|
||||
관문 결과를 사람이 옮겨 적는 한 §2 와 §7 은 같은 뿌리에서 계속 난다. 그래서 명령을 돌리는
|
||||
자리와 종료 코드를 적는 자리를 하나로 붙였다. `scripts/capture-evidence.py` 는 명령을
|
||||
`subprocess` 로 직접 실행하고 **그 프로세스의 반환값을 그대로** 메타에 적는다.
|
||||
|
||||
```python
|
||||
proc = subprocess.run(command, cwd=cwd, capture_output=True,
|
||||
text=True, timeout=timeout)
|
||||
exit_code, out = proc.returncode, proc.stdout + proc.stderr
|
||||
```
|
||||
|
||||
셸을 거치지 않으므로 파이프가 낄 자리가 없고, 종료 코드를 인자로 받지 않으므로 손으로 적을
|
||||
경로도 없다. 원문은 `final/evidence/raw/` 에, 실행 메타는 `final/evidence/meta/` 에 같은
|
||||
이름으로 함께 떨어진다.
|
||||
|
||||
실제로 떨어진 메타 하나다. `§4` 의 측정을 담은 것이다.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "help-exit-codes-measured-without-a-pipe",
|
||||
"kind": "terminal",
|
||||
"sourceRevision": "43e1aadef077ad93c30495df428ee3a71dd73f4a",
|
||||
"sourceDirty": true,
|
||||
"executedAt": "2026-09-10T09:54:41+09:00",
|
||||
"exitCode": 0,
|
||||
"exitCodeSource": "실행한 프로세스의 반환값. 손으로 적지 않는다",
|
||||
"rawPath": "evidence/raw/help-exit-codes-measured-without-a-pipe.txt",
|
||||
"proves": "파이프 없이 재면 같은 14개 중 build-tech-log-tree.py 와 verify-refactor-work-item.py 가 exit 1 이다",
|
||||
"doesNotProve": "다른 인자에서의 동작. --help 하나만 잰 값이다",
|
||||
"sha256": "e8c134d07592bb1b051ea6ad3abf952a9fa502fc85eb26cdb56194447ea03a9b",
|
||||
"bytes": 410
|
||||
}
|
||||
```
|
||||
|
||||
여기서 `exitCode: 0` 은 **측정 반복문 자체가 성공했다**는 뜻이다. 열넷 각각의 종료 코드는
|
||||
원문 `raw/` 안에 있다. 반복문의 종료 코드와 그 안에서 잰 값은 다른 것이고, 메타는 앞의 것을
|
||||
적는다.
|
||||
|
||||
`sourceDirty` 는 그 실행 시점에 작업 트리에 커밋 안 된 변경이 있었는지다. 있으면
|
||||
`sourceRevision` 이 그 출력을 설명하지 못한다.
|
||||
|
||||
처음 판에서는 이 도구도 인자를 잘못 먹었다. `argparse.REMAINDER` 로 명령을 받았더니
|
||||
`--proves` 부터가 실행할 명령으로 딸려 가 `No such file or directory: '--proves'` 로 죽었다.
|
||||
`--` 앞뒤를 직접 가르도록 고쳤고 그 이유를 코드에 한 줄로 남겼다.
|
||||
|
||||
```python
|
||||
# `--` 앞뒤를 먼저 가른다. argparse.REMAINDER 에 맡기면 옵션이 명령으로 딸려 간다
|
||||
argv = sys.argv[1:]
|
||||
```
|
||||
|
||||
§5 에서 `--help` 를 위치 인자로 먹은 세 스크립트와 같은 종류의 실수다.
|
||||
|
||||
셸 한 줄을 감싸는 래퍼로 만들 수도 있었다. 그러면 파이프를 다시 쓸 수 있게 되고, 값을 두 번
|
||||
틀리게 만든 것이 그 셸이라 아예 거치지 않기로 했다. 감수한 것은 셸 문법이 필요한 명령을
|
||||
`bash -c` 로 넘겨야 한다는 점이다 — 그 안에서 다시 파이프를 쓰면 같은 실수가 난다.
|
||||
|
||||
기존 경로를 지우지 않았다. 손으로 만든 `raw/`·`meta/` 도 그대로 유효하고 이 도구는
|
||||
선택적으로 부른다.
|
||||
|
||||
## §9 확인하지 못한 것
|
||||
|
||||
- **셸을 쓰는 다른 자리.** `--help` 하나만 쟀다. 다른 인자에서 같은 모양인지는 안 봤다.
|
||||
- **`argparse` 를 쓰는 열이 모든 인자를 옳게 다루는지.** `--help` 만 통과한 것을 확인했다.
|
||||
- **`nonexistent-project` 넷이 실재하는 프로젝트에서도 통과만 내는지.** 아니다 —
|
||||
`check-figure-text.py ca-tmpl` 은 exit 1 을 낸다. §6 은 「대상이 없을 때」의 동작이다.
|
||||
- **`capture-evidence.py` 가 이 저장소의 모든 증거 수집을 대체할 수 있는지.** 브라우저 캡처와
|
||||
대화형 명령은 이 경로로 못 담는다.
|
||||
- **원장 검사기 수정의 영향.** §7 을 고치는 것은 이 저장소의 다른 담당 몫이고 여기서는
|
||||
현상만 적는다.
|
||||
@@ -0,0 +1 @@
|
||||
Playwright MCP 로 찍은 브라우저 캡처. 무엇을 찍었는지 한 줄을 적는다.
|
||||
@@ -0,0 +1 @@
|
||||
그 실행의 command·cwd·executedAt·exitCode·revision. 형식은 evidence.json.
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "argparse-absent-scripts",
|
||||
"kind": "terminal",
|
||||
"sourceRevision": "43e1aadef077ad93c30495df428ee3a71dd73f4a",
|
||||
"sourceDirty": true,
|
||||
"executedAt": "2026-09-10T09:54:57+09:00",
|
||||
"executedAtSource": "이 도구가 명령을 실행한 시각",
|
||||
"command": "bash -c cd /home/donghyeon/workspace/chat-gpt-container/dh-B; for s in $(git ls-tree --name-only 43e1aad scripts/ | grep '\\.py$'); do if grep -q '^import argparse' $s; then echo \"argparse $(basename $s)\"; else echo \"ARGPARSE 없음 $(basename $s)\"; fi; done",
|
||||
"cwd": ".",
|
||||
"exitCode": 0,
|
||||
"exitCodeSource": "실행한 프로세스의 반환값. 손으로 적지 않는다",
|
||||
"rawPath": "evidence/raw/argparse-absent-scripts.txt",
|
||||
"presentationPath": null,
|
||||
"proves": "14개 중 넷이 argparse 를 쓰지 않는다",
|
||||
"doesNotProve": "argparse 를 쓰는 열이 모든 인자를 옳게 다룬다는 것",
|
||||
"sha256": "2412e5266e3889012f6a0c9732234dbfcc1a611c3368c1851d61b6c40307ff67",
|
||||
"bytes": 496
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "exit-code-through-a-pipe",
|
||||
"kind": "terminal",
|
||||
"sourceRevision": "43e1aadef077ad93c30495df428ee3a71dd73f4a",
|
||||
"sourceDirty": true,
|
||||
"executedAt": "2026-09-10T09:54:19+09:00",
|
||||
"executedAtSource": "이 도구가 명령을 실행한 시각",
|
||||
"command": "bash -c set +o pipefail; false | head -1; echo \"false | head -1 -> exit=$?\"; false; echo \"false -> exit=$?\"; echo \"bash $BASH_VERSION\"",
|
||||
"cwd": ".",
|
||||
"exitCode": 0,
|
||||
"exitCodeSource": "실행한 프로세스의 반환값. 손으로 적지 않는다",
|
||||
"rawPath": "evidence/raw/exit-code-through-a-pipe.txt",
|
||||
"presentationPath": null,
|
||||
"proves": "파이프의 마지막 명령이 성공하면 $? 가 0 이다. 앞 명령의 종료 코드는 사라진다",
|
||||
"doesNotProve": "이 셸이 특별하다는 것. POSIX 셸의 정의된 동작이다",
|
||||
"sha256": "b17529b23ee69c887fbf28a474a7c566c9d9d3ef2e5b0d1c115e90649d60c36f",
|
||||
"bytes": 77
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "gates-on-a-nonexistent-project",
|
||||
"kind": "terminal",
|
||||
"sourceRevision": "43e1aadef077ad93c30495df428ee3a71dd73f4a",
|
||||
"sourceDirty": true,
|
||||
"executedAt": "2026-09-10T09:57:21+09:00",
|
||||
"executedAtSource": "이 도구가 명령을 실행한 시각",
|
||||
"command": "bash -c cd /home/donghyeon/workspace/chat-gpt-container/dh-B; for cmd in 'python3 scripts/audit-records.py nonexistent-project' 'python3 scripts/verify-tech-log-tree.py nonexistent-project' 'python3 scripts/check-figure-text.py nonexistent-project' 'python3 scripts/check-figure-overlap.py nonexistent-project' 'python3 scripts/verify-project-layout.py nonexistent-project' 'node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs nonexistent-project --repo'; do out=$(timeout 120 sh -c \"$cmd\" 2>&1); code=$?; echo \"\\$ $cmd\"; echo \"$out\" | grep -v '^$' | head -4; echo \"exit=$code\"; echo; done",
|
||||
"cwd": ".",
|
||||
"exitCode": 0,
|
||||
"exitCodeSource": "실행한 프로세스의 반환값. 손으로 적지 않는다",
|
||||
"rawPath": "evidence/raw/gates-on-a-nonexistent-project.txt",
|
||||
"presentationPath": null,
|
||||
"proves": "실재하지 않는 프로젝트 이름에 검사기 여섯 중 넷이 exit 0 을 낸다",
|
||||
"doesNotProve": "그 넷이 실재하는 프로젝트에서도 통과만 낸다는 것. check-figure-text.py ca-tmpl 은 exit 1 이다",
|
||||
"sha256": "3afad37da2e49290c5cf75a6970562b649fd5ddab6e54870ba9d45dd4c80ab8f",
|
||||
"bytes": 1080
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "help-exit-codes-measured-through-a-pipe",
|
||||
"kind": "terminal",
|
||||
"sourceRevision": "43e1aadef077ad93c30495df428ee3a71dd73f4a",
|
||||
"sourceDirty": true,
|
||||
"executedAt": "2026-09-10T09:54:41+09:00",
|
||||
"executedAtSource": "이 도구가 명령을 실행한 시각",
|
||||
"command": "bash -c cd /home/donghyeon/workspace/chat-gpt-container/dh-B; for s in $(git ls-tree --name-only 43e1aad scripts/ | grep '\\.py$'); do timeout 60 python3 $s --help 2>&1 | head -30 >/dev/null; code=$?; n=$(basename $s); echo \"$n exit=$code\"; done",
|
||||
"cwd": ".",
|
||||
"exitCode": 0,
|
||||
"exitCodeSource": "실행한 프로세스의 반환값. 손으로 적지 않는다",
|
||||
"rawPath": "evidence/raw/help-exit-codes-measured-through-a-pipe.txt",
|
||||
"presentationPath": null,
|
||||
"proves": "파이프를 끼고 재면 43e1aad 의 scripts/*.py 14개가 전부 --help exit 0 으로 보인다",
|
||||
"doesNotProve": "그 14개가 실제로 --help 를 지원한다는 것",
|
||||
"sha256": "0827ec5c575d24c9998fa79f3cb26f0d4e98b1c647e5121d9e651b13784ff611",
|
||||
"bytes": 410
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "help-exit-codes-measured-without-a-pipe",
|
||||
"kind": "terminal",
|
||||
"sourceRevision": "43e1aadef077ad93c30495df428ee3a71dd73f4a",
|
||||
"sourceDirty": true,
|
||||
"executedAt": "2026-09-10T09:54:41+09:00",
|
||||
"executedAtSource": "이 도구가 명령을 실행한 시각",
|
||||
"command": "bash -c cd /home/donghyeon/workspace/chat-gpt-container/dh-B; for s in $(git ls-tree --name-only 43e1aad scripts/ | grep '\\.py$'); do out=$(timeout 60 python3 $s --help 2>&1); code=$?; n=$(basename $s); echo \"$n exit=$code\"; done",
|
||||
"cwd": ".",
|
||||
"exitCode": 0,
|
||||
"exitCodeSource": "실행한 프로세스의 반환값. 손으로 적지 않는다",
|
||||
"rawPath": "evidence/raw/help-exit-codes-measured-without-a-pipe.txt",
|
||||
"presentationPath": null,
|
||||
"proves": "파이프 없이 재면 같은 14개 중 build-tech-log-tree.py 와 verify-refactor-work-item.py 가 exit 1 이다",
|
||||
"doesNotProve": "다른 인자에서의 동작. --help 하나만 잰 값이다",
|
||||
"sha256": "e8c134d07592bb1b051ea6ad3abf952a9fa502fc85eb26cdb56194447ea03a9b",
|
||||
"bytes": 410
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "redaction-covers-command-lines",
|
||||
"kind": "terminal",
|
||||
"sourceRevision": "43e1aadef077ad93c30495df428ee3a71dd73f4a",
|
||||
"sourceDirty": true,
|
||||
"executedAt": "2026-09-10T10:09:26+09:00",
|
||||
"executedAtSource": "이 도구가 명령을 실행한 시각",
|
||||
"command": "bash -c cd /home/donghyeon/workspace/chat-gpt-container/dh-B; python3 scripts/terminal-evidence/render_terminal.py /tmp/claude-1000/-home-donghyeon-workspace-chat-gpt-container-dh-B/0106b08a-c31d-459b-8469-4e9326cfe523/scratchpad/r8/raw.txt /tmp/claude-1000/-home-donghyeon-workspace-chat-gpt-container-dh-B/0106b08a-c31d-459b-8469-4e9326cfe523/scratchpad/r8/out.svg --command 'cat evidence-sample.txt' --cwd /tmp --exit-code 0 --executed-at 2026-09-10T10:20:00+09:00; echo '--- 렌더한 SVG 의 text 노드 ---'; python3 -c \"\nimport re\ns=open('/tmp/claude-1000/-home-donghyeon-workspace-chat-gpt-container-dh-B/0106b08a-c31d-459b-8469-4e9326cfe523/scratchpad/r8/out.svg').read()\nfor t in re.findall(r'<text[^>]*>(.*?)</text>', s, re.S):\n if any(k in t for k in ('curl','psql','Authorization','GET')): print(t)\nprint('TESTONLY 남은 횟수:', s.count('TESTONLY'))\n\"",
|
||||
"cwd": ".",
|
||||
"exitCode": 0,
|
||||
"exitCodeSource": "실행한 프로세스의 반환값. 손으로 적지 않는다",
|
||||
"rawPath": "evidence/raw/guards/redaction-covers-command-lines.txt",
|
||||
"presentationPath": null,
|
||||
"proves": "렌더러가 명령 인자 안의 Bearer·Cookie 와 접속 문자열 암호를 [REDACTED] 로 덮고, 렌더한 SVG 에 합성 자격증명이 한 건도 남지 않는다",
|
||||
"doesNotProve": "raw 원문에 secret 이 들어가도 된다는 것. 렌더러 마스킹은 그물이지 방벽이 아니다",
|
||||
"sha256": "2fa64df31ba1dea4194ea744a33c2e4fb921e0a45293f46cdf70e89e8ce7c2bb",
|
||||
"bytes": 320
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "redaction-keeps-the-command-intact",
|
||||
"kind": "terminal",
|
||||
"sourceRevision": "43e1aadef077ad93c30495df428ee3a71dd73f4a",
|
||||
"sourceDirty": true,
|
||||
"executedAt": "2026-09-10T10:33:11+09:00",
|
||||
"executedAtSource": "이 도구가 명령을 실행한 시각",
|
||||
"command": "bash -c cd /home/donghyeon/workspace/chat-gpt-container/dh-B; python3 -c \"\nimport importlib.util, html, re, subprocess\nspec=importlib.util.spec_from_file_location('rt','scripts/terminal-evidence/render_terminal.py')\nm=importlib.util.module_from_spec(spec); spec.loader.exec_module(m)\nraw=open('/tmp/claude-1000/-home-donghyeon-workspace-chat-gpt-container-dh-B/0106b08a-c31d-459b-8469-4e9326cfe523/scratchpad/r8/raw2.txt').read().splitlines()\nsubprocess.run(['python3','scripts/terminal-evidence/render_terminal.py','/tmp/claude-1000/-home-donghyeon-workspace-chat-gpt-container-dh-B/0106b08a-c31d-459b-8469-4e9326cfe523/scratchpad/r8/raw2.txt','/tmp/claude-1000/-home-donghyeon-workspace-chat-gpt-container-dh-B/0106b08a-c31d-459b-8469-4e9326cfe523/scratchpad/r8/out2.svg','--command','cat evidence-sample.txt','--cwd','/tmp','--exit-code','0','--executed-at','2026-09-10T10:45:00+09:00'],capture_output=True)\nsvg=open('/tmp/claude-1000/-home-donghyeon-workspace-chat-gpt-container-dh-B/0106b08a-c31d-459b-8469-4e9326cfe523/scratchpad/r8/out2.svg').read()\nprint('원문 줄 -> 렌더 줄 (따옴표 수)')\nfor line in raw:\n out=m.redact_line(line)\n print(f' {line.count(chr(34))} -> {out.count(chr(34))} {out}')\nprint()\nprint('렌더한 SVG 에 TESTONLY 가 남은 횟수:', svg.count('TESTONLY'))\n\"",
|
||||
"cwd": ".",
|
||||
"exitCode": 0,
|
||||
"exitCodeSource": "실행한 프로세스의 반환값. 손으로 적지 않는다",
|
||||
"rawPath": "evidence/raw/guards/redaction-keeps-the-command-intact.txt",
|
||||
"presentationPath": null,
|
||||
"proves": "마스킹이 자격증명을 덮으면서 줄마다 따옴표 수를 그대로 둔다. 합성값(TESTONLY-*)이 렌더 결과에 하나도 안 남는다",
|
||||
"doesNotProve": "모든 자격증명 모양을 덮는다는 것. 확인한 것은 여기 여섯 줄이다",
|
||||
"sha256": "40ed59329c1b4d1a66e49c941216c8953c1f63f0a459f2f583c48e060d0ce4b2",
|
||||
"bytes": 464
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "what-the-three-print-for-help",
|
||||
"kind": "terminal",
|
||||
"sourceRevision": "43e1aadef077ad93c30495df428ee3a71dd73f4a",
|
||||
"sourceDirty": true,
|
||||
"executedAt": "2026-09-10T09:57:21+09:00",
|
||||
"executedAtSource": "이 도구가 명령을 실행한 시각",
|
||||
"command": "bash -c cd /home/donghyeon/workspace/chat-gpt-container/dh-B; for s in audit-records build-tech-log-tree verify-refactor-work-item; do echo \"\\$ python3 scripts/$s.py --help\"; out=$(timeout 60 python3 scripts/$s.py --help 2>&1); code=$?; echo \"$out\"; echo \"exit=$code\"; echo; done",
|
||||
"cwd": ".",
|
||||
"exitCode": 0,
|
||||
"exitCodeSource": "실행한 프로세스의 반환값. 손으로 적지 않는다",
|
||||
"rawPath": "evidence/raw/what-the-three-print-for-help.txt",
|
||||
"presentationPath": null,
|
||||
"proves": "argparse 없는 넷 가운데 CLI 인 셋이 --help 를 프로젝트 이름·경로로 먹는다",
|
||||
"doesNotProve": "다른 잘못된 인자에서도 같은 모양이라는 것",
|
||||
"sha256": "91101c72e788bce9c81d090fe605ba92433f67b94476cfcb641f6e55e75d0035",
|
||||
"bytes": 409
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
명령 출력·csv·덤프 원문. 여기가 정본이다.
|
||||
하위 폴더를 자유롭게 둔다(explain/, guards/). 폴더마다 무엇을 담았는지 한 줄을 README.txt 에 적는다.
|
||||
@@ -0,0 +1,14 @@
|
||||
ARGPARSE 없음 audit-records.py
|
||||
ARGPARSE 없음 build-tech-log-tree.py
|
||||
argparse check-figure-overlap.py
|
||||
argparse check-figure-text.py
|
||||
argparse fold-analysis-into-final.py
|
||||
argparse fold-studio-contract-into-index.py
|
||||
argparse preview-figure.py
|
||||
argparse studio-body.py
|
||||
ARGPARSE 없음 techlog.py
|
||||
argparse verify-pipeline-run.py
|
||||
argparse verify-pipeline.py
|
||||
argparse verify-project-layout.py
|
||||
ARGPARSE 없음 verify-refactor-work-item.py
|
||||
argparse verify-tech-log-tree.py
|
||||
@@ -0,0 +1,3 @@
|
||||
false | head -1 -> exit=0
|
||||
false -> exit=1
|
||||
bash 5.2.21(1)-release
|
||||
@@ -0,0 +1,32 @@
|
||||
$ python3 scripts/audit-records.py nonexistent-project
|
||||
nonexistent-project — 기록 0건 · 원문 0 · 메타 0 · 렌더 0
|
||||
문제 없음
|
||||
합계 0건
|
||||
exit=0
|
||||
|
||||
$ python3 scripts/verify-tech-log-tree.py nonexistent-project
|
||||
[nonexistent-project] records=0
|
||||
! warn 1 분해 계약 없음
|
||||
· nonexistent-project: tech-log-tree.json 이 없다. 디렉터리가 정본 노릇을 하고 있다
|
||||
TECH LOG TREE: PASS — 프로젝트 1 · error 0 · warn 1
|
||||
exit=0
|
||||
|
||||
$ python3 scripts/check-figure-text.py nonexistent-project
|
||||
FIGURE TEXT: PASS — 그림 0장 · 문장 0건
|
||||
exit=0
|
||||
|
||||
$ python3 scripts/check-figure-overlap.py nonexistent-project
|
||||
볼 그림이 없다
|
||||
exit=0
|
||||
|
||||
$ python3 scripts/verify-project-layout.py nonexistent-project
|
||||
PROJECT LAYOUT: FAIL — 프로젝트 1 · error 1 · warn 0
|
||||
[nonexistent-project] —
|
||||
✗ error 1 final/document.md 가 없다
|
||||
· nonexistent-project
|
||||
exit=1
|
||||
|
||||
$ node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs nonexistent-project --repo
|
||||
nonexistent-project: tech-log-tree.json 이 없다
|
||||
exit=2
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
터미널 증거 렌더러의 마스킹이 실제로 덮는지 확인한 출력.
|
||||
쓰인 자격증명은 전부 합성(TESTONLY-*)이고 실제 비밀값이 아니다.
|
||||
@@ -0,0 +1,7 @@
|
||||
--- 렌더한 SVG 의 text 노드 ---
|
||||
$ curl -H "Authorization: Bearer [REDACTED]" https://example.test/api
|
||||
$ curl -H "Cookie: [REDACTED]" https://example.test/api
|
||||
$ psql postgresql://app:[REDACTED]@db:5432/app
|
||||
Authorization: Bearer [REDACTED]
|
||||
GET /api/me -> 200 in 14ms
|
||||
TESTONLY 남은 횟수: 0
|
||||
@@ -0,0 +1,9 @@
|
||||
원문 줄 -> 렌더 줄 (따옴표 수)
|
||||
2 -> 2 $ curl -H "X-Api-Key: [REDACTED]" https://example.invalid/d
|
||||
2 -> 2 $ curl -H "Authorization: Basic [REDACTED]" https://example.invalid/d
|
||||
2 -> 2 $ curl -H "Proxy-Authorization: Basic [REDACTED]" https://example.invalid/d
|
||||
0 -> 0 $ curl -u admin:[REDACTED] https://example.invalid/d
|
||||
2 -> 2 $ export TOKEN="[REDACTED]"
|
||||
0 -> 0 GET /api/me -> 200 in 14ms
|
||||
|
||||
렌더한 SVG 에 TESTONLY 가 남은 횟수: 0
|
||||
@@ -0,0 +1,14 @@
|
||||
audit-records.py exit=0
|
||||
build-tech-log-tree.py exit=0
|
||||
check-figure-overlap.py exit=0
|
||||
check-figure-text.py exit=0
|
||||
fold-analysis-into-final.py exit=0
|
||||
fold-studio-contract-into-index.py exit=0
|
||||
preview-figure.py exit=0
|
||||
studio-body.py exit=0
|
||||
techlog.py exit=0
|
||||
verify-pipeline-run.py exit=0
|
||||
verify-pipeline.py exit=0
|
||||
verify-project-layout.py exit=0
|
||||
verify-refactor-work-item.py exit=0
|
||||
verify-tech-log-tree.py exit=0
|
||||
@@ -0,0 +1,14 @@
|
||||
audit-records.py exit=0
|
||||
build-tech-log-tree.py exit=1
|
||||
check-figure-overlap.py exit=0
|
||||
check-figure-text.py exit=0
|
||||
fold-analysis-into-final.py exit=0
|
||||
fold-studio-contract-into-index.py exit=0
|
||||
preview-figure.py exit=0
|
||||
studio-body.py exit=0
|
||||
techlog.py exit=0
|
||||
verify-pipeline-run.py exit=0
|
||||
verify-pipeline.py exit=0
|
||||
verify-project-layout.py exit=0
|
||||
verify-refactor-work-item.py exit=1
|
||||
verify-tech-log-tree.py exit=0
|
||||
@@ -0,0 +1,17 @@
|
||||
$ python3 scripts/audit-records.py --help
|
||||
|
||||
--help — 기록 0건 · 원문 0 · 메타 0 · 렌더 0
|
||||
문제 없음
|
||||
|
||||
합계 0건
|
||||
exit=0
|
||||
|
||||
$ python3 scripts/build-tech-log-tree.py --help
|
||||
--help: tech-log-tree.json 이 없다. 글감을 먼저 적는다
|
||||
exit=1
|
||||
|
||||
$ python3 scripts/verify-refactor-work-item.py --help
|
||||
REFACTOR WORK ITEM VERIFICATION: FAIL
|
||||
- invalid work-item.json: --help/work-item.json
|
||||
exit=1
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
raw 에서 만든 터미널 SVG. 표현물이지 정본이 아니다.
|
||||
scripts/terminal-evidence/render_terminal.py 로 만든다.
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
---
|
||||
id:
|
||||
kind: CASE
|
||||
slug: exit-code-read-behind-a-pipe
|
||||
title: 파이프 뒤의 종료 코드를 읽고 검사기 열넷이 다 통과한다고 적었다
|
||||
topic: pipeline-gate-exit-codes
|
||||
topicName: 관문의 종료 코드
|
||||
project: document-haness
|
||||
status: 게시 전
|
||||
studio: ""
|
||||
lastVerifiedOn: 2026-09-10
|
||||
source:
|
||||
- final/document.md#§2-관찰한-것
|
||||
- final/document.md#§3-파이프-뒤의-종료-코드
|
||||
- final/document.md#§4-다시-잰-값
|
||||
- final/document.md#§5-argparse-를-쓰지-않는-넷
|
||||
- final/document.md#§8-종료-코드를-손으로-적을-수-없게-만든다
|
||||
sourceRevision: 43e1aadef077ad93c30495df428ee3a71dd73f4a
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/exit-code-through-a-pipe.txt
|
||||
- ../../../final/evidence/raw/help-exit-codes-measured-through-a-pipe.txt
|
||||
- ../../../final/evidence/raw/help-exit-codes-measured-without-a-pipe.txt
|
||||
- ../../../final/evidence/raw/argparse-absent-scripts.txt
|
||||
- ../../../final/evidence/raw/what-the-three-print-for-help.txt
|
||||
---
|
||||
|
||||
# 파이프 뒤의 종료 코드를 읽고 검사기 열넷이 다 통과한다고 적었다
|
||||
|
||||
scripts/ 의 검사기 열넷에 --help 를 돌려 전부 종료 코드 0 을 받았고, 이 저장소를 함께
|
||||
조사한 다른 세션도 같은 값을 얻어 확인 등급 확인함으로 적었다. 값은 검사기가 아니라 재는
|
||||
방법이 만든 것이었다. 파이프를 걷어 내고 다시 재니 둘이 exit 1 이다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **검사할 것이 없을 때 관문은 무엇을 내야 하는가**
|
||||
이 사건에서 `audit-records.py --help` 가 「문제 없음」을 찍으면서 그 물음이 열렸다.
|
||||
`--help` 라는 이름의 프로젝트에는 검사할 기록이 하나도 없어 그대로 통과했다.
|
||||
|
||||
## 문제
|
||||
|
||||
관문은 종료 코드로 말한다. 단계 계약이 「관문은 종료 코드가 0 이어야 지난 것이다」 라고
|
||||
적었고, 런 원장 run.json 의 stages[].gates[].exit 에 그 값이 남는데, 그 값을 사람이 셸에서
|
||||
읽어 옮겨 적는다.
|
||||
|
||||
--help 하나를 잰 것뿐인데 값이 두 번 틀렸다. 관문 결과 전부가 같은 방법으로 적히고 있다.
|
||||
|
||||
## 결론
|
||||
|
||||
리비전 43e1aad 의 scripts/*.py 열넷 중 열둘이 --help 에 exit 0,
|
||||
build-tech-log-tree.py 와 verify-refactor-work-item.py 가 exit 1 이다.
|
||||
exit 0 이 usage 가 나왔다는 뜻은 아니다 — audit-records.py 는 --help 를 프로젝트
|
||||
이름으로 받아 검사하고 통과시킨다.
|
||||
|
||||
값을 틀리게 만든 것은 셸의 동작 둘이다. 파이프의 종료 코드 변수는 마지막 명령을 가리키고,
|
||||
명령 치환은 그 변수를 덮어쓴다. 둘 다 정의된 동작이라 셸이 경고하지 않는다.
|
||||
|
||||
조치로 만든 scripts/capture-evidence.py 는 명령을 subprocess 로 직접 돌리고 그 프로세스의
|
||||
반환값을 그대로 메타에 적는다. 종료 코드를 인자로 받지 않으므로 손으로 적을 경로가 없다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
python 3.12.3 · node v24.14.0 · bash 5.2.21(1)-release · Linux.
|
||||
대상 저장소 document-haness 리비전 43e1aadef077ad93c30495df428ee3a71dd73f4a.
|
||||
측정은 그 커밋에서 갈라진 worktree dh-B 에서 했고, 그 시점 작업 트리에는
|
||||
capture-evidence.py 가 더해져 있어 증거 메타의 sourceDirty 가 true 다.
|
||||
측정 대상 열넷은 git ls-tree 로 그 커밋의 목록만 골라 냈다.
|
||||
|
||||
## 재현 조건
|
||||
|
||||
1. document-haness 를 43e1aad 로 체크아웃한다.
|
||||
2. 파이프를 끼고 잰다 — 반복문 안에서 python3 출력을 head 로 넘기고 그다음 줄에서 $? 를 읽는다.
|
||||
3. 열넷이 전부 exit=0 으로 나오는 것을 본다.
|
||||
4. 파이프를 걷고 out=$(...) 다음 줄에서 code=$? 로 받아 다시 잰다.
|
||||
5. build-tech-log-tree.py 와 verify-refactor-work-item.py 가 exit=1 로 갈리는 것을 본다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
## 두 번 같은 값이 나왔다
|
||||
|
||||
검사기 목록을 만들려고 열넷에 `--help` 를 돌렸는데, 출력이 길어서 `head` 로 잘랐다.
|
||||
|
||||
```bash
|
||||
for s in $(git ls-tree --name-only 43e1aad scripts/ | grep '\.py$'); do
|
||||
timeout 60 python3 $s --help 2>&1 | head -30 >/dev/null
|
||||
code=$?
|
||||
n=$(basename $s)
|
||||
echo "$n exit=$code"
|
||||
done
|
||||
```
|
||||
|
||||
열넷이 전부 `exit=0` 이었다.
|
||||
|
||||
```
|
||||
audit-records.py exit=0
|
||||
build-tech-log-tree.py exit=0
|
||||
check-figure-overlap.py exit=0
|
||||
check-figure-text.py exit=0
|
||||
fold-analysis-into-final.py exit=0
|
||||
fold-studio-contract-into-index.py exit=0
|
||||
preview-figure.py exit=0
|
||||
studio-body.py exit=0
|
||||
techlog.py exit=0
|
||||
verify-pipeline-run.py exit=0
|
||||
verify-pipeline.py exit=0
|
||||
verify-project-layout.py exit=0
|
||||
verify-refactor-work-item.py exit=0
|
||||
verify-tech-log-tree.py exit=0
|
||||
```
|
||||
|
||||
이 저장소를 함께 조사한 다른 세션도 같은 값을 얻어 「`scripts/*.py` 14개 전부 `--help` 가
|
||||
종료 코드 0 으로 돌아온다」 를 확인 등급 **확인함**으로 적었다. 두 사람이 같은 값을 얻었으니
|
||||
맞는 값처럼 보였다.
|
||||
|
||||
## 값을 만든 것은 셸이다
|
||||
|
||||
파이프라인의 `$?` 는 **마지막** 명령의 종료 코드다. `head` 는 언제나 성공하므로 앞의
|
||||
`python3` 가 무엇을 반환하든 `$?` 는 0 이 된다.
|
||||
|
||||
```bash
|
||||
set +o pipefail
|
||||
false | head -1; echo "false | head -1 -> exit=$?"
|
||||
false; echo "false -> exit=$?"
|
||||
```
|
||||
|
||||
```
|
||||
false | head -1 -> exit=0
|
||||
false -> exit=1
|
||||
bash 5.2.21(1)-release
|
||||
```
|
||||
|
||||
POSIX 셸의 정의된 동작이고 이 셸의 특이점이 아니다. `pipefail` 을 켜거나
|
||||
`${PIPESTATUS[0]}` 를 읽으면 앞 명령의 값을 얻는다.
|
||||
|
||||
같은 착각이 한 번 더 났다. 파이프를 걷어 내고 다시 잴 때 이렇게 썼다.
|
||||
|
||||
```bash
|
||||
out=$(timeout 60 python3 $s --help 2>&1)
|
||||
echo "$(basename $s) exit=$?"
|
||||
```
|
||||
|
||||
명령 치환 `$(basename $s)` 가 먼저 실행되면서 `$?` 를 덮어써서 두 번째 측정도 열넷 전부
|
||||
0 이었다. `code=$?` 를 명령 바로 다음 줄에 두고서야 값이 갈렸다.
|
||||
|
||||
## 다시 잰 값
|
||||
|
||||
```bash
|
||||
for s in $(git ls-tree --name-only 43e1aad scripts/ | grep '\.py$'); do
|
||||
out=$(timeout 60 python3 $s --help 2>&1)
|
||||
code=$?
|
||||
n=$(basename $s)
|
||||
echo "$n exit=$code"
|
||||
done
|
||||
```
|
||||
|
||||
두 측정의 차이는 두 줄뿐이다.
|
||||
|
||||
```
|
||||
2c2
|
||||
< build-tech-log-tree.py exit=0
|
||||
---
|
||||
> build-tech-log-tree.py exit=1
|
||||
13c13
|
||||
< verify-refactor-work-item.py exit=0
|
||||
---
|
||||
> verify-refactor-work-item.py exit=1
|
||||
```
|
||||
|
||||
잰 것은 `--help` 하나뿐이라 나머지 열둘이 다른 인자에서 어떻게 도는지는 이 값이 말해 주지
|
||||
않는다.
|
||||
|
||||
## 왜 그 둘만인가
|
||||
|
||||
열넷 중 넷이 `argparse` 를 쓰지 않는다.
|
||||
|
||||
| 스크립트 | `argparse` | `--help` |
|
||||
|---|---|---|
|
||||
| `audit-records.py` | 없음 | `exit 0` — 「문제 없음」 |
|
||||
| `build-tech-log-tree.py` | 없음 | `exit 1` |
|
||||
| `techlog.py` | 없음 | `exit 0` — CLI 가 아니라 인자를 안 읽는다 |
|
||||
| `verify-refactor-work-item.py` | 없음 | `exit 1` |
|
||||
| 나머지 열 | 있음 | `exit 0` — usage |
|
||||
|
||||
남은 셋은 `--help` 를 옵션이 아니라 위치 인자로 먹는다. `audit-records.py` 는 그것을
|
||||
프로젝트 이름으로 받아 「문제 없음」을 찍고, `build-tech-log-tree.py` 와
|
||||
`verify-refactor-work-item.py` 는 그 이름의 파일을 못 찾아 실패한다.
|
||||
|
||||
이 두 숫자를 한 번 섞었다. 넷을 셋으로 적었고, 그 셋이 분석 문서 네 곳과 증거 메타 둘에
|
||||
그대로 실렸다. 증거 원문에서 줄을 다시 센 뒤에 넷으로 고쳤다.
|
||||
|
||||
```
|
||||
$ python3 scripts/audit-records.py --help
|
||||
|
||||
--help — 기록 0건 · 원문 0 · 메타 0 · 렌더 0
|
||||
문제 없음
|
||||
|
||||
합계 0건
|
||||
exit=0
|
||||
|
||||
$ python3 scripts/build-tech-log-tree.py --help
|
||||
--help: tech-log-tree.json 이 없다. 글감을 먼저 적는다
|
||||
exit=1
|
||||
|
||||
$ python3 scripts/verify-refactor-work-item.py --help
|
||||
REFACTOR WORK ITEM VERIFICATION: FAIL
|
||||
- invalid work-item.json: --help/work-item.json
|
||||
exit=1
|
||||
```
|
||||
|
||||
`exit 0` 이 usage 가 나왔다는 뜻은 아니다. `audit-records.py` 는 `--help` 라는 이름의
|
||||
프로젝트를 찾아 검사하고 통과시켰다.
|
||||
|
||||
## 손으로 적지 못하게 했다
|
||||
|
||||
관문 결과를 사람이 옮겨 적는 한 같은 뿌리에서 같은 실수가 계속 난다. 그래서 명령을 돌리는
|
||||
쪽과 종료 코드를 적는 쪽을 하나로 붙였다.
|
||||
|
||||
```python
|
||||
proc = subprocess.run(command, cwd=cwd, capture_output=True,
|
||||
text=True, timeout=timeout)
|
||||
exit_code, out = proc.returncode, proc.stdout + proc.stderr
|
||||
```
|
||||
|
||||
종료 코드를 인자로 받지 않으므로 손으로 적어 넣을 수는 없다. 도구의 첫머리 주석이 목표를
|
||||
적는다 — 돌리지 않은 검증을 완료로 적는 경로가 없어야 한다. 원문은
|
||||
`final/evidence/raw/` 에, 실행 메타는 `final/evidence/meta/` 에 같은 이름으로 함께 떨어진다.
|
||||
둘을 사람이 따로 적으면 갈라지기 때문이다.
|
||||
|
||||
셸 한 줄을 감싸는 래퍼도 됐지만 그러면 파이프를 다시 쓸 수 있게 된다. 값을 두 번 틀리게
|
||||
만든 것이 바로 그 셸이라 아예 거치지 않기로 했다. 대신 셸 문법이 필요한 명령은 `bash -c` 를
|
||||
인자로 넘겨야 하고, 그 안에서 다시 파이프를 쓰면 같은 실수가 난다 — 이 기록의 증거
|
||||
다섯 개도 그렇게 수집했다.
|
||||
|
||||
처음 판에서는 이 도구도 인자를 잘못 먹었다. `argparse.REMAINDER` 로 명령을 받았더니
|
||||
`--proves` 부터가 실행할 명령으로 딸려 가 `No such file or directory: '--proves'` 로 죽었다.
|
||||
지금은 `--` 앞뒤를 직접 가르고, 왜 그렇게 했는지를 코드에 한 줄로 남겨 두었다.
|
||||
|
||||
```python
|
||||
# `--` 앞뒤를 먼저 가른다. argparse.REMAINDER 에 맡기면 옵션이 명령으로 딸려 간다
|
||||
argv = sys.argv[1:]
|
||||
```
|
||||
|
||||
`--help` 를 위치 인자로 먹은 세 스크립트와 같은 종류의 실수다. 이 저장소에서 인자를 손으로
|
||||
가르는 코드는 대개 여기서 걸린다.
|
||||
|
||||
이 기록이 인용한 원문 다섯 개를 그 도구가 수집했다. 메타 하나는 이렇게 생겼다.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "help-exit-codes-measured-without-a-pipe",
|
||||
"kind": "terminal",
|
||||
"sourceRevision": "43e1aadef077ad93c30495df428ee3a71dd73f4a",
|
||||
"sourceDirty": true,
|
||||
"executedAt": "2026-09-10T09:54:41+09:00",
|
||||
"exitCode": 0,
|
||||
"exitCodeSource": "실행한 프로세스의 반환값. 손으로 적지 않는다",
|
||||
"rawPath": "evidence/raw/help-exit-codes-measured-without-a-pipe.txt",
|
||||
"proves": "파이프 없이 재면 같은 14개 중 build-tech-log-tree.py 와 verify-refactor-work-item.py 가 exit 1 이다",
|
||||
"doesNotProve": "다른 인자에서의 동작. --help 하나만 잰 값이다",
|
||||
"sha256": "e8c134d07592bb1b051ea6ad3abf952a9fa502fc85eb26cdb56194447ea03a9b",
|
||||
"bytes": 410
|
||||
}
|
||||
```
|
||||
|
||||
여기서 `exitCode: 0` 은 **측정 반복문 자체가 끝까지 돌았다**고 말한다. 열넷 각각의 종료
|
||||
코드는 원문 `raw/` 안에 있다. 반복문이 반환한 값과 그 안에서 잰 값을 섞지 않는다 — 메타에는
|
||||
반복문 쪽이 들어간다.
|
||||
|
||||
`sourceDirty` 는 그 실행 시점에 작업 트리에 커밋 안 된 변경이 있었는지 적는다. 있으면
|
||||
`sourceRevision` 이 그 출력을 설명하지 못한다. 이 다섯은 전부 `true` 다 — 수집기 자신을
|
||||
더한 상태에서 쟀다.
|
||||
|
||||
기존 경로를 지우지 않았다. 손으로 만든 `raw/`·`meta/` 도 그대로 쓰이고, 이 도구는 필요할
|
||||
때만 부른다.
|
||||
|
||||
## 이 사건이 닫지 못한 것
|
||||
|
||||
수집기는 브라우저 캡처와 대화형 명령을 담지 못한다. 이 저장소의 증거 가운데
|
||||
`final/evidence/browser/` 쪽은 여전히 Playwright 로 찍고 메타를 손으로 적는다 — 종료 코드를
|
||||
손으로 적을 수 없게 만든 것이 아직 절반이라는 뜻이다.
|
||||
|
||||
<!-- body:end -->
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
---
|
||||
id:
|
||||
kind: QUESTION
|
||||
slug: what-a-gate-returns-with-nothing-to-check
|
||||
title: 검사할 것이 없을 때 관문은 무엇을 내야 하는가
|
||||
topic: pipeline-gate-exit-codes
|
||||
topicName: 관문의 종료 코드
|
||||
project: document-haness
|
||||
status: 게시 전
|
||||
studio: ""
|
||||
questionStatus: OPEN
|
||||
source:
|
||||
- final/document.md#§6-실재하지-않는-프로젝트-이름
|
||||
sourceRevision: 43e1aadef077ad93c30495df428ee3a71dd73f4a
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/gates-on-a-nonexistent-project.txt
|
||||
---
|
||||
|
||||
# 검사할 것이 없을 때 관문은 무엇을 내야 하는가
|
||||
|
||||
실재하지 않는 프로젝트 이름을 관문 여섯에 주면 넷이 통과를 보고한다. 검사할 것이 없으니
|
||||
찾은 문제도 없다는 뜻이라 틀린 값은 아니다. 다만 관문 결과를 읽는 쪽은 그것을 검사를
|
||||
지났다는 뜻으로 읽는다. 어느 쪽이 맞는지 이 저장소가 아직 정하지 않았다.
|
||||
|
||||
분해 계약에서는 이 후보를 Case 로 올리려다 Question 으로 돌렸다. 현상은 재현했는데 조치가
|
||||
없어서다 — 조치 없이 쓰면 관찰만 있고 결과가 없는 글이 된다. 검사기 수정은 이 저장소의
|
||||
다른 담당이 맡고 있어 여기서 고칠 수도 없었다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **파이프 뒤의 종료 코드를 읽고 검사기 열넷이 다 통과한다고 적었다**
|
||||
그 사건에서 audit-records.py 가 --help 를 프로젝트 이름으로 받아 「문제 없음」을 찍은
|
||||
것이 이 물음의 입구다. 같은 동작을 오타난 프로젝트 이름에서 다시 보게 된다.
|
||||
|
||||
## 사실
|
||||
|
||||
- 관문 여섯에 nonexistent-project 를 주면 넷이 exit 0 을 낸다. audit-records.py 는 「문제 없음」, verify-tech-log-tree.py 는 「PASS — 프로젝트 1 · error 0 · warn 1」, check-figure-text.py 는 「PASS — 그림 0장 · 문장 0건」, check-figure-overlap.py 는 「볼 그림이 없다」를 찍는다.
|
||||
- 잡아내는 둘은 대상 파일을 먼저 찾는다. verify-project-layout.py 는 final/document.md 가 없다며 exit 1, check_evidence.mjs 는 tech-log-tree.json 이 없다며 exit 2 다.
|
||||
- 검사기가 고장난 것이 아니다. 실재하는 프로젝트에서는 판정을 낸다 — check-figure-text.py ca-tmpl 은 그림 29장에서 문장 13건을 찾아 exit 1 이다.
|
||||
- 런 원장 검사기 verify-pipeline-run.py 는 관문의 대상 프로젝트를 보지 않는다. 명령 문자열에 토큰이 들어 있는지와 원장이 적어 낸 exit 값만 본다.
|
||||
|
||||
## 가정
|
||||
|
||||
- 관문 결과를 읽는 쪽이 exit 0 을 「그 프로젝트를 검사했고 문제가 없었다」로 읽는다고 보고 있다. 실제로 그렇게 읽는지는 사람에게 물어보지 않았다.
|
||||
|
||||
## 미지수
|
||||
|
||||
- 「대상 없음」을 통과로 볼지 실패로 볼지 정해지지 않았다. 아직 만들지 않은 프로젝트에 관문을 미리 걸어 보는 쓰임이 있다면 exit 0 이 맞는 값이다.
|
||||
- 넷 각각이 「대상 없음」과 「대상이 있는데 검사할 것이 0 건」을 구분할 수 있는지 모른다. 그 코드를 읽지 않았다.
|
||||
- 오타로 관문이 무효가 된 런이 실제로 있었는지 모른다. 지금 있는 원장 세 건은 프로젝트 이름이 전부 맞다.
|
||||
|
||||
## 제약
|
||||
|
||||
- 검사기 자체의 수정은 이 저장소의 다른 담당 몫이다. 여기서는 현상만 적는다.
|
||||
- 관문을 실패로 바꾸면 기존 원장과 전체 훑기가 함께 움직인다. 한쪽만 보고 정할 수 없다.
|
||||
|
||||
## 선택지
|
||||
|
||||
### 1. 넷이 「대상 없음」에서 실패를 내게 한다
|
||||
|
||||
대상 프로젝트 폴더가 없으면 exit 1 을 낸다. verify-project-layout.py 가 이미 그렇게 한다.
|
||||
오타 한 번에 관문이 무효가 되는 경로가 막힌다. 대신 아직 만들지 않은 프로젝트에 미리 돌려
|
||||
보는 쓰임이 사라지고, 기존 원장 세 건과 전체 훑기가 그대로 통과하는지 확인해야 한다.
|
||||
|
||||
### 2. 관문은 그대로 두고 원장 쪽에서 대상 이름을 대조한다
|
||||
|
||||
verify-pipeline-run.py 가 gates[].cmd 에서 프로젝트 이름을 뽑아 원장의 project 칸과
|
||||
견준다. 검사기 넷을 건드리지 않아 다른 쓰임이 안 깨진다. 대신 명령을 「<기록.md>」 같은 꼴로
|
||||
줄여 적은 기존 원장에서는 이름을 못 뽑는다 — 원장 세 건에 이미 그런 줄이 있다.
|
||||
|
||||
## 다음 검증
|
||||
|
||||
1. 넷에 실재하는 프로젝트를 주고 「검사 대상 0 건」이 나오는 경우와 「프로젝트 없음」이 나오는 경우의 출력을 나란히 잰다. 둘이 지금 구분되는지를 먼저 본다.
|
||||
2. 원장의 gates[].cmd 에서 프로젝트 이름을 뽑아 project 칸과 대조하는 검사를 넣어 보고, 기존 원장 세 건이 통과하는지 본다.
|
||||
|
||||
닫는 조건 : 넷이 「대상 없음」에서 exit 0 이 아닌 값을 내도 기존 원장 세 건과 전체 훑기가 그대로 통과하면 선택지 1 로 정하고 Decision 으로 넘긴다. 하나라도 깨지면 선택지 2 로 넘긴다.
|
||||
@@ -0,0 +1,230 @@
|
||||
{
|
||||
"schemaVersion": 4,
|
||||
"project": "document-haness",
|
||||
"ssot": "final/document.md",
|
||||
"ssotSha256": "6d2c41d2d02eecd1476b95c83caa91e262151236b38aedf5dda775114fed561f",
|
||||
"sourceRevision": "43e1aadef077ad93c30495df428ee3a71dd73f4a",
|
||||
"generatedAt": "2026-09-10",
|
||||
"sourceRepository": {
|
||||
"path": "/home/donghyeon/workspace/chat-gpt-container/document-haness",
|
||||
"revision": "43e1aadef077ad93c30495df428ee3a71dd73f4a",
|
||||
"verified": "worktree dh-B 에서 git rev-parse HEAD 로 확인했다. 측정 대상 열넷은 git ls-tree --name-only 43e1aad scripts/ 로 그 커밋의 목록만 골라 냈다. code[] 의 세 파일도 git cat-file -e 43e1aad:<경로> 로 그 커밋에 실재하는 것을 대조했고, 인용한 줄 155·148·193 이 셋 다 raise SystemExit(...) 인 것을 git show 로 확인했다. 이 런이 더한 scripts/capture-evidence.py 와 scripts/skill-versions.py 는 43e1aad 에 없으므로 code[] 에 넣지 않았다 — 기록 본문에서는 「이 배치에서 만든 것」으로 밝힌다. 증거 메타의 sourceDirty 가 전부 true 인 것이 그 상태를 적는다."
|
||||
},
|
||||
"candidateScope": {
|
||||
"document": "final/document.md",
|
||||
"sections": [
|
||||
"§2 관찰한 것",
|
||||
"§3 파이프 뒤의 종료 코드",
|
||||
"§4 다시 잰 값",
|
||||
"§5 argparse 를 쓰지 않는 넷",
|
||||
"§6 실재하지 않는 프로젝트 이름",
|
||||
"§7 원장은 종료 코드를 다시 재지 않는다",
|
||||
"§8 종료 코드를 손으로 적을 수 없게 만든다"
|
||||
],
|
||||
"excluded": [
|
||||
"§1 검사기 열넷이 있는 자리",
|
||||
"§9 확인하지 못한 것"
|
||||
],
|
||||
"note": "§1 은 배경이고 §9 는 한계 목록이라 후보 자리가 아니다. 후보는 §2~§8 에서만 나온다"
|
||||
},
|
||||
"note": "이 프로젝트의 글감 전부다. 분해 계약이자 색인이고, 이 파일이 정본이다. 노드의 칸(readiness·source·classification·relations…)은 사람이 적고, file·publication·status 는 기록 파일에서 읽어 채운다 — python3 scripts/build-tech-log-tree.py document-haness",
|
||||
"contract": {
|
||||
"decomposition": [
|
||||
"글감을 찾는 입력은 final/document.md 하나다. 거기에 없는 근거는 먼저 SSOT 에 넣는다.",
|
||||
"후보 전부는 candidates 에 처분과 함께 남고 PROMOTE 만 topics 로 올라간다.",
|
||||
"없애고 관련 Case 나 Concept 의 한 절로 넣어도 이해·결정·재사용성이 그대로라면 독립 기록으로 만들지 않는다.",
|
||||
"Topic 은 독자 질문 하나다. 그 물음에 답하지 않는 글감은 다른 Topic 으로 옮긴다.",
|
||||
"Concept 은 Case·Decision·Question 을 먼저 고른 뒤 그것을 이해하는 데 필요한 것만 거꾸로 더한다."
|
||||
],
|
||||
"readinessValues": [
|
||||
"READY",
|
||||
"OPEN",
|
||||
"NEEDS_EVIDENCE",
|
||||
"NEEDS_DECISION",
|
||||
"BLOCKED"
|
||||
],
|
||||
"dispositionValues": {
|
||||
"PROMOTE": "독립 Tech Log 로 쓴다",
|
||||
"MERGE_INTO": "다른 기록의 한 절로 흡수한다",
|
||||
"KEEP_IN_SSOT": "분석에는 남기고 독립 기록으로 만들지 않는다 — 정상적인 성공 결과다",
|
||||
"NEEDS_EVIDENCE": "주장에 아직 검증이 없다",
|
||||
"NEEDS_DECISION": "방향이 그럴듯하지만 프로젝트가 정하지 않았다",
|
||||
"BLOCKED": "원본이 불완전하거나 서로 어긋난다"
|
||||
}
|
||||
},
|
||||
"counts": {
|
||||
"topics": 1,
|
||||
"nodes": 2,
|
||||
"written": 2,
|
||||
"unwritten": 0,
|
||||
"unlisted": 0,
|
||||
"candidates": 5
|
||||
},
|
||||
"topics": {
|
||||
"pipeline-gate-exit-codes": {
|
||||
"topic": "pipeline-gate-exit-codes",
|
||||
"title": "관문의 종료 코드",
|
||||
"readerQuestion": "관문이 exit 0 을 냈다는 것은 무엇을 확인했다는 뜻인가?",
|
||||
"kinds": {
|
||||
"case": [
|
||||
{
|
||||
"title": "파이프 뒤의 종료 코드를 읽고 검사기 열넷이 다 통과한다고 적었다",
|
||||
"kind": "case",
|
||||
"slug": "exit-code-read-behind-a-pipe",
|
||||
"readiness": "READY",
|
||||
"source": [
|
||||
"final/document.md#§2-관찰한-것",
|
||||
"final/document.md#§3-파이프-뒤의-종료-코드",
|
||||
"final/document.md#§4-다시-잰-값",
|
||||
"final/document.md#§5-argparse-를-쓰지-않는-넷",
|
||||
"final/document.md#§8-종료-코드를-손으로-적을-수-없게-만든다"
|
||||
],
|
||||
"code": [
|
||||
"scripts/audit-records.py:155",
|
||||
"scripts/build-tech-log-tree.py:148",
|
||||
"scripts/verify-refactor-work-item.py:193"
|
||||
],
|
||||
"evidence": [
|
||||
"evidence/raw/exit-code-through-a-pipe.txt",
|
||||
"evidence/raw/help-exit-codes-measured-through-a-pipe.txt",
|
||||
"evidence/raw/help-exit-codes-measured-without-a-pipe.txt",
|
||||
"evidence/raw/argparse-absent-scripts.txt",
|
||||
"evidence/raw/what-the-three-print-for-help.txt"
|
||||
],
|
||||
"classification": "재현·진단·조치가 닫히는 하나의 사건이다. 두 세션이 같은 오측정을 하고, 원인이 셸의 정의된 동작으로 좁혀지고, 종료 코드를 손으로 적을 수 없게 만드는 수집기로 닫았다",
|
||||
"missing-verification": "--help 하나만 쟀다. 다른 인자에서 argparse 를 쓰는 열이 어떻게 도는지는 확인하지 않았다",
|
||||
"relations": [
|
||||
"question:what-a-gate-returns-with-nothing-to-check"
|
||||
],
|
||||
"publication": "초안",
|
||||
"file": "pipeline-gate-exit-codes/case/case-exit-code-read-behind-a-pipe.md",
|
||||
"status": "게시 전",
|
||||
"studioId": "",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": [
|
||||
"../../../final/evidence/raw/exit-code-through-a-pipe.txt",
|
||||
"../../../final/evidence/raw/help-exit-codes-measured-through-a-pipe.txt",
|
||||
"../../../final/evidence/raw/help-exit-codes-measured-without-a-pipe.txt",
|
||||
"../../../final/evidence/raw/argparse-absent-scripts.txt",
|
||||
"../../../final/evidence/raw/what-the-three-print-for-help.txt"
|
||||
]
|
||||
}
|
||||
],
|
||||
"concept": [],
|
||||
"reference": [],
|
||||
"question": [
|
||||
{
|
||||
"title": "검사할 것이 없을 때 관문은 무엇을 내야 하는가",
|
||||
"kind": "question",
|
||||
"slug": "what-a-gate-returns-with-nothing-to-check",
|
||||
"readiness": "OPEN",
|
||||
"source": [
|
||||
"final/document.md#§6-실재하지-않는-프로젝트-이름"
|
||||
],
|
||||
"evidence": [
|
||||
"evidence/raw/gates-on-a-nonexistent-project.txt"
|
||||
],
|
||||
"classification": "현상은 재현했고 조치는 정해지지 않았다. 답이 나오려면 이 저장소가 「대상 없음」을 통과로 볼지 실패로 볼지 정해야 한다",
|
||||
"missing-verification": "실재하는 프로젝트 전부에서 같은 넷이 어떻게 도는지는 안 쟀다. ca-tmpl 하나에서 check-figure-text.py 가 exit 1 을 내는 것만 봤다",
|
||||
"relations": [
|
||||
"case:exit-code-read-behind-a-pipe"
|
||||
],
|
||||
"known": [
|
||||
"관문 여섯에 실재하지 않는 프로젝트 이름 nonexistent-project 를 주면 넷이 exit 0 을 낸다 — audit-records.py 는 「문제 없음」, verify-tech-log-tree.py 는 「PASS — error 0 · warn 1」, check-figure-text.py 는 「PASS — 그림 0장」, check-figure-overlap.py 는 「볼 그림이 없다」",
|
||||
"잡아내는 둘은 대상 파일을 먼저 찾는다 — verify-project-layout.py 는 final/document.md 가 없다고 exit 1, check_evidence.mjs 는 tech-log-tree.json 이 없다고 exit 2",
|
||||
"이것은 「검사기가 고장났다」가 아니다. 실재하는 프로젝트에서는 판정을 낸다 — check-figure-text.py ca-tmpl 은 그림 29장에서 문장 13건을 찾아 exit 1 이다",
|
||||
"런 원장 검사기 verify-pipeline-run.py 는 관문의 대상 프로젝트를 보지 않고 명령 문자열에 토큰이 있는지와 원장이 적어 낸 exit 만 본다"
|
||||
],
|
||||
"unknown": [
|
||||
"「대상 없음」을 통과로 볼지 실패로 볼지 이 저장소가 정하지 않았다. 아직 안 만든 프로젝트에 관문을 미리 걸어 보는 쓰임이 있다면 exit 0 이 맞다",
|
||||
"넷 각각이 「대상 없음」과 「대상이 있는데 검사할 것이 0 건」을 구분할 수 있는지 — 코드를 읽지 않았다",
|
||||
"오타로 관문이 무효가 된 런이 실제로 있었는지. 원장 세 건은 전부 프로젝트 이름이 맞다"
|
||||
],
|
||||
"next-verification": [
|
||||
"넷에 실재하는 프로젝트를 주고 「검사 대상 0 건」이 나오는 경우와 「프로젝트 없음」이 나오는 경우의 출력을 나란히 잰다",
|
||||
"런 원장의 gates[].cmd 에서 프로젝트 이름을 뽑아 원장의 project 칸과 대조하는 검사를 verify-pipeline-run.py 에 넣어 보고, 기존 원장 세 건이 통과하는지 본다"
|
||||
],
|
||||
"decision-criterion": "넷이 「대상 없음」에서 exit 0 이 아닌 값을 내도 기존 원장 세 건과 verify-pipeline.py 전체 훑기가 그대로 통과하면 그 방향으로 정한다. 하나라도 깨지면 「대상 없음」을 통과로 두고 대신 원장 쪽에서 대상 이름을 대조한다",
|
||||
"publication": "초안",
|
||||
"file": "pipeline-gate-exit-codes/question/question-what-a-gate-returns-with-nothing-to-check.md",
|
||||
"status": "게시 전",
|
||||
"studioId": "",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": [
|
||||
"../../../final/evidence/raw/gates-on-a-nonexistent-project.txt"
|
||||
]
|
||||
}
|
||||
],
|
||||
"decision": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"candidates": [
|
||||
{
|
||||
"id": "DH-C01",
|
||||
"kindCandidate": "CASE",
|
||||
"sourceRefs": [
|
||||
"final/document.md#§2-관찰한-것",
|
||||
"final/document.md#§3-파이프-뒤의-종료-코드",
|
||||
"final/document.md#§4-다시-잰-값"
|
||||
],
|
||||
"summary": "파이프를 끼고 재서 검사기 열넷이 전부 --help 를 받는 것으로 보였고, 다시 재니 둘이 exit 1 이었다",
|
||||
"disposition": "PROMOTE",
|
||||
"dispositionReview": "CONFIRMED",
|
||||
"target": "case:exit-code-read-behind-a-pipe",
|
||||
"reason": "관찰·원인·조치가 한 사건으로 닫히고 두 세션이 같은 값을 얻어 계약 문서에까지 들어간 자리다. 독립 기록으로 남길 값이 있다"
|
||||
},
|
||||
{
|
||||
"id": "DH-C02",
|
||||
"kindCandidate": "CASE",
|
||||
"sourceRefs": [
|
||||
"final/document.md#§5-argparse-를-쓰지-않는-넷"
|
||||
],
|
||||
"summary": "열넷 중 넷이 argparse 를 쓰지 않고, 그중 셋이 --help 를 위치 인자로 먹는다",
|
||||
"disposition": "MERGE_INTO",
|
||||
"dispositionReview": "CONFIRMED",
|
||||
"target": "case:exit-code-read-behind-a-pipe",
|
||||
"reason": "왜 그 둘만 exit 1 이었는지를 설명하는 절이다. 떼어 내면 CASE 의 원인 절이 비고, 따로 읽을 값도 없다"
|
||||
},
|
||||
{
|
||||
"id": "DH-C03",
|
||||
"kindCandidate": "CASE",
|
||||
"sourceRefs": [
|
||||
"final/document.md#§8-종료-코드를-손으로-적을-수-없게-만든다"
|
||||
],
|
||||
"summary": "명령 실행과 종료 코드 기록을 한 자리로 붙인 증거 수집기를 만들었다",
|
||||
"disposition": "MERGE_INTO",
|
||||
"dispositionReview": "CONFIRMED",
|
||||
"target": "case:exit-code-read-behind-a-pipe",
|
||||
"reason": "CASE 의 조치 절이다. 도구 자체의 사용법은 Reference 감이지만 아직 이 저장소 하나에서만 써서 「주의할 조건」을 적을 근거가 없다 — 실행 여덟 건이 전부 이 프로젝트의 증거 수집이고, 다른 프로젝트나 브라우저 캡처에 걸어 본 적이 없다"
|
||||
},
|
||||
{
|
||||
"id": "DH-C04",
|
||||
"kindCandidate": "CASE",
|
||||
"sourceRefs": [
|
||||
"final/document.md#§6-실재하지-않는-프로젝트-이름"
|
||||
],
|
||||
"summary": "실재하지 않는 프로젝트 이름에 관문 여섯 중 넷이 exit 0 을 낸다",
|
||||
"disposition": "PROMOTE",
|
||||
"dispositionReview": "CONFIRMED",
|
||||
"target": "question:what-a-gate-returns-with-nothing-to-check",
|
||||
"reason": "현상은 재현했지만 조치가 없다. 조치 없는 CASE 는 「관찰만 있고 결과가 없는 글」이 되므로 Question 으로 올린다 — 확인된 사실과 답을 구할 방법은 있고 답만 없다. 검사기 수정은 이 저장소의 다른 담당 몫이다"
|
||||
},
|
||||
{
|
||||
"id": "DH-C05",
|
||||
"kindCandidate": "QUESTION",
|
||||
"sourceRefs": [
|
||||
"final/document.md#§7-원장은-종료-코드를-다시-재지-않는다"
|
||||
],
|
||||
"summary": "런 원장이 관문을 재실행하지 않고 자기 신고한 exit 만 본다. 재실행이 옳은 답인지는 안 정했다",
|
||||
"disposition": "NEEDS_DECISION",
|
||||
"dispositionReview": "CONFIRMED",
|
||||
"target": null,
|
||||
"reason": "재실행하면 원장 검사가 느려지고 부작용 있는 관문(파일을 고치는 build-tech-log-tree.py)도 다시 돌게 된다. 무엇을 재실행하고 무엇을 신고로 받을지 이 저장소가 정하지 않았다. 정해지기 전에는 Question 으로도 못 쓴다 — 「답을 구할 방법」이 결정에 달려 있다"
|
||||
}
|
||||
],
|
||||
"unlisted": [],
|
||||
"history": {}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"runId": "2026-09-10-1033",
|
||||
"project": "document-haness",
|
||||
"record": "docs/document-haness/tech-log-studio/pipeline-gate-exit-codes/case/case-exit-code-read-behind-a-pipe.md",
|
||||
"startedAt": "2026-09-10T10:33:45+09:00",
|
||||
"finishedAt": "2026-09-10T11:00:39+09:00",
|
||||
"stages": [
|
||||
{
|
||||
"id": "S1",
|
||||
"name": "코드베이스 → SSOT",
|
||||
"skill": "analyzing-codebase-for-tech-log",
|
||||
"runBy": "subagent",
|
||||
"status": "DONE",
|
||||
"skipReason": "",
|
||||
"skillEcho": "Do not turn inference into observation in the final document.",
|
||||
"inputs": [
|
||||
"docs/document-haness/final/document.md",
|
||||
"docs/document-haness/final/evidence/raw/**",
|
||||
"docs/document-haness/final/evidence/meta/**"
|
||||
],
|
||||
"outputs": [
|
||||
"docs/document-haness/final/document.md",
|
||||
"docs/document-haness/final/evidence/meta/argparse-absent-scripts.json",
|
||||
"docs/document-haness/final/evidence/meta/what-the-three-print-for-help.json",
|
||||
"docs/document-haness/tech-log-studio/tech-log-tree.json"
|
||||
],
|
||||
"gates": [
|
||||
{
|
||||
"cmd": "python3 scripts/verify-project-layout.py document-haness",
|
||||
"exit": 0
|
||||
},
|
||||
{
|
||||
"cmd": "python3 scripts/build-tech-log-tree.py document-haness",
|
||||
"exit": 0
|
||||
},
|
||||
{
|
||||
"cmd": "python3 scripts/verify-tech-log-tree.py document-haness",
|
||||
"exit": 0
|
||||
},
|
||||
{
|
||||
"cmd": "node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs document-haness --repo",
|
||||
"exit": 0
|
||||
}
|
||||
],
|
||||
"notes": "대조 모드다. 분석을 새로 하지 않고 SSOT 가 증거 원문과 어긋나는지만 봤다. S3 이 올린 어긋남 하나가 실물이었다 — 증거 정본 argparse-absent-scripts.txt 의 「ARGPARSE 없음」이 4줄인데 SSOT 는 셋이라고 적었다. 「argparse 가 없는 것(넷)」과 「--help 를 위치 인자로 먹는 것(셋)」을 한 숫자로 섞은 것이다. SSOT 네 곳(:155 절 제목 · :157 · :176 · §9)을 고치고, 절 제목이 바뀌어 앵커를 가리키는 세 곳(계약 2 · 기록 frontmatter 1)을 함께 고쳤다. 증거 메타 둘의 proves·doesNotProve 도 같은 오산을 담고 있어 고쳤다 — 그것은 사람이 적은 주장이다. exitCode·sha256·bytes·command 는 실행이 적은 값이라 안 건드렸고, raw 원문은 한 글자도 안 건드렸다(메타 8건의 sha256 을 다시 계산해 원문과 일치 확인). runs/.../stage/S3-before.md 는 과거 사본이라 옛 앵커를 그대로 두었다. 그 밖에 §1~§9 의 인용 블록 다섯을 raw 와 diff 로 대조했고 §7 의 원장 인용이 runs/virtualization/2026-09-09-1052/run.json:78 에 실재하는 것까지 확인했다. 오케스트레이터가 증거 원문과 코드로 다시 세어 넷인 것을 독립으로 확인했다. 판단이 필요해 안 고친 것 둘 — guards/ 증거 둘을 인용하는 기록이 없고(warn 2건), sourceRepository.path 가 worktree 가 아니라 원본 경로를 가리킨다(verified 칸이 사유를 적는다)."
|
||||
},
|
||||
{
|
||||
"id": "S2",
|
||||
"name": "SSOT → 분해 계약",
|
||||
"skill": "deriving-tech-log-root-tree",
|
||||
"runBy": "orchestrator",
|
||||
"status": "SKIPPED",
|
||||
"skipReason": "이 글감이 tech-log-tree.json 에 이미 PROMOTE · dispositionReview CONFIRMED 로 있다 (candidates[DH-C01], target=case:exit-code-read-behind-a-pipe). 분해를 다시 하지 않았다.",
|
||||
"skillEcho": "",
|
||||
"inputs": [
|
||||
"docs/document-haness/final/document.md",
|
||||
"docs/document-haness/tech-log-studio/tech-log-tree.json"
|
||||
],
|
||||
"outputs": [
|
||||
"docs/document-haness/tech-log-studio/tech-log-tree.json"
|
||||
],
|
||||
"gates": [
|
||||
{
|
||||
"cmd": "python3 scripts/build-tech-log-tree.py document-haness",
|
||||
"exit": 0
|
||||
},
|
||||
{
|
||||
"cmd": "python3 scripts/verify-tech-log-tree.py document-haness",
|
||||
"exit": 0
|
||||
}
|
||||
],
|
||||
"notes": "build 는 파생 칸(file·publication·status·ssotSha256)만 다시 채운다. 사람이 적은 칸은 그대로다."
|
||||
},
|
||||
{
|
||||
"id": "S3",
|
||||
"name": "글감 → 기록",
|
||||
"skill": "writing-tech-log-records",
|
||||
"runBy": "subagent",
|
||||
"status": "DONE",
|
||||
"skipReason": "",
|
||||
"skillEcho": "**옮겨 적었다고 확인한 것이 아니다.** 앞선 기록에서 코드를 가져오거나 기억으로 경로를 쓰면",
|
||||
"inputs": [
|
||||
"docs/document-haness/tech-log-studio/tech-log-tree.json",
|
||||
"docs/document-haness/final/document.md",
|
||||
"runs/document-haness/2026-09-10-1033/stage/S3-before.md"
|
||||
],
|
||||
"outputs": [
|
||||
"docs/document-haness/tech-log-studio/pipeline-gate-exit-codes/case/case-exit-code-read-behind-a-pipe.md"
|
||||
],
|
||||
"gates": [
|
||||
{
|
||||
"cmd": "python3 scripts/studio-body.py docs/document-haness/tech-log-studio/pipeline-gate-exit-codes/case/case-exit-code-read-behind-a-pipe.md -o /tmp/s3-body.md",
|
||||
"exit": 0
|
||||
},
|
||||
{
|
||||
"cmd": "node --experimental-transform-types .agents/skills/writing-tech-log-records/scripts/check_body.mjs /tmp/s3-body.md",
|
||||
"exit": 0
|
||||
},
|
||||
{
|
||||
"cmd": "node .agents/skills/rewriting-technical-prose-naturally/scripts/check_prose.mjs --warn docs/document-haness/tech-log-studio/pipeline-gate-exit-codes/case/case-exit-code-read-behind-a-pipe.md",
|
||||
"exit": 0
|
||||
},
|
||||
{
|
||||
"cmd": "node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs document-haness --repo",
|
||||
"exit": 0
|
||||
},
|
||||
{
|
||||
"cmd": "python3 scripts/verify-tech-log-tree.py document-haness",
|
||||
"exit": 0
|
||||
}
|
||||
],
|
||||
"notes": "기록은 이미 있었고 서브에이전트가 계약·SSOT 와 대조해 세 곳을 고쳤다. (1) 요약 칸의 백틱 — 평문으로 렌더링되는 칸이다. (2) 요약이 「계약 문서에까지 들어갔다」고 적었는데 SSOT 는 「확인 등급 확인함으로 적었다」까지만 말한다. SSOT 가 뒷받침하는 문장으로 바꿨다. (3) 본문이 「열넷 중 셋이 argparse 를 쓰지 않는다」로 시작하면서 바로 아래 표에 「없음」을 넷 적어 두었다. 증거 정본 argparse-absent-scripts.txt 의 「ARGPARSE 없음」이 4줄이므로 표가 맞고 문장이 틀렸다 — 셋→넷, 남은 둘→남은 셋으로 고쳤다. 오케스트레이터가 증거 원문과 코드로 다시 세어 넷인 것을 확인했다. 같은 오산이 SSOT·계약·증거 메타에 그대로 있다는 것을 서브에이전트가 크게 적어 올렸고, SSOT 를 고치는 것은 자기 단계 밖이라 손대지 않았다 — S1 로 돌렸다. check_prose 경고 2건(CASE·POSIX 약어)은 남겼다. CASE 는 frontmatter 의 kind 값이고 POSIX 는 SSOT 가 쓰는 표준 명칭이라 풀어 쓰면 보호 구간을 건드린다."
|
||||
},
|
||||
{
|
||||
"id": "S4",
|
||||
"name": "기록 → 그림",
|
||||
"skill": "technical-visualizer",
|
||||
"runBy": "orchestrator",
|
||||
"status": "SKIPPED",
|
||||
"skipReason": "그림이 필요 없다. 세 관문 중 셋째에 걸린다 — 본문의 「왜 그 둘만인가」 절이 argparse 유무와 --help 결과를 표 하나로 답하고 있어, 같은 것을 그림으로 다시 그리면 옆 문단이 이미 말한 것을 되풀이한다. 계약의 이 노드에도 assets 가 없다.",
|
||||
"skillEcho": "",
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"gates": [],
|
||||
"notes": "이 프로젝트의 final/assets 는 비어 있다. check-figure-text.py 와 check-figure-overlap.py 는 볼 그림이 없어 돌리지 않았다."
|
||||
},
|
||||
{
|
||||
"id": "S5",
|
||||
"name": "AI 티 제거",
|
||||
"skill": "rewriting-technical-prose-naturally",
|
||||
"runBy": "subagent",
|
||||
"status": "DONE",
|
||||
"skipReason": "",
|
||||
"skillEcho": "| Sentences are all short and choppy | Rejoin with `~기 때문에`, `~다 보니`, `~어서`; break only where the subject changes |",
|
||||
"inputs": [
|
||||
"docs/document-haness/tech-log-studio/pipeline-gate-exit-codes/case/case-exit-code-read-behind-a-pipe.md",
|
||||
"runs/document-haness/2026-09-10-1033/stage/S5-before.md"
|
||||
],
|
||||
"outputs": [
|
||||
"docs/document-haness/tech-log-studio/pipeline-gate-exit-codes/case/case-exit-code-read-behind-a-pipe.md"
|
||||
],
|
||||
"gates": [
|
||||
{
|
||||
"cmd": "node .agents/skills/rewriting-technical-prose-naturally/scripts/check_prose.mjs --warn docs/document-haness/tech-log-studio/pipeline-gate-exit-codes/case/case-exit-code-read-behind-a-pipe.md",
|
||||
"exit": 0
|
||||
},
|
||||
{
|
||||
"cmd": "node .agents/skills/rewriting-technical-prose-naturally/scripts/style_profile.mjs docs/document-haness/tech-log-studio/pipeline-gate-exit-codes/case/case-exit-code-read-behind-a-pipe.md",
|
||||
"exit": 1
|
||||
},
|
||||
{
|
||||
"cmd": "python3 scripts/studio-body.py docs/document-haness/tech-log-studio/pipeline-gate-exit-codes/case/case-exit-code-read-behind-a-pipe.md -o /tmp/s5-body.md",
|
||||
"exit": 0
|
||||
},
|
||||
{
|
||||
"cmd": "node --experimental-transform-types .agents/skills/writing-tech-log-records/scripts/check_body.mjs /tmp/s5-body.md",
|
||||
"exit": 0
|
||||
},
|
||||
{
|
||||
"cmd": "node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs document-haness --repo",
|
||||
"exit": 0
|
||||
},
|
||||
{
|
||||
"cmd": "python3 scripts/check-preservation.py runs/document-haness/2026-09-10-1033/stage/S5-before.md docs/document-haness/tech-log-studio/pipeline-gate-exit-codes/case/case-exit-code-read-behind-a-pipe.md",
|
||||
"exit": 0
|
||||
}
|
||||
],
|
||||
"notes": "서브에이전트가 아홉 곳을 고쳤다. 전부 문장을 잇거나 문단 순서를 옮긴 것이고 삭제한 문장도 새로 쓴 문장도 없다(68문장 → 63문장). check-preservation.py 가 「보호 구간 변화 0건 · 유보 감소 0종」으로 확인했다 — 윤문이 수치·코드·인용·URL 을 건드리지 않았고 유보 표현도 안 지웠다는 뜻이다. style_profile.mjs 는 exit 1 로 그대로 적는다. 관문이 아니라 측정이고 문서 계약이 이것에만 「error 0」을 안 붙였다. 이유 연결어미는 5.9 → 9.5 로 기준 안에 들어왔고 문장 평균 길이는 41.6 → 45 로 기준(48~75) 밖에 남았다. 서브에이전트가 남은 짧은 문장 22개를 전부 열어 보고 재현 조건 단계·수치 한 줄·코드로 넘기는 도입·정의 한 줄·방향 전환이라 잇지 않았다고 적었다. 수치를 맞추려고 문장을 넣지 말라는 것이 스킬의 규칙이다. 서브에이전트가 사실 어긋남 하나를 올렸다 — 본문이 「이 기록의 증거 여섯 개」라고 적었는데 frontmatter 의 evidence 는 다섯이다. 수치는 보호 구간이라 S5 가 못 고친다고 판단해 넘겼고, 오케스트레이터가 frontmatter 를 세어 확인한 뒤 다섯으로 고쳤다. check-preservation 은 이 고침을 못 잡는다 — 한글 수사는 아라비아 숫자가 아니라서 보호 구간 비교에 안 걸린다. 이 검사기의 알려진 한계다."
|
||||
},
|
||||
{
|
||||
"id": "S6",
|
||||
"name": "일한 사람의 목소리",
|
||||
"skill": "writing-as-the-person-who-did-it",
|
||||
"runBy": "subagent",
|
||||
"status": "DONE",
|
||||
"skipReason": "",
|
||||
"skillEcho": "고치는 방법은 하나뿐이다. **자료에 남아 있는 사람의 흔적을 찾아서 제자리에 놓는다.**",
|
||||
"inputs": [
|
||||
"docs/document-haness/tech-log-studio/pipeline-gate-exit-codes/case/case-exit-code-read-behind-a-pipe.md",
|
||||
"docs/document-haness/final/document.md",
|
||||
"docs/document-haness/tech-log-studio/tech-log-tree.json",
|
||||
"docs/document-haness/final/evidence/meta/*.json",
|
||||
"scripts/capture-evidence.py",
|
||||
"runs/document-haness/2026-09-10-1033/run.json",
|
||||
"runs/document-haness/2026-09-10-1033/stage/S6-before.md"
|
||||
],
|
||||
"outputs": [
|
||||
"docs/document-haness/tech-log-studio/pipeline-gate-exit-codes/case/case-exit-code-read-behind-a-pipe.md"
|
||||
],
|
||||
"gates": [
|
||||
{
|
||||
"cmd": "node .agents/skills/writing-as-the-person-who-did-it/scripts/check_voice.mjs docs/document-haness/tech-log-studio/pipeline-gate-exit-codes/case/case-exit-code-read-behind-a-pipe.md",
|
||||
"exit": 0
|
||||
},
|
||||
{
|
||||
"cmd": "node .agents/skills/rewriting-technical-prose-naturally/scripts/check_prose.mjs --warn docs/document-haness/tech-log-studio/pipeline-gate-exit-codes/case/case-exit-code-read-behind-a-pipe.md",
|
||||
"exit": 0
|
||||
},
|
||||
{
|
||||
"cmd": "python3 scripts/studio-body.py docs/document-haness/tech-log-studio/pipeline-gate-exit-codes/case/case-exit-code-read-behind-a-pipe.md -o /tmp/s6-body.md",
|
||||
"exit": 0
|
||||
},
|
||||
{
|
||||
"cmd": "node --experimental-transform-types .agents/skills/writing-tech-log-records/scripts/check_body.mjs /tmp/s6-body.md",
|
||||
"exit": 0
|
||||
},
|
||||
{
|
||||
"cmd": "node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs document-haness --repo",
|
||||
"exit": 0
|
||||
},
|
||||
{
|
||||
"cmd": "python3 scripts/check-preservation.py runs/document-haness/2026-09-10-1033/stage/S6-before.md docs/document-haness/tech-log-studio/pipeline-gate-exit-codes/case/case-exit-code-read-behind-a-pipe.md",
|
||||
"exit": 0
|
||||
}
|
||||
],
|
||||
"notes": "흔적 있음. 두 곳을 넣었고 둘 다 근거를 파일과 칸으로 댔다. (A) 「왜 그 둘만인가」 절에 이 런이 겪은 어긋남을 그 대목에 놓았다 — 두 숫자를 섞어 넷을 셋으로 적었고 그것이 분석 문서 네 곳과 증거 메타 둘에 실렸으며 증거 원문에서 줄을 다시 세어 고쳤다는 것. 근거는 이 원장의 S1·S3 notes 다. (B) 「손으로 적지 못하게 했다」 절에 도구를 쓴 사람의 말을 옮겼다 — scripts/capture-evidence.py 의 docstring :5 와 :9 다. 기록이 기능만 적고 목표와 이유는 안 적던 자리다. 인용부호나 백틱으로 감싸지 않고 평문으로 녹였다 — check-preservation 이 새로 생긴 인라인코드와 「」 직접인용을 「새로생김」으로 세기 때문이다. 낱말은 docstring 그대로다. 안 넣은 것도 적었다. 「처음에는」·「고민 끝에」·「놀랍게도」는 자료에 없어 한 건도 안 썼고, 커밋 메시지는 capture-evidence.py 가 아직 untracked 라 흔적이 없다 — 찾아봤고 없었다. 이미 있던 사람의 흔적 셋(같은 착각이 한 번 더 났다 · 셸 래퍼도 됐지만 · 처음 판에서는 이 도구도 인자를 잘못 먹었다)에는 손대지 않았다. 서브에이전트가 계약의 어긋남 하나를 올렸다 — candidates[DH-C03].reason 이 「한 번밖에 안 써서」라고 적는데 이 도구가 적은 메타가 여덟 건이다. 자료와 어긋나 그 문장을 근거로 못 쓴다고 판단하고 안 넣었다. 오케스트레이터가 메타를 세어 확인한 뒤 계약을 「이 저장소 하나에서만 써서 … 실행 여덟 건이 전부 이 프로젝트의 증거 수집이고 다른 프로젝트나 브라우저 캡처에 걸어 본 적이 없다」로 고쳤다."
|
||||
},
|
||||
{
|
||||
"id": "S7",
|
||||
"name": "Studio 저장",
|
||||
"skill": "publishing-tech-log-to-studio",
|
||||
"runBy": "orchestrator",
|
||||
"status": "SKIPPED",
|
||||
"skipReason": "Studio 반입을 요청받지 않았다. 이 배치에서 Studio 브라우저 세션은 통합 세션 A 가 소유하고, 게시 권한이 저장 권한과 분리돼 있지 않아 무인 저장이 막혀 있다 (A-studio-change-requests.md 의 CR-001). 화면을 열지 않았다.",
|
||||
"skillEcho": "",
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"gates": [],
|
||||
"notes": "기록 frontmatter 의 id 와 studio 는 비어 있다. 저장한 적이 없다는 뜻이고 그대로 둔다."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
---
|
||||
id:
|
||||
kind: CASE
|
||||
slug: exit-code-read-behind-a-pipe
|
||||
title: 파이프 뒤의 종료 코드를 읽고 검사기 열넷이 다 통과한다고 적었다
|
||||
topic: pipeline-gate-exit-codes
|
||||
topicName: 관문의 종료 코드
|
||||
project: document-haness
|
||||
status: 게시 전
|
||||
studio: ""
|
||||
lastVerifiedOn: 2026-09-10
|
||||
source:
|
||||
- final/document.md#§2-관찰한-것
|
||||
- final/document.md#§3-파이프-뒤의-종료-코드
|
||||
- final/document.md#§4-다시-잰-값
|
||||
- final/document.md#§5-argparse-를-쓰지-않는-셋
|
||||
- final/document.md#§8-종료-코드를-손으로-적을-수-없게-만든다
|
||||
sourceRevision: 43e1aadef077ad93c30495df428ee3a71dd73f4a
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/exit-code-through-a-pipe.txt
|
||||
- ../../../final/evidence/raw/help-exit-codes-measured-through-a-pipe.txt
|
||||
- ../../../final/evidence/raw/help-exit-codes-measured-without-a-pipe.txt
|
||||
- ../../../final/evidence/raw/argparse-absent-scripts.txt
|
||||
- ../../../final/evidence/raw/what-the-three-print-for-help.txt
|
||||
---
|
||||
|
||||
# 파이프 뒤의 종료 코드를 읽고 검사기 열넷이 다 통과한다고 적었다
|
||||
|
||||
`scripts/` 의 검사기 열넷에 `--help` 를 돌려 전부 종료 코드 0 을 받았다. 두 세션이 각자
|
||||
같은 값을 얻어 계약 문서에까지 「14개 전부 `--help` 가 종료 코드 0」으로 들어갔다. 값은
|
||||
검사기가 아니라 재는 방법이 만든 것이었다. 파이프를 걷어 내고 다시 재니 둘이 `exit 1` 이다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **검사할 것이 없을 때 관문은 무엇을 내야 하는가**
|
||||
이 사건에서 `audit-records.py --help` 가 「문제 없음」을 찍으면서 그 물음이 열렸다.
|
||||
`--help` 라는 이름의 프로젝트에는 검사할 기록이 하나도 없어 그대로 통과했다.
|
||||
|
||||
## 문제
|
||||
|
||||
관문은 종료 코드로 말한다. 단계 계약이 「관문은 종료 코드가 0 이어야 지난 것이다」 라고
|
||||
적었고, 런 원장 run.json 의 stages[].gates[].exit 에 그 값이 남는다. 사람이 셸에서 그 값을
|
||||
읽어 옮겨 적는다.
|
||||
|
||||
--help 하나를 잰 것뿐인데 값이 두 번 틀렸다. 관문 결과 전부가 같은 방법으로 적히고 있다.
|
||||
|
||||
## 결론
|
||||
|
||||
리비전 43e1aad 의 scripts/*.py 열넷 중 열둘이 --help 에 exit 0,
|
||||
build-tech-log-tree.py 와 verify-refactor-work-item.py 가 exit 1 이다.
|
||||
exit 0 이 usage 가 나왔다는 뜻은 아니다 — audit-records.py 는 --help 를 프로젝트
|
||||
이름으로 받아 검사하고 통과시킨다.
|
||||
|
||||
값을 틀리게 만든 것은 셸의 동작 둘이다. 파이프의 종료 코드 변수는 마지막 명령을 가리키고,
|
||||
명령 치환은 그 변수를 덮어쓴다. 둘 다 정의된 동작이라 셸이 경고하지 않는다.
|
||||
|
||||
조치로 scripts/capture-evidence.py 를 만들었다. 명령을 subprocess 로 직접 돌리고 그
|
||||
프로세스의 반환값을 그대로 메타에 적는다. 종료 코드를 인자로 받지 않으므로 손으로 적을
|
||||
경로가 없다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
python 3.12.3 · node v24.14.0 · bash 5.2.21(1)-release · Linux.
|
||||
대상 저장소 document-haness 리비전 43e1aadef077ad93c30495df428ee3a71dd73f4a.
|
||||
측정은 그 커밋에서 갈라진 worktree dh-B 에서 했고, 그 시점 작업 트리에는
|
||||
capture-evidence.py 가 더해져 있어 증거 메타의 sourceDirty 가 true 다.
|
||||
측정 대상 열넷은 git ls-tree 로 그 커밋의 목록만 골라 냈다.
|
||||
|
||||
## 재현 조건
|
||||
|
||||
1. document-haness 를 43e1aad 로 체크아웃한다.
|
||||
2. 파이프를 끼고 잰다 — 반복문 안에서 python3 출력을 head 로 넘기고 그다음 줄에서 $? 를 읽는다.
|
||||
3. 열넷이 전부 exit=0 으로 나오는 것을 본다.
|
||||
4. 파이프를 걷고 out=$(...) 다음 줄에서 code=$? 로 받아 다시 잰다.
|
||||
5. build-tech-log-tree.py 와 verify-refactor-work-item.py 가 exit=1 로 갈리는 것을 본다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
## 두 번 같은 값이 나왔다
|
||||
|
||||
검사기 목록을 만들려고 열넷에 `--help` 를 돌렸다. 출력이 길어서 `head` 로 잘랐다.
|
||||
|
||||
```bash
|
||||
for s in $(git ls-tree --name-only 43e1aad scripts/ | grep '\.py$'); do
|
||||
timeout 60 python3 $s --help 2>&1 | head -30 >/dev/null
|
||||
code=$?
|
||||
n=$(basename $s)
|
||||
echo "$n exit=$code"
|
||||
done
|
||||
```
|
||||
|
||||
열넷이 전부 `exit=0` 이었다.
|
||||
|
||||
```
|
||||
audit-records.py exit=0
|
||||
build-tech-log-tree.py exit=0
|
||||
check-figure-overlap.py exit=0
|
||||
check-figure-text.py exit=0
|
||||
fold-analysis-into-final.py exit=0
|
||||
fold-studio-contract-into-index.py exit=0
|
||||
preview-figure.py exit=0
|
||||
studio-body.py exit=0
|
||||
techlog.py exit=0
|
||||
verify-pipeline-run.py exit=0
|
||||
verify-pipeline.py exit=0
|
||||
verify-project-layout.py exit=0
|
||||
verify-refactor-work-item.py exit=0
|
||||
verify-tech-log-tree.py exit=0
|
||||
```
|
||||
|
||||
이 저장소를 함께 조사한 다른 세션도 같은 값을 얻어 「`scripts/*.py` 14개 전부 `--help` 가
|
||||
종료 코드 0 으로 돌아온다」 를 확인 등급 **확인함**으로 적었다. 두 사람이 같은 값을 얻었으니
|
||||
맞는 값처럼 보였다.
|
||||
|
||||
## 값을 만든 것은 셸이다
|
||||
|
||||
파이프라인의 `$?` 는 **마지막** 명령의 종료 코드다. `head` 는 언제나 성공하므로 앞의
|
||||
`python3` 가 무엇을 반환하든 `$?` 는 0 이 된다.
|
||||
|
||||
```bash
|
||||
set +o pipefail
|
||||
false | head -1; echo "false | head -1 -> exit=$?"
|
||||
false; echo "false -> exit=$?"
|
||||
```
|
||||
|
||||
```
|
||||
false | head -1 -> exit=0
|
||||
false -> exit=1
|
||||
bash 5.2.21(1)-release
|
||||
```
|
||||
|
||||
POSIX 셸의 정의된 동작이고 이 셸의 특이점이 아니다. `pipefail` 을 켜거나
|
||||
`${PIPESTATUS[0]}` 를 읽으면 앞 명령의 값을 얻는다.
|
||||
|
||||
같은 착각이 한 번 더 났다. 파이프를 걷어 내고 다시 잴 때 이렇게 썼다.
|
||||
|
||||
```bash
|
||||
out=$(timeout 60 python3 $s --help 2>&1)
|
||||
echo "$(basename $s) exit=$?"
|
||||
```
|
||||
|
||||
명령 치환 `$(basename $s)` 가 먼저 실행되면서 `$?` 를 덮어썼다. 그래서 두 번째 측정도
|
||||
열넷 전부 0 이었다. `code=$?` 를 명령 바로 다음 줄에 두고서야 값이 갈렸다.
|
||||
|
||||
## 다시 잰 값
|
||||
|
||||
```bash
|
||||
for s in $(git ls-tree --name-only 43e1aad scripts/ | grep '\.py$'); do
|
||||
out=$(timeout 60 python3 $s --help 2>&1)
|
||||
code=$?
|
||||
n=$(basename $s)
|
||||
echo "$n exit=$code"
|
||||
done
|
||||
```
|
||||
|
||||
두 측정의 차이는 두 줄뿐이다. 잰 것은 `--help` 하나뿐이라 나머지 열둘이 다른 인자에서
|
||||
어떻게 도는지는 이 값이 말해 주지 않는다.
|
||||
|
||||
```
|
||||
2c2
|
||||
< build-tech-log-tree.py exit=0
|
||||
---
|
||||
> build-tech-log-tree.py exit=1
|
||||
13c13
|
||||
< verify-refactor-work-item.py exit=0
|
||||
---
|
||||
> verify-refactor-work-item.py exit=1
|
||||
```
|
||||
|
||||
## 왜 그 둘만인가
|
||||
|
||||
열넷 중 셋이 `argparse` 를 쓰지 않는다.
|
||||
|
||||
| 스크립트 | `argparse` | `--help` |
|
||||
|---|---|---|
|
||||
| `audit-records.py` | 없음 | `exit 0` — 「문제 없음」 |
|
||||
| `build-tech-log-tree.py` | 없음 | `exit 1` |
|
||||
| `techlog.py` | 없음 | `exit 0` — CLI 가 아니라 인자를 안 읽는다 |
|
||||
| `verify-refactor-work-item.py` | 없음 | `exit 1` |
|
||||
| 나머지 열 | 있음 | `exit 0` — usage |
|
||||
|
||||
남은 둘은 `--help` 를 옵션이 아니라 위치 인자로 먹는다. `audit-records.py` 는 그것을
|
||||
프로젝트 이름으로 받아 「문제 없음」을 찍고, `build-tech-log-tree.py` 와
|
||||
`verify-refactor-work-item.py` 는 그 이름의 파일을 못 찾아 실패한다.
|
||||
|
||||
```
|
||||
$ python3 scripts/audit-records.py --help
|
||||
|
||||
--help — 기록 0건 · 원문 0 · 메타 0 · 렌더 0
|
||||
문제 없음
|
||||
|
||||
합계 0건
|
||||
exit=0
|
||||
|
||||
$ python3 scripts/build-tech-log-tree.py --help
|
||||
--help: tech-log-tree.json 이 없다. 글감을 먼저 적는다
|
||||
exit=1
|
||||
|
||||
$ python3 scripts/verify-refactor-work-item.py --help
|
||||
REFACTOR WORK ITEM VERIFICATION: FAIL
|
||||
- invalid work-item.json: --help/work-item.json
|
||||
exit=1
|
||||
```
|
||||
|
||||
`exit 0` 이 usage 가 나왔다는 뜻은 아니다. `audit-records.py` 는 `--help` 라는 이름의
|
||||
프로젝트를 찾아 검사하고 통과시켰다.
|
||||
|
||||
## 손으로 적지 못하게 했다
|
||||
|
||||
관문 결과를 사람이 옮겨 적는 한 같은 뿌리에서 계속 난다. 그래서 명령을 돌리는 쪽과
|
||||
종료 코드를 적는 쪽을 하나로 붙였다.
|
||||
|
||||
```python
|
||||
proc = subprocess.run(command, cwd=cwd, capture_output=True,
|
||||
text=True, timeout=timeout)
|
||||
exit_code, out = proc.returncode, proc.stdout + proc.stderr
|
||||
```
|
||||
|
||||
셸 한 줄을 감싸는 래퍼도 됐지만 그러면 파이프를 다시 쓸 수 있게 된다. 값을 두 번 틀리게
|
||||
만든 것이 바로 그 셸이라 아예 거치지 않기로 했다. 대신 셸 문법이 필요한 명령은
|
||||
`bash -c` 를 인자로 넘겨야 하고, 그 안에서 다시 파이프를 쓰면 같은 실수가 난다 — 이 기록의
|
||||
증거 여섯 개도 그렇게 수집했다. 종료 코드를 인자로 받지 않으므로 손으로 적어 넣을 수는 없다. 원문은 `final/evidence/raw/` 에, 실행 메타는 `final/evidence/meta/` 에
|
||||
같은 이름으로 함께 떨어진다.
|
||||
|
||||
처음 판에서는 이 도구도 인자를 잘못 먹었다. `argparse.REMAINDER` 로 명령을 받았더니
|
||||
`--proves` 부터가 실행할 명령으로 딸려 가 `No such file or directory: '--proves'` 로 죽었다.
|
||||
지금은 `--` 앞뒤를 직접 가르고, 왜 그렇게 했는지를 코드에 한 줄로 남겨 두었다.
|
||||
|
||||
```python
|
||||
# `--` 앞뒤를 먼저 가른다. argparse.REMAINDER 에 맡기면 옵션이 명령으로 딸려 간다
|
||||
argv = sys.argv[1:]
|
||||
```
|
||||
|
||||
`--help` 를 위치 인자로 먹은 세 스크립트와 같은 종류의 실수다. 이 저장소에서 인자를 손으로
|
||||
가르는 코드는 대개 여기서 걸린다.
|
||||
|
||||
이 기록이 인용한 원문 다섯 개를 그 도구가 수집했다. 메타 하나는 이렇게 생겼다.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "help-exit-codes-measured-without-a-pipe",
|
||||
"kind": "terminal",
|
||||
"sourceRevision": "43e1aadef077ad93c30495df428ee3a71dd73f4a",
|
||||
"sourceDirty": true,
|
||||
"executedAt": "2026-09-10T09:54:41+09:00",
|
||||
"exitCode": 0,
|
||||
"exitCodeSource": "실행한 프로세스의 반환값. 손으로 적지 않는다",
|
||||
"rawPath": "evidence/raw/help-exit-codes-measured-without-a-pipe.txt",
|
||||
"proves": "파이프 없이 재면 같은 14개 중 build-tech-log-tree.py 와 verify-refactor-work-item.py 가 exit 1 이다",
|
||||
"doesNotProve": "다른 인자에서의 동작. --help 하나만 잰 값이다",
|
||||
"sha256": "e8c134d07592bb1b051ea6ad3abf952a9fa502fc85eb26cdb56194447ea03a9b",
|
||||
"bytes": 410
|
||||
}
|
||||
```
|
||||
|
||||
여기서 `exitCode: 0` 은 **측정 반복문 자체가 끝까지 돌았다**고 말한다. 열넷 각각의 종료
|
||||
코드는 원문 `raw/` 안에 있다. 반복문이 반환한 값과 그 안에서 잰 값을 섞지 않는다 — 메타에는
|
||||
반복문 쪽이 들어간다.
|
||||
|
||||
`sourceDirty` 는 그 실행 시점에 작업 트리에 커밋 안 된 변경이 있었는지 적는다. 있으면
|
||||
`sourceRevision` 이 그 출력을 설명하지 못한다. 이 다섯은 전부 `true` 다 — 수집기 자신을
|
||||
더한 상태에서 쟀다.
|
||||
|
||||
기존 경로를 지우지 않았다. 손으로 만든 `raw/`·`meta/` 도 그대로 쓰이고, 이 도구는 필요할
|
||||
때만 부른다.
|
||||
|
||||
## 이 사건이 닫지 못한 것
|
||||
|
||||
수집기는 브라우저 캡처와 대화형 명령을 담지 못한다. 이 저장소의 증거 가운데
|
||||
`final/evidence/browser/` 쪽은 여전히 Playwright 로 찍고 메타를 손으로 적는다 — 종료 코드를
|
||||
손으로 적을 수 없게 만든 것이 아직 절반이라는 뜻이다.
|
||||
|
||||
<!-- body:end -->
|
||||
@@ -0,0 +1,275 @@
|
||||
---
|
||||
id:
|
||||
kind: CASE
|
||||
slug: exit-code-read-behind-a-pipe
|
||||
title: 파이프 뒤의 종료 코드를 읽고 검사기 열넷이 다 통과한다고 적었다
|
||||
topic: pipeline-gate-exit-codes
|
||||
topicName: 관문의 종료 코드
|
||||
project: document-haness
|
||||
status: 게시 전
|
||||
studio: ""
|
||||
lastVerifiedOn: 2026-09-10
|
||||
source:
|
||||
- final/document.md#§2-관찰한-것
|
||||
- final/document.md#§3-파이프-뒤의-종료-코드
|
||||
- final/document.md#§4-다시-잰-값
|
||||
- final/document.md#§5-argparse-를-쓰지-않는-넷
|
||||
- final/document.md#§8-종료-코드를-손으로-적을-수-없게-만든다
|
||||
sourceRevision: 43e1aadef077ad93c30495df428ee3a71dd73f4a
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/exit-code-through-a-pipe.txt
|
||||
- ../../../final/evidence/raw/help-exit-codes-measured-through-a-pipe.txt
|
||||
- ../../../final/evidence/raw/help-exit-codes-measured-without-a-pipe.txt
|
||||
- ../../../final/evidence/raw/argparse-absent-scripts.txt
|
||||
- ../../../final/evidence/raw/what-the-three-print-for-help.txt
|
||||
---
|
||||
|
||||
# 파이프 뒤의 종료 코드를 읽고 검사기 열넷이 다 통과한다고 적었다
|
||||
|
||||
scripts/ 의 검사기 열넷에 --help 를 돌려 전부 종료 코드 0 을 받았다. 이 저장소를 함께
|
||||
조사한 다른 세션도 같은 값을 얻어 확인 등급 확인함으로 적었다. 값은 검사기가 아니라 재는
|
||||
방법이 만든 것이었다. 파이프를 걷어 내고 다시 재니 둘이 exit 1 이다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **검사할 것이 없을 때 관문은 무엇을 내야 하는가**
|
||||
이 사건에서 `audit-records.py --help` 가 「문제 없음」을 찍으면서 그 물음이 열렸다.
|
||||
`--help` 라는 이름의 프로젝트에는 검사할 기록이 하나도 없어 그대로 통과했다.
|
||||
|
||||
## 문제
|
||||
|
||||
관문은 종료 코드로 말한다. 단계 계약이 「관문은 종료 코드가 0 이어야 지난 것이다」 라고
|
||||
적었고, 런 원장 run.json 의 stages[].gates[].exit 에 그 값이 남는다. 사람이 셸에서 그 값을
|
||||
읽어 옮겨 적는다.
|
||||
|
||||
--help 하나를 잰 것뿐인데 값이 두 번 틀렸다. 관문 결과 전부가 같은 방법으로 적히고 있다.
|
||||
|
||||
## 결론
|
||||
|
||||
리비전 43e1aad 의 scripts/*.py 열넷 중 열둘이 --help 에 exit 0,
|
||||
build-tech-log-tree.py 와 verify-refactor-work-item.py 가 exit 1 이다.
|
||||
exit 0 이 usage 가 나왔다는 뜻은 아니다 — audit-records.py 는 --help 를 프로젝트
|
||||
이름으로 받아 검사하고 통과시킨다.
|
||||
|
||||
값을 틀리게 만든 것은 셸의 동작 둘이다. 파이프의 종료 코드 변수는 마지막 명령을 가리키고,
|
||||
명령 치환은 그 변수를 덮어쓴다. 둘 다 정의된 동작이라 셸이 경고하지 않는다.
|
||||
|
||||
조치로 scripts/capture-evidence.py 를 만들었다. 명령을 subprocess 로 직접 돌리고 그
|
||||
프로세스의 반환값을 그대로 메타에 적는다. 종료 코드를 인자로 받지 않으므로 손으로 적을
|
||||
경로가 없다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
python 3.12.3 · node v24.14.0 · bash 5.2.21(1)-release · Linux.
|
||||
대상 저장소 document-haness 리비전 43e1aadef077ad93c30495df428ee3a71dd73f4a.
|
||||
측정은 그 커밋에서 갈라진 worktree dh-B 에서 했고, 그 시점 작업 트리에는
|
||||
capture-evidence.py 가 더해져 있어 증거 메타의 sourceDirty 가 true 다.
|
||||
측정 대상 열넷은 git ls-tree 로 그 커밋의 목록만 골라 냈다.
|
||||
|
||||
## 재현 조건
|
||||
|
||||
1. document-haness 를 43e1aad 로 체크아웃한다.
|
||||
2. 파이프를 끼고 잰다 — 반복문 안에서 python3 출력을 head 로 넘기고 그다음 줄에서 $? 를 읽는다.
|
||||
3. 열넷이 전부 exit=0 으로 나오는 것을 본다.
|
||||
4. 파이프를 걷고 out=$(...) 다음 줄에서 code=$? 로 받아 다시 잰다.
|
||||
5. build-tech-log-tree.py 와 verify-refactor-work-item.py 가 exit=1 로 갈리는 것을 본다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
## 두 번 같은 값이 나왔다
|
||||
|
||||
검사기 목록을 만들려고 열넷에 `--help` 를 돌렸다. 출력이 길어서 `head` 로 잘랐다.
|
||||
|
||||
```bash
|
||||
for s in $(git ls-tree --name-only 43e1aad scripts/ | grep '\.py$'); do
|
||||
timeout 60 python3 $s --help 2>&1 | head -30 >/dev/null
|
||||
code=$?
|
||||
n=$(basename $s)
|
||||
echo "$n exit=$code"
|
||||
done
|
||||
```
|
||||
|
||||
열넷이 전부 `exit=0` 이었다.
|
||||
|
||||
```
|
||||
audit-records.py exit=0
|
||||
build-tech-log-tree.py exit=0
|
||||
check-figure-overlap.py exit=0
|
||||
check-figure-text.py exit=0
|
||||
fold-analysis-into-final.py exit=0
|
||||
fold-studio-contract-into-index.py exit=0
|
||||
preview-figure.py exit=0
|
||||
studio-body.py exit=0
|
||||
techlog.py exit=0
|
||||
verify-pipeline-run.py exit=0
|
||||
verify-pipeline.py exit=0
|
||||
verify-project-layout.py exit=0
|
||||
verify-refactor-work-item.py exit=0
|
||||
verify-tech-log-tree.py exit=0
|
||||
```
|
||||
|
||||
이 저장소를 함께 조사한 다른 세션도 같은 값을 얻어 「`scripts/*.py` 14개 전부 `--help` 가
|
||||
종료 코드 0 으로 돌아온다」 를 확인 등급 **확인함**으로 적었다. 두 사람이 같은 값을 얻었으니
|
||||
맞는 값처럼 보였다.
|
||||
|
||||
## 값을 만든 것은 셸이다
|
||||
|
||||
파이프라인의 `$?` 는 **마지막** 명령의 종료 코드다. `head` 는 언제나 성공하므로 앞의
|
||||
`python3` 가 무엇을 반환하든 `$?` 는 0 이 된다.
|
||||
|
||||
```bash
|
||||
set +o pipefail
|
||||
false | head -1; echo "false | head -1 -> exit=$?"
|
||||
false; echo "false -> exit=$?"
|
||||
```
|
||||
|
||||
```
|
||||
false | head -1 -> exit=0
|
||||
false -> exit=1
|
||||
bash 5.2.21(1)-release
|
||||
```
|
||||
|
||||
POSIX 셸의 정의된 동작이고 이 셸의 특이점이 아니다. `pipefail` 을 켜거나
|
||||
`${PIPESTATUS[0]}` 를 읽으면 앞 명령의 값을 얻는다.
|
||||
|
||||
같은 착각이 한 번 더 났다. 파이프를 걷어 내고 다시 잴 때 이렇게 썼다.
|
||||
|
||||
```bash
|
||||
out=$(timeout 60 python3 $s --help 2>&1)
|
||||
echo "$(basename $s) exit=$?"
|
||||
```
|
||||
|
||||
명령 치환 `$(basename $s)` 가 먼저 실행되면서 `$?` 를 덮어썼다. 그래서 두 번째 측정도
|
||||
열넷 전부 0 이었다. `code=$?` 를 명령 바로 다음 줄에 두고서야 값이 갈렸다.
|
||||
|
||||
## 다시 잰 값
|
||||
|
||||
```bash
|
||||
for s in $(git ls-tree --name-only 43e1aad scripts/ | grep '\.py$'); do
|
||||
out=$(timeout 60 python3 $s --help 2>&1)
|
||||
code=$?
|
||||
n=$(basename $s)
|
||||
echo "$n exit=$code"
|
||||
done
|
||||
```
|
||||
|
||||
두 측정의 차이는 두 줄뿐이다. 잰 것은 `--help` 하나뿐이라 나머지 열둘이 다른 인자에서
|
||||
어떻게 도는지는 이 값이 말해 주지 않는다.
|
||||
|
||||
```
|
||||
2c2
|
||||
< build-tech-log-tree.py exit=0
|
||||
---
|
||||
> build-tech-log-tree.py exit=1
|
||||
13c13
|
||||
< verify-refactor-work-item.py exit=0
|
||||
---
|
||||
> verify-refactor-work-item.py exit=1
|
||||
```
|
||||
|
||||
## 왜 그 둘만인가
|
||||
|
||||
열넷 중 넷이 `argparse` 를 쓰지 않는다.
|
||||
|
||||
| 스크립트 | `argparse` | `--help` |
|
||||
|---|---|---|
|
||||
| `audit-records.py` | 없음 | `exit 0` — 「문제 없음」 |
|
||||
| `build-tech-log-tree.py` | 없음 | `exit 1` |
|
||||
| `techlog.py` | 없음 | `exit 0` — CLI 가 아니라 인자를 안 읽는다 |
|
||||
| `verify-refactor-work-item.py` | 없음 | `exit 1` |
|
||||
| 나머지 열 | 있음 | `exit 0` — usage |
|
||||
|
||||
남은 셋은 `--help` 를 옵션이 아니라 위치 인자로 먹는다. `audit-records.py` 는 그것을
|
||||
프로젝트 이름으로 받아 「문제 없음」을 찍고, `build-tech-log-tree.py` 와
|
||||
`verify-refactor-work-item.py` 는 그 이름의 파일을 못 찾아 실패한다.
|
||||
|
||||
```
|
||||
$ python3 scripts/audit-records.py --help
|
||||
|
||||
--help — 기록 0건 · 원문 0 · 메타 0 · 렌더 0
|
||||
문제 없음
|
||||
|
||||
합계 0건
|
||||
exit=0
|
||||
|
||||
$ python3 scripts/build-tech-log-tree.py --help
|
||||
--help: tech-log-tree.json 이 없다. 글감을 먼저 적는다
|
||||
exit=1
|
||||
|
||||
$ python3 scripts/verify-refactor-work-item.py --help
|
||||
REFACTOR WORK ITEM VERIFICATION: FAIL
|
||||
- invalid work-item.json: --help/work-item.json
|
||||
exit=1
|
||||
```
|
||||
|
||||
`exit 0` 이 usage 가 나왔다는 뜻은 아니다. `audit-records.py` 는 `--help` 라는 이름의
|
||||
프로젝트를 찾아 검사하고 통과시켰다.
|
||||
|
||||
## 손으로 적지 못하게 했다
|
||||
|
||||
관문 결과를 사람이 옮겨 적는 한 같은 뿌리에서 계속 난다. 그래서 명령을 돌리는 쪽과
|
||||
종료 코드를 적는 쪽을 하나로 붙였다.
|
||||
|
||||
```python
|
||||
proc = subprocess.run(command, cwd=cwd, capture_output=True,
|
||||
text=True, timeout=timeout)
|
||||
exit_code, out = proc.returncode, proc.stdout + proc.stderr
|
||||
```
|
||||
|
||||
셸 한 줄을 감싸는 래퍼도 됐지만 그러면 파이프를 다시 쓸 수 있게 된다. 값을 두 번 틀리게
|
||||
만든 것이 바로 그 셸이라 아예 거치지 않기로 했다. 대신 셸 문법이 필요한 명령은
|
||||
`bash -c` 를 인자로 넘겨야 하고, 그 안에서 다시 파이프를 쓰면 같은 실수가 난다 — 이 기록의
|
||||
증거 여섯 개도 그렇게 수집했다. 종료 코드를 인자로 받지 않으므로 손으로 적어 넣을 수는 없다. 원문은 `final/evidence/raw/` 에, 실행 메타는 `final/evidence/meta/` 에
|
||||
같은 이름으로 함께 떨어진다.
|
||||
|
||||
처음 판에서는 이 도구도 인자를 잘못 먹었다. `argparse.REMAINDER` 로 명령을 받았더니
|
||||
`--proves` 부터가 실행할 명령으로 딸려 가 `No such file or directory: '--proves'` 로 죽었다.
|
||||
지금은 `--` 앞뒤를 직접 가르고, 왜 그렇게 했는지를 코드에 한 줄로 남겨 두었다.
|
||||
|
||||
```python
|
||||
# `--` 앞뒤를 먼저 가른다. argparse.REMAINDER 에 맡기면 옵션이 명령으로 딸려 간다
|
||||
argv = sys.argv[1:]
|
||||
```
|
||||
|
||||
`--help` 를 위치 인자로 먹은 세 스크립트와 같은 종류의 실수다. 이 저장소에서 인자를 손으로
|
||||
가르는 코드는 대개 여기서 걸린다.
|
||||
|
||||
이 기록이 인용한 원문 다섯 개를 그 도구가 수집했다. 메타 하나는 이렇게 생겼다.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "help-exit-codes-measured-without-a-pipe",
|
||||
"kind": "terminal",
|
||||
"sourceRevision": "43e1aadef077ad93c30495df428ee3a71dd73f4a",
|
||||
"sourceDirty": true,
|
||||
"executedAt": "2026-09-10T09:54:41+09:00",
|
||||
"exitCode": 0,
|
||||
"exitCodeSource": "실행한 프로세스의 반환값. 손으로 적지 않는다",
|
||||
"rawPath": "evidence/raw/help-exit-codes-measured-without-a-pipe.txt",
|
||||
"proves": "파이프 없이 재면 같은 14개 중 build-tech-log-tree.py 와 verify-refactor-work-item.py 가 exit 1 이다",
|
||||
"doesNotProve": "다른 인자에서의 동작. --help 하나만 잰 값이다",
|
||||
"sha256": "e8c134d07592bb1b051ea6ad3abf952a9fa502fc85eb26cdb56194447ea03a9b",
|
||||
"bytes": 410
|
||||
}
|
||||
```
|
||||
|
||||
여기서 `exitCode: 0` 은 **측정 반복문 자체가 끝까지 돌았다**고 말한다. 열넷 각각의 종료
|
||||
코드는 원문 `raw/` 안에 있다. 반복문이 반환한 값과 그 안에서 잰 값을 섞지 않는다 — 메타에는
|
||||
반복문 쪽이 들어간다.
|
||||
|
||||
`sourceDirty` 는 그 실행 시점에 작업 트리에 커밋 안 된 변경이 있었는지 적는다. 있으면
|
||||
`sourceRevision` 이 그 출력을 설명하지 못한다. 이 다섯은 전부 `true` 다 — 수집기 자신을
|
||||
더한 상태에서 쟀다.
|
||||
|
||||
기존 경로를 지우지 않았다. 손으로 만든 `raw/`·`meta/` 도 그대로 쓰이고, 이 도구는 필요할
|
||||
때만 부른다.
|
||||
|
||||
## 이 사건이 닫지 못한 것
|
||||
|
||||
수집기는 브라우저 캡처와 대화형 명령을 담지 못한다. 이 저장소의 증거 가운데
|
||||
`final/evidence/browser/` 쪽은 여전히 Playwright 로 찍고 메타를 손으로 적는다 — 종료 코드를
|
||||
손으로 적을 수 없게 만든 것이 아직 절반이라는 뜻이다.
|
||||
|
||||
<!-- body:end -->
|
||||
@@ -0,0 +1,278 @@
|
||||
---
|
||||
id:
|
||||
kind: CASE
|
||||
slug: exit-code-read-behind-a-pipe
|
||||
title: 파이프 뒤의 종료 코드를 읽고 검사기 열넷이 다 통과한다고 적었다
|
||||
topic: pipeline-gate-exit-codes
|
||||
topicName: 관문의 종료 코드
|
||||
project: document-haness
|
||||
status: 게시 전
|
||||
studio: ""
|
||||
lastVerifiedOn: 2026-09-10
|
||||
source:
|
||||
- final/document.md#§2-관찰한-것
|
||||
- final/document.md#§3-파이프-뒤의-종료-코드
|
||||
- final/document.md#§4-다시-잰-값
|
||||
- final/document.md#§5-argparse-를-쓰지-않는-넷
|
||||
- final/document.md#§8-종료-코드를-손으로-적을-수-없게-만든다
|
||||
sourceRevision: 43e1aadef077ad93c30495df428ee3a71dd73f4a
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/exit-code-through-a-pipe.txt
|
||||
- ../../../final/evidence/raw/help-exit-codes-measured-through-a-pipe.txt
|
||||
- ../../../final/evidence/raw/help-exit-codes-measured-without-a-pipe.txt
|
||||
- ../../../final/evidence/raw/argparse-absent-scripts.txt
|
||||
- ../../../final/evidence/raw/what-the-three-print-for-help.txt
|
||||
---
|
||||
|
||||
# 파이프 뒤의 종료 코드를 읽고 검사기 열넷이 다 통과한다고 적었다
|
||||
|
||||
scripts/ 의 검사기 열넷에 --help 를 돌려 전부 종료 코드 0 을 받았고, 이 저장소를 함께
|
||||
조사한 다른 세션도 같은 값을 얻어 확인 등급 확인함으로 적었다. 값은 검사기가 아니라 재는
|
||||
방법이 만든 것이었다. 파이프를 걷어 내고 다시 재니 둘이 exit 1 이다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **검사할 것이 없을 때 관문은 무엇을 내야 하는가**
|
||||
이 사건에서 `audit-records.py --help` 가 「문제 없음」을 찍으면서 그 물음이 열렸다.
|
||||
`--help` 라는 이름의 프로젝트에는 검사할 기록이 하나도 없어 그대로 통과했다.
|
||||
|
||||
## 문제
|
||||
|
||||
관문은 종료 코드로 말한다. 단계 계약이 「관문은 종료 코드가 0 이어야 지난 것이다」 라고
|
||||
적었고, 런 원장 run.json 의 stages[].gates[].exit 에 그 값이 남는데, 그 값을 사람이 셸에서
|
||||
읽어 옮겨 적는다.
|
||||
|
||||
--help 하나를 잰 것뿐인데 값이 두 번 틀렸다. 관문 결과 전부가 같은 방법으로 적히고 있다.
|
||||
|
||||
## 결론
|
||||
|
||||
리비전 43e1aad 의 scripts/*.py 열넷 중 열둘이 --help 에 exit 0,
|
||||
build-tech-log-tree.py 와 verify-refactor-work-item.py 가 exit 1 이다.
|
||||
exit 0 이 usage 가 나왔다는 뜻은 아니다 — audit-records.py 는 --help 를 프로젝트
|
||||
이름으로 받아 검사하고 통과시킨다.
|
||||
|
||||
값을 틀리게 만든 것은 셸의 동작 둘이다. 파이프의 종료 코드 변수는 마지막 명령을 가리키고,
|
||||
명령 치환은 그 변수를 덮어쓴다. 둘 다 정의된 동작이라 셸이 경고하지 않는다.
|
||||
|
||||
조치로 만든 scripts/capture-evidence.py 는 명령을 subprocess 로 직접 돌리고 그 프로세스의
|
||||
반환값을 그대로 메타에 적는다. 종료 코드를 인자로 받지 않으므로 손으로 적을 경로가 없다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
python 3.12.3 · node v24.14.0 · bash 5.2.21(1)-release · Linux.
|
||||
대상 저장소 document-haness 리비전 43e1aadef077ad93c30495df428ee3a71dd73f4a.
|
||||
측정은 그 커밋에서 갈라진 worktree dh-B 에서 했고, 그 시점 작업 트리에는
|
||||
capture-evidence.py 가 더해져 있어 증거 메타의 sourceDirty 가 true 다.
|
||||
측정 대상 열넷은 git ls-tree 로 그 커밋의 목록만 골라 냈다.
|
||||
|
||||
## 재현 조건
|
||||
|
||||
1. document-haness 를 43e1aad 로 체크아웃한다.
|
||||
2. 파이프를 끼고 잰다 — 반복문 안에서 python3 출력을 head 로 넘기고 그다음 줄에서 $? 를 읽는다.
|
||||
3. 열넷이 전부 exit=0 으로 나오는 것을 본다.
|
||||
4. 파이프를 걷고 out=$(...) 다음 줄에서 code=$? 로 받아 다시 잰다.
|
||||
5. build-tech-log-tree.py 와 verify-refactor-work-item.py 가 exit=1 로 갈리는 것을 본다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
## 두 번 같은 값이 나왔다
|
||||
|
||||
검사기 목록을 만들려고 열넷에 `--help` 를 돌렸는데, 출력이 길어서 `head` 로 잘랐다.
|
||||
|
||||
```bash
|
||||
for s in $(git ls-tree --name-only 43e1aad scripts/ | grep '\.py$'); do
|
||||
timeout 60 python3 $s --help 2>&1 | head -30 >/dev/null
|
||||
code=$?
|
||||
n=$(basename $s)
|
||||
echo "$n exit=$code"
|
||||
done
|
||||
```
|
||||
|
||||
열넷이 전부 `exit=0` 이었다.
|
||||
|
||||
```
|
||||
audit-records.py exit=0
|
||||
build-tech-log-tree.py exit=0
|
||||
check-figure-overlap.py exit=0
|
||||
check-figure-text.py exit=0
|
||||
fold-analysis-into-final.py exit=0
|
||||
fold-studio-contract-into-index.py exit=0
|
||||
preview-figure.py exit=0
|
||||
studio-body.py exit=0
|
||||
techlog.py exit=0
|
||||
verify-pipeline-run.py exit=0
|
||||
verify-pipeline.py exit=0
|
||||
verify-project-layout.py exit=0
|
||||
verify-refactor-work-item.py exit=0
|
||||
verify-tech-log-tree.py exit=0
|
||||
```
|
||||
|
||||
이 저장소를 함께 조사한 다른 세션도 같은 값을 얻어 「`scripts/*.py` 14개 전부 `--help` 가
|
||||
종료 코드 0 으로 돌아온다」 를 확인 등급 **확인함**으로 적었다. 두 사람이 같은 값을 얻었으니
|
||||
맞는 값처럼 보였다.
|
||||
|
||||
## 값을 만든 것은 셸이다
|
||||
|
||||
파이프라인의 `$?` 는 **마지막** 명령의 종료 코드다. `head` 는 언제나 성공하므로 앞의
|
||||
`python3` 가 무엇을 반환하든 `$?` 는 0 이 된다.
|
||||
|
||||
```bash
|
||||
set +o pipefail
|
||||
false | head -1; echo "false | head -1 -> exit=$?"
|
||||
false; echo "false -> exit=$?"
|
||||
```
|
||||
|
||||
```
|
||||
false | head -1 -> exit=0
|
||||
false -> exit=1
|
||||
bash 5.2.21(1)-release
|
||||
```
|
||||
|
||||
POSIX 셸의 정의된 동작이고 이 셸의 특이점이 아니다. `pipefail` 을 켜거나
|
||||
`${PIPESTATUS[0]}` 를 읽으면 앞 명령의 값을 얻는다.
|
||||
|
||||
같은 착각이 한 번 더 났다. 파이프를 걷어 내고 다시 잴 때 이렇게 썼다.
|
||||
|
||||
```bash
|
||||
out=$(timeout 60 python3 $s --help 2>&1)
|
||||
echo "$(basename $s) exit=$?"
|
||||
```
|
||||
|
||||
명령 치환 `$(basename $s)` 가 먼저 실행되면서 `$?` 를 덮어써서 두 번째 측정도 열넷 전부
|
||||
0 이었다. `code=$?` 를 명령 바로 다음 줄에 두고서야 값이 갈렸다.
|
||||
|
||||
## 다시 잰 값
|
||||
|
||||
```bash
|
||||
for s in $(git ls-tree --name-only 43e1aad scripts/ | grep '\.py$'); do
|
||||
out=$(timeout 60 python3 $s --help 2>&1)
|
||||
code=$?
|
||||
n=$(basename $s)
|
||||
echo "$n exit=$code"
|
||||
done
|
||||
```
|
||||
|
||||
두 측정의 차이는 두 줄뿐이다.
|
||||
|
||||
```
|
||||
2c2
|
||||
< build-tech-log-tree.py exit=0
|
||||
---
|
||||
> build-tech-log-tree.py exit=1
|
||||
13c13
|
||||
< verify-refactor-work-item.py exit=0
|
||||
---
|
||||
> verify-refactor-work-item.py exit=1
|
||||
```
|
||||
|
||||
잰 것은 `--help` 하나뿐이라 나머지 열둘이 다른 인자에서 어떻게 도는지는 이 값이 말해 주지
|
||||
않는다.
|
||||
|
||||
## 왜 그 둘만인가
|
||||
|
||||
열넷 중 넷이 `argparse` 를 쓰지 않는다.
|
||||
|
||||
| 스크립트 | `argparse` | `--help` |
|
||||
|---|---|---|
|
||||
| `audit-records.py` | 없음 | `exit 0` — 「문제 없음」 |
|
||||
| `build-tech-log-tree.py` | 없음 | `exit 1` |
|
||||
| `techlog.py` | 없음 | `exit 0` — CLI 가 아니라 인자를 안 읽는다 |
|
||||
| `verify-refactor-work-item.py` | 없음 | `exit 1` |
|
||||
| 나머지 열 | 있음 | `exit 0` — usage |
|
||||
|
||||
남은 셋은 `--help` 를 옵션이 아니라 위치 인자로 먹는다. `audit-records.py` 는 그것을
|
||||
프로젝트 이름으로 받아 「문제 없음」을 찍고, `build-tech-log-tree.py` 와
|
||||
`verify-refactor-work-item.py` 는 그 이름의 파일을 못 찾아 실패한다.
|
||||
|
||||
```
|
||||
$ python3 scripts/audit-records.py --help
|
||||
|
||||
--help — 기록 0건 · 원문 0 · 메타 0 · 렌더 0
|
||||
문제 없음
|
||||
|
||||
합계 0건
|
||||
exit=0
|
||||
|
||||
$ python3 scripts/build-tech-log-tree.py --help
|
||||
--help: tech-log-tree.json 이 없다. 글감을 먼저 적는다
|
||||
exit=1
|
||||
|
||||
$ python3 scripts/verify-refactor-work-item.py --help
|
||||
REFACTOR WORK ITEM VERIFICATION: FAIL
|
||||
- invalid work-item.json: --help/work-item.json
|
||||
exit=1
|
||||
```
|
||||
|
||||
`exit 0` 이 usage 가 나왔다는 뜻은 아니다. `audit-records.py` 는 `--help` 라는 이름의
|
||||
프로젝트를 찾아 검사하고 통과시켰다.
|
||||
|
||||
## 손으로 적지 못하게 했다
|
||||
|
||||
관문 결과를 사람이 옮겨 적는 한 같은 뿌리에서 같은 실수가 계속 난다. 그래서 명령을 돌리는
|
||||
쪽과 종료 코드를 적는 쪽을 하나로 붙였다.
|
||||
|
||||
```python
|
||||
proc = subprocess.run(command, cwd=cwd, capture_output=True,
|
||||
text=True, timeout=timeout)
|
||||
exit_code, out = proc.returncode, proc.stdout + proc.stderr
|
||||
```
|
||||
|
||||
종료 코드를 인자로 받지 않으므로 손으로 적어 넣을 수는 없다. 원문은
|
||||
`final/evidence/raw/` 에, 실행 메타는 `final/evidence/meta/` 에 같은 이름으로 함께 떨어진다.
|
||||
|
||||
셸 한 줄을 감싸는 래퍼도 됐지만 그러면 파이프를 다시 쓸 수 있게 된다. 값을 두 번 틀리게
|
||||
만든 것이 바로 그 셸이라 아예 거치지 않기로 했다. 대신 셸 문법이 필요한 명령은 `bash -c` 를
|
||||
인자로 넘겨야 하고, 그 안에서 다시 파이프를 쓰면 같은 실수가 난다 — 이 기록의 증거
|
||||
다섯 개도 그렇게 수집했다.
|
||||
|
||||
처음 판에서는 이 도구도 인자를 잘못 먹었다. `argparse.REMAINDER` 로 명령을 받았더니
|
||||
`--proves` 부터가 실행할 명령으로 딸려 가 `No such file or directory: '--proves'` 로 죽었다.
|
||||
지금은 `--` 앞뒤를 직접 가르고, 왜 그렇게 했는지를 코드에 한 줄로 남겨 두었다.
|
||||
|
||||
```python
|
||||
# `--` 앞뒤를 먼저 가른다. argparse.REMAINDER 에 맡기면 옵션이 명령으로 딸려 간다
|
||||
argv = sys.argv[1:]
|
||||
```
|
||||
|
||||
`--help` 를 위치 인자로 먹은 세 스크립트와 같은 종류의 실수다. 이 저장소에서 인자를 손으로
|
||||
가르는 코드는 대개 여기서 걸린다.
|
||||
|
||||
이 기록이 인용한 원문 다섯 개를 그 도구가 수집했다. 메타 하나는 이렇게 생겼다.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "help-exit-codes-measured-without-a-pipe",
|
||||
"kind": "terminal",
|
||||
"sourceRevision": "43e1aadef077ad93c30495df428ee3a71dd73f4a",
|
||||
"sourceDirty": true,
|
||||
"executedAt": "2026-09-10T09:54:41+09:00",
|
||||
"exitCode": 0,
|
||||
"exitCodeSource": "실행한 프로세스의 반환값. 손으로 적지 않는다",
|
||||
"rawPath": "evidence/raw/help-exit-codes-measured-without-a-pipe.txt",
|
||||
"proves": "파이프 없이 재면 같은 14개 중 build-tech-log-tree.py 와 verify-refactor-work-item.py 가 exit 1 이다",
|
||||
"doesNotProve": "다른 인자에서의 동작. --help 하나만 잰 값이다",
|
||||
"sha256": "e8c134d07592bb1b051ea6ad3abf952a9fa502fc85eb26cdb56194447ea03a9b",
|
||||
"bytes": 410
|
||||
}
|
||||
```
|
||||
|
||||
여기서 `exitCode: 0` 은 **측정 반복문 자체가 끝까지 돌았다**고 말한다. 열넷 각각의 종료
|
||||
코드는 원문 `raw/` 안에 있다. 반복문이 반환한 값과 그 안에서 잰 값을 섞지 않는다 — 메타에는
|
||||
반복문 쪽이 들어간다.
|
||||
|
||||
`sourceDirty` 는 그 실행 시점에 작업 트리에 커밋 안 된 변경이 있었는지 적는다. 있으면
|
||||
`sourceRevision` 이 그 출력을 설명하지 못한다. 이 다섯은 전부 `true` 다 — 수집기 자신을
|
||||
더한 상태에서 쟀다.
|
||||
|
||||
기존 경로를 지우지 않았다. 손으로 만든 `raw/`·`meta/` 도 그대로 쓰이고, 이 도구는 필요할
|
||||
때만 부른다.
|
||||
|
||||
## 이 사건이 닫지 못한 것
|
||||
|
||||
수집기는 브라우저 캡처와 대화형 명령을 담지 못한다. 이 저장소의 증거 가운데
|
||||
`final/evidence/browser/` 쪽은 여전히 Playwright 로 찍고 메타를 손으로 적는다 — 종료 코드를
|
||||
손으로 적을 수 없게 만든 것이 아직 절반이라는 뜻이다.
|
||||
|
||||
<!-- body:end -->
|
||||
Reference in New Issue
Block a user