ENAMETOOLONG 은 파일 탓이 아니었습니다 — 경로 한도는 open() 에 넘긴 문자열 길이였습니다
사내 문서 지식그래프 검색 도구는 공유 문서함을 훑어 파일을 읽고 색인합니다. 어느 날 한 공공 발주처 산출물 한 건이 'File name too long' 으로 읽히지 않았습니다. 그 파일의 절대 경로가 1,075바이트였고, 우리 환경의 경로 한도는 1,024바이트였습니다.
저는 그 자리에서 결론을 냈습니다. 이 OS 가 이 파일을 영영 못 연다, 원본이 있는 클라우드 드라이브에서 파일 이름을 줄여야 한다. 그리고 그 결론을 코드로 굳혔습니다. 그 파일을 읽기 실패로 갈라 두고, 매일 '파일 이름을 줄여 주세요' 알림을 보내는 분기를 넣었습니다.
하루 뒤에 이 결론이 통째로 뒤집혔습니다. 뒤집는 데 걸린 시간은 3분이었습니다.
처방을 남에게 시키는 순간 그건 처방이 아닙니다
그 분기에는 문제가 하나 더 있었습니다. 파일이 들어온 폴더가 **남의 공유 폴더**였습니다. 우리에게 그 폴더의 파일 이름을 줄일 권한이 없습니다. 즉 매일 보내던 알림은 아무도 실행할 수 없는 일을 요구하고 있었습니다.
그 사실은 제가 발견한 것이 아닙니다. '남의 폴더라 못 줄이는데요' 라는 되물음이 들어왔습니다. 한 마디였고, 그 한 마디가 조사를 열었습니다. 저는 전날 에러 문구를 그대로 받아 결론으로 옮겨 적었을 뿐이고, 그 사이에 확인 한 칸이 비어 있었습니다.
비어 있던 칸은 이것입니다. **내가 부른 방식이 유일한 방식인가.**

세 가지를 직접 재 봤습니다
다시 보기로 한 뒤에는 추론을 멈추고 세 방식으로 같은 파일을 열어 봤습니다. 결과는 아래와 같습니다.
열어 본 방식 | 결과 |
|---|---|
절대 경로를 그대로 넘겨 열기 | 실패 — 'ENAMETOOLONG'(이 OS 에서는 Errno 63) |
심볼릭 링크로 앞부분을 짧게 만들어 열기 | 실패 — 커널이 링크를 풀어 원래 경로로 다시 잰다 |
폴더를 먼저 열고 파일명만으로 열기 | 성공 — 63,226바이트 전량 수신 |
세 번째가 통한 순간 전날 결론의 주어가 바뀌었습니다. 한도에 걸린 것은 **파일의 절대 경로 길이가 아니라, 'open()' 과 'stat()' 에 넘기는 경로 문자열의 길이**였습니다. 그 파일의 이름만 따로 재면 400바이트입니다. 폴더를 먼저 열어 그 디렉터리 서술자(열어 둔 폴더를 가리키는 핸들)를 기준으로 400바이트만 넘기면 한도 안입니다. 압축 컨테이너 무결성 검사까지 통과했습니다.
심볼릭 링크 쪽도 그냥 버린 시도가 아닙니다. 심볼릭 링크로 앞을 짧게 만드는 우회는 직관적으로는 통할 것 같은데 안 됩니다. 커널이 링크를 풀어 원래 경로로 다시 재기 때문입니다. 세 번째가 통한 이유와 두 번째가 안 통한 이유가 같은 원리를 가리킵니다.

