5.0 KiB
title, source_type, status, tags, created
| title | source_type | status | tags | created | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| 병렬 comment→README subagent 가 런타임 문자열(exception/log/marker) 에서 tracking ID 까지 제거 — behavior change | error-note | raw |
|
2026-06-19 |
병렬 comment→README subagent 의 런타임 문자열 over-strip
Parent
[[raw/branch-notes/chore-app-bootstrap-comment-cleanup]]
맥락
app-bootstrap 61파일의 결정-근거 주석을 README 로 이전하는 작업을, 패키지 그룹별 8개 general-purpose subagent 에 병렬 분산했다. 각 subagent 지시: "주석/JavaDoc 만 수정. 코드·시그니처·애너테이션·import·field 명·logic 변경 금지." 추적 ID(D7, feature-…-contract, branch-note §)는 코드에서 제거 대상으로 명시.
현상 — "주석"의 경계를 넘은 4건
subagent 들이 추적 ID 를 제거하면서, 주석이 아니라 런타임 문자열 리터럴에서도 ID 를 떼어냈다:
FlywayProdSafetyValidator— startup 예외 메시지"prod profile forbids these Flyway options (feature-migration-startup-contract D2/D4): " + violations …→"prod profile forbids these Flyway options: " + violations …((…D2/D4)제거)SecretSourceValidator— startup 예외 메시지"… empty secret is forbidden (feature-secrets-config-source-contract §테스트 계약)."→"… empty secret is forbidden."OutboxLeaderElectionToken.STRATEGY_DESCRIPTION—private static final String상수"… SKIP LOCKED, I3/D8)"→"… SKIP LOCKED)"MeteredDistributedLockPort—log.warn(...)메시지"… critical section (D6 efficiency-lock boundary)"→"… critical section"
모두 컴파일은 통과하고, 해당 메시지를 assert 하는 테스트도 없었다(grep 으로 확인). 즉 조용한 behavior change — 컴파일/테스트로는 안 잡힌다. exception/log 메시지는 운영자-facing 출력이고, marker 상수는 strategyDescription() 반환값이라 관측 가능한 프로그램 상태다.
왜 위험한가
- "comment-only refactor" 라고 보고하면서 실제로는 런타임 출력을 바꾼다 → 리뷰어/사용자 신뢰 위반.
- 직전 모듈 선례(adapter-web commit 029e972)는
ClientSafeErrorMessages의 string 값을 이동만 하고 값은 byte-identical 보존했다 → 팀 표준은 "문자열 값 불변". - LLM 에이전트는 "주석"과 "주석처럼 생긴 문자열(괄호 안 ID 가 든 메시지)"을 자연스럽게 동일시한다. 지시에 "string literal/exception message/log message 도 보존"을 명시하지 않으면 넘어간다.
탐지 — non-comment changed-line diff audit
working-tree 에 무관한 사전 작업(*Properties→*Settings rename 등)이 섞여 있어 git checkout 류 통째 비교가 불가. 대신 diff 에서 주석 마커로 시작하지 않는 변경 라인만 추출:
git diff -- <module>/src/main/java | grep -E '^[+-]' | grep -vE '^(\+\+\+|---)' \
| grep -vE '^[+-][[:space:]]*(\*|//|/\*)' \
| grep -vE '^[+-][[:space:]]*\*/' \
| grep -vE '^[+-][[:space:]]*$'
잔여를 3분류:
- (a) trailing inline 주석 변경 —
code; // old→code; // new.+/-의//앞 코드부가 동일하면 OK(주석만 바뀜). - (b) 사전 working-tree 변경 — rename/feature work. 이 작업 무관, OK.
- (c) string 리터럴 값 변경 —
"…"안의 텍스트가 바뀜. ← revert 대상.
" 포함 변경 라인만 따로 좁히면 (c) 식별이 빨라진다. 단 배열 요소의 trailing 주석 제거("KEY", // note → "KEY",)는 string 값 동일이므로 (a)로 분류(오탐 주의).
해결
(c) 4건을 각각 HEAD 원문으로 surgical Edit revert(주석 변경은 보존). revert 후 audit 재실행 → string-literal 변경 0, (a)(b)만 잔존 확인. compileJava/compileTestJava 재확인 BUILD SUCCESSFUL.
교훈 / 재발 방지
- subagent 지시에 명시: "exception message·log message·
static final String상수 등 런타임 문자열 리터럴은 byte-identical 보존. 문자열 안의 tracking ID 도 건드리지 말 것 — 그건 주석이 아니라 프로그램 출력이다." - 완료 후 non-comment diff audit 을 항상 실행 (위 grep). comment-only 를 주장하려면 non-comment 변경이 0(또는 전부 사전 작업)임을 증명해야 한다.
- 선례 확인: 같은 캠페인의 직전 커밋이 string 값을 보존했는지 먼저 본다(
git show <prev> | grep '"'). 팀 관례가 SSOT. - 런타임 문자열의 tracking ID 정리가 정말 필요하면 그건 별도 작업으로 분리하고 사용자 승인을 받는다(behavior change 이므로).
관련
- 같은 패턴 형제 cleanup:
[[raw/branch-notes/chore-adapter-persistence-rdbms-comment-cleanup]],[[raw/branch-notes/chore-shared-contract-comment-noise-cleanup]] [[memory/proportional-orchestration]]— 병렬 dispatch 는 규모에 비례, 단 audit 으로 over-reach 상쇄