Files
llm-wiki/raw/errors/ca-comment-to-readme-subagents-overstrip-runtime-strings.md
T

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
parallel-subagents
refactoring
comment-cleanup
behavior-preserving
diff-audit
app-bootstrap
ca-skeleton
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 를 떼어냈다:

  1. FlywayProdSafetyValidator — startup 예외 메시지 "prod profile forbids these Flyway options (feature-migration-startup-contract D2/D4): " + violations …"prod profile forbids these Flyway options: " + violations … ((…D2/D4) 제거)
  2. SecretSourceValidator — startup 예외 메시지 "… empty secret is forbidden (feature-secrets-config-source-contract §테스트 계약).""… empty secret is forbidden."
  3. OutboxLeaderElectionToken.STRATEGY_DESCRIPTIONprivate static final String 상수 "… SKIP LOCKED, I3/D8)""… SKIP LOCKED)"
  4. MeteredDistributedLockPortlog.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; // oldcode; // 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.

교훈 / 재발 방지

  1. subagent 지시에 명시: "exception message·log message·static final String 상수 등 런타임 문자열 리터럴은 byte-identical 보존. 문자열 안의 tracking ID 도 건드리지 말 것 — 그건 주석이 아니라 프로그램 출력이다."
  2. 완료 후 non-comment diff audit 을 항상 실행 (위 grep). comment-only 를 주장하려면 non-comment 변경이 0(또는 전부 사전 작업)임을 증명해야 한다.
  3. 선례 확인: 같은 캠페인의 직전 커밋이 string 값을 보존했는지 먼저 본다(git show <prev> | grep '"'). 팀 관례가 SSOT.
  4. 런타임 문자열의 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 상쇄