힌트는 처음부터 있었습니다
다시 보고 나서야 전날 지나친 신호가 보였습니다. 표준 파일 찾기 명령은 그 파일을 **목록에 올리고 있었습니다.** 그 도구가 디렉터리를 순회하면서 내부적으로 작업 디렉터리를 옮겨 다니기 때문입니다. 즉 누군가는 이미 그 파일에 닿고 있었습니다.
목록에는 뜨는데 못 연다는 것은 이상한 상태입니다. 같은 대상을 다루는 다른 창구가 이미 잘 돌고 있으면, 그게 제 결론을 뒤집는 가장 값싼 반증입니다. A 는 되는데 B 는 안 되는 것이 보이면 플랫폼의 한계가 아니라 A 와 B 의 차이를 봐야 합니다. 저는 그 차이를 하루 늦게 봤습니다.
폴백은 실패했을 때만 타게 했습니다
고치는 범위는 좁게 잡았습니다. 수집기에서 파일 판정·메타 조회·해시 계산·업로드 네 자리가 경로를 받는데, 그 네 자리를 전부 새 방식으로 바꾸지는 않았습니다.
짧은 경로는 기존대로 경로 문자열을 그대로 넘깁니다
'ENAMETOOLONG' 이 났을 때만 디렉터리 서술자 기준으로 다시 엽니다
예열 스크립트는 폴더로 들어가 파일명으로 읽게 바꾸고, 너무 긴 경로를 따로 모아 두던 분기와 그 목록을 지웠습니다
모든 경로를 새 방식으로 열게 바꾸면 코드가 복잡해지고, 무엇보다 되돌릴 근거가 사라집니다. 기존 경로를 기본으로 두고 특정 에러에서만 갈라지게 하면 이번에 바뀐 범위가 에러 하나로 좁혀집니다. 문제가 생겼을 때 어디를 보면 되는지가 그 자리에 적혀 있는 셈입니다.
테스트를 고치기 전 코드에 대고도 돌려 봤습니다
회귀 테스트는 한도를 넘는 경로를 실제로 만들어서 읽어 보내는지 확인하게 썼습니다. 그리고 그 테스트를 **고치기 전 코드에 대고 돌려 떨어지는 것**을 확인했습니다. 옛 코드에서도 통과하는 테스트는 그 수정이 무엇을 바꿨는지 증명하지 못합니다.
이 확인이 필요한 이유는 검사를 고를 때 사람이 가장 쉽게 빠지는 함정 때문입니다. 통과하는 것을 보면 안심하는데, 실패할 수 없는 검사는 통과해도 아무것도 말해 주지 않습니다. 먼저 물어야 할 것은 이것이 아니라면 무엇이 보일까입니다. 셸 회귀 항목과 옛 실패 파일이 남아 있어도 알림이 안 간다는 항목까지 붙여 테스트 878건이 통과했습니다. 실데이터 드라이런에서는 전날 읽기 실패였던 그 파일이 신규로 잡혔고 읽기 실패가 0 이었습니다. 예열 스크립트는 한 배치를 3.1초에 실패 없이 끝냈습니다.
같은 파이프라인의 색인 쪽을 손본 기록도 따로 남겨 뒀습니다. 수집에서 읽은 본문이 검색까지 가는 경로를 바꾼 이야기입니다.
알림을 고친 게 아니라 지웠습니다
마지막으로 스케줄러에서 '파일 이름을 줄여 주세요' 알림을 **지웠습니다.** 문구를 다듬거나 주기를 늘리지 않고 분기째로 없앴습니다. 사람에게 시킬 일이 아니었으니 알릴 것도 없습니다.
이 자리가 이번 사건에서 가장 오래 남는 부분이라고 봅니다. 못 하는 것으로 갈라 둔 항목은 매일 알림으로 사람을 길들입니다. 어차피 못 고치는 것이라는 라벨이 붙으면 알림이 반복될수록 배경 소음이 됩니다. 그 시점부터는 아무도 그 결론을 다시 검증하지 않습니다. 실행할 수 없는 처방을 요구하는 알림은 알림이 아니라 잡음입니다. 지우거나 고치거나 둘 중 하나입니다.
알림과 측정이 사람을 어떻게 움직이는지는 다른 축에서도 같았습니다. 무엇을 재게 해 두면 그 방향으로 비용이 흐릅니다.

같은 자리에서 다시 틀리지 않으려고 보는 것
의사결정 쪽에서 이 사건을 다시 쓸 수 있게 다섯 줄로 줄였습니다. 운영 중인 시스템에 못 한다로 갈라 둔 항목이 있으면 이 순서로 봅니다.
그 결론이 이 플랫폼이 못 한다인지, 내가 부른 방식이 못 한다인지 문장으로 구분해 적어 봅니다
같은 대상을 다루는 다른 창구가 이미 돌고 있는지 찾습니다. 하나라도 닿고 있으면 그게 반증입니다
처방이 우리 권한 안에 있는지 확인합니다. 남에게 시키는 처방은 영원히 실행되지 않습니다
실행할 수 없는 처방을 요구하는 알림은 지웁니다. 남겨 두면 다른 알림까지 같이 무시됩니다
고친 뒤 테스트는 고치기 전 코드에 대고도 돌려 떨어지는지 봅니다
남은 것도 적어 둡니다. 같은 수집 실행에서 'Resource deadlock avoided' 라는 다른 에러로 실패한 파일 2건이 있습니다. 어떤 보안 제품 형식이고 원인이 다릅니다. 이번 처방으로는 풀리지 않고 드라이런에서는 재현되지 않아, 반복되면 다시 봅니다. 한 번 뒤집혔다고 나머지도 같은 이유일 거라고 넘겨짚지 않습니다.
마무리
이 처방이 어디서나 통하는 것은 아닙니다. 디렉터리 경로 자체가 한도를 넘으면 그 폴더를 한 번에 열 수 없고, 중간 디렉터리를 여러 단계로 나눠 내려가야 합니다. 여기서는 한 단계로 충분했을 뿐입니다. 쓰는 라이브러리가 경로 문자열만 받는 API 로 되어 있으면 이 우회가 적용되지 않고, 그때는 호출 계층을 바꾸는 비용이 따로 듭니다. 긴 경로를 다루는 방식은 OS 마다 다릅니다. 한도는 넘기는 문자열의 길이라는 관찰이 보편이고 이 구현이 보편은 아닙니다.
되물음에 대해서도 하나 적어 둡니다. 저를 다시 보게 만든 말은 답이 아니었습니다. 못 줄인다는 말 자체는 해결책을 주지 않았고, 다시 재 보라는 신호였습니다. 반문이 나오면 내 결론을 변호하는 대신 그 자리부터 다시 재 보는 것으로 충분합니다. 이번에는 그 비용이 3분이었고, 그 전까지 매일 나가던 알림은 하루치가 쌓였습니다.