사람인 이력서 자동 입력이 저장 직전에 멈춘 이유는 무엇인가?
사람인 이력서 특수문자 가운데 화살표(→ ⇒ ➔ ▶)는 입력창이 입력하는 순간 지워 버립니다. 그래서 자동 입력 스크립트가 소개글을 넣고 다시 읽어 보면 한 글자가 모자라고, 저장 전 대조 단계가 거기서 멈춥니다. 2026년 9월 11일에 이 문제를 찾아 고쳤고, 같은 날 두 가지 문제가 더 나왔습니다.
스크립트 이름은 resume-platform-sync.py 이고, 이력서 내용을 사람인·잡코리아·원티드 세 채용 사이트에 같은 값으로 맞춰 두는 일을 합니다. 입력만 하고 끝내지 않습니다. 저장 버튼을 누르기 전에 입력칸의 값을 다시 읽어 원문과 비교하고, 하나라도 다르면 저장하지 않고 멈춥니다. 틀린 내용이 저장 버튼 뒤로 넘어가지 않게 하는 검문소입니다.
이 글은 그 검문소가 한 번 멈춘 일에서 시작합니다. 멈춤은 장치의 고장이 아니라 장치가 제 일을 한 결과였고, 원인은 입력칸 쪽에 있었습니다. 같은 날 확인한 문제는 모두 세 가지입니다.
- 기호 삭제: → ⇒ ➔ ▶ 네 가지가 입력 즉시 사라지고, 소개글이 382자에서 381자로 읽혀 저장 전에 멈췄다
- 작성완료 버튼: 동기 JS 클릭은 죽고, Playwright 실클릭은 30초 타임아웃이 났다
- 핵심역량 5줄 소실: 17:55 에는 있던 줄이 18:03 에 서버에서 사라졌고, 원인은 아직 모른다
앞의 두 가지는 원인을 찾아 고쳤고, 세 번째는 고치지 못했습니다. 세 번째를 숨기지 않고 끝에서 따로 다룹니다.
이 글은 숫자의 이동을 따라갑니다. 382자가 381자가 된 1자, 30초를 기다리다 끝난 클릭, 50밀리초로 풀린 클릭, 17:55 에 5줄이던 값이 18:03 에 0줄이 된 일이 그것입니다. 숫자마다 어떻게 쟀는지를 함께 적었고, 재지 못한 값은 재지 못했다고 적었습니다.
채용 사이트 입력칸에 자동으로 값을 넣어 본 적이 없는 독자도 읽을 수 있게 썼습니다. 브라우저 자동화 도구는 사람이 마우스로 하는 일을 코드로 대신해 주는 프로그램이고, 입력칸에 글을 넣고 버튼을 누르는 일이 그 기본 동작입니다. 문제는 이 기본 동작이 늘 사람이 한 것과 같은 결과를 내지는 않는다는 데 있었습니다.
처음 잰 숫자는 382자와 381자였다
재는 방법은 단순합니다. 스크립트가 소개글을 입력칸에 넣고, 입력칸의 값을 다시 읽어 글자 수를 비교합니다. 이때 넣은 글은 382자였고 다시 읽은 값은 381자였습니다. 차이는 1자이고, 이 차이 때문에 저장 전에 멈췄으니 저장은 0회였습니다.

측정 절차를 순서대로 적으면 이렇습니다. 입력과 비교를 모두 스크립트가 하기 때문에 사람의 눈은 한 번도 들어가지 않습니다.
- 원문 소개글의 글자 수를 센다 — 382자
- 입력칸에 값을 넣는다
- 입력칸의 값을 다시 읽어 글자 수를 센다 — 381자
- 두 값이 같으면 저장 단계로 가고, 다르면 저장 없이 멈춘다 — 이번에는 멈춤, 저장 0회
1자는 작은 숫자라 처음에는 글자가 하나 잘렸거나 줄바꿈 하나를 다르게 센 정도로 보입니다. 이 글에서 가장 조심한 대목이 여기입니다. 대조 단계가 멈췄다는 사실만으로는 어느 쪽이 틀렸는지 알 수 없습니다. 입력이 틀렸는지, 읽는 쪽이 틀렸는지, 입력창이 값을 바꿨는지 세 경우가 모두 같은 숫자를 냅니다.
처음 세운 가설: 글자를 세는 쪽이나 입력 쪽이 틀렸을 것
기록에는 당시 어떤 가설을 먼저 세웠는지 남아 있지 않습니다. 그래서 여기서는 이런 상황에서 흔히 의심하는 후보 둘을 놓고, 이 글이 증거로 가려낸 순서대로 적습니다. 당시의 순서였다고 주장하지 않습니다.
- 가설 하나: 다시 읽는 코드가 글자 수를 세는 방식이 틀렸다. 줄바꿈이나 공백을 다르게 세면 1자쯤은 쉽게 어긋난다
- 가설 둘: 입력이 끝까지 가지 못했다. 마지막 글자가 빠졌다면 381자로 읽힌다
가설을 둘로 좁힌 이유도 적어 둡니다. 숫자가 말해 주는 사실은 하나뿐입니다. 쓴 값과 읽은 값이 다르다는 것이고, 어디서 달라졌는지는 숫자에 들어 있지 않습니다. 그래서 값이 지나가는 길을 입력 전, 입력칸, 읽기 후 세 곳으로 나누고 각 곳에서 바뀔 수 있는 경우를 하나씩 따져야 합니다. 입력 전의 값은 원문 그대로였으니 남는 곳은 입력칸과 읽기였습니다.
두 가설 모두 우리가 만든 코드 쪽을 의심합니다. 입력창은 시키는 대로 받아 적는 곳이라는 전제가 깔려 있습니다. 이 전제가 틀렸다는 단서가 숫자 안에 이미 있었습니다. 382자에서 정확히 1자가 빠졌고, 소개글에는 화살표 하나가 들어 있었다는 계산과 맞아떨어집니다.
가설이 틀린 지점은 대조 코드가 아니라 입력창이었다
결정적인 증거는 글자가 빠진 모양이었습니다. 입력창에 `A → B` 를 넣으면 `A B` 로 읽힙니다. 화살표만 사라지고 앞뒤 공백 두 칸은 그대로 남았습니다. 끝이 잘렸다면 뒤쪽이 통째로 없어져야 하니 가설 둘과 모양이 다릅니다. 줄바꿈을 잘못 센 경우라면 화살표 자리가 비는 일이 없으니 가설 하나와도 맞지 않습니다.
이 모양을 보고서야 입력창이 입력하는 순간 기호를 지운다는 가설로 넘어갔습니다. 대조 코드는 틀리지 않았습니다. 입력값이 정말 381자였기 때문에 정확히 맞게 읽었고, 그래서 정확히 멈췄습니다.
두 가설을 가려낸 질문은 이랬습니다. 읽는 쪽이 틀렸다면 입력칸에 무엇이 들어 있어도 같은 방식으로 틀립니다. 그런데 화살표가 없는 문장은 아무 문제 없이 같은 숫자로 읽혔습니다. 같은 읽기 코드가 어떤 글에서는 맞고 어떤 글에서는 틀린다면, 코드보다 글 안의 무엇이 원인입니다. 소개글 안에서 다른 문장과 달랐던 것은 화살표였습니다.
뒤늦게 풀린 의문도 하나 있습니다. 2026년 8월 24일에 사람이 손으로 입력한 판은 `80억 → 500억` 을 `80억에서 500억으로` 로 풀어 쓰고 있었습니다. 당시에는 문장을 매끄럽게 하려는 선택으로 보였습니다. 이제 보니 화살표가 지워지는 입력칸에 이미 한 번 데어 본 사람의 우회였습니다. 그 이유가 어디에도 적혀 있지 않았기 때문에 같은 문제를 스크립트가 처음부터 다시 겪었습니다.
진짜 원인은 무엇인가? 입력창이 지우는 기호와 남기는 기호
원인은 사람인 입력칸이 특정 기호를 입력하는 즉시 지운다는 점입니다. 저장할 때가 아니라 입력할 때 지웁니다. 확인한 기호 여덟 개 가운데 → ⇒ ➔ ▶ 네 개는 사라지고, `->` `>` `>` `〉` 네 개는 남았습니다.

지워지는 기호는 모두 방향을 가리키는 화살표 모양이고, 남는 기호는 ASCII 로 쓰는 `->` 와 부등호 계열입니다. 왜 이 기호만 골라서 지우는지는 알 수 없습니다. 이 글이 확인한 범위는 어떤 기호가 지워지는지까지이고, 입력칸 안에서 무슨 규칙이 돌아가는지는 들여다보지 못했습니다.
입력 시점에 지운다는 점이 중요합니다. 저장하고 난 뒤 서버가 지웠다면 저장 화면을 다시 열어야 알 수 있습니다. 입력창이 그 자리에서 지우기 때문에, 입력 직후에 다시 읽는 방법만으로 바로 드러납니다. 이 글의 대조 단계가 이 문제를 저장 전에 잡을 수 있었던 이유입니다.
소개글 하나만 어긋났는데도 이렇게 잘 드러난 데에는 우연이 섞여 있다는 점도 짚어 둡니다. 저장 전에 입력값을 다시 읽는 장치가 없었다면 화살표가 빠진 이력서가 그대로 저장됐을 것입니다. `80억 → 500억` 이 `80억 500억` 으로 올라가 있어도 화면만 훑는 사람은 알아채기 어렵습니다.
조치: saramin_safe() 가 화살표를 ->로 바꾼다
고친 방법은 입력 직전에 한 번 거르는 함수입니다. saramin_safe() 가 소개글과 모든 입력값에서 `→` 를 `->` 로 바꿉니다. `->` 는 입력칸이 지우지 않는 기호이므로 글자 수가 맞고, 읽는 사람에게도 방향이 그대로 전달됩니다.
사람이 쓴 판처럼 문장을 풀어 쓰는 방법도 있습니다. 다만 그러려면 화살표가 나올 때마다 어색하지 않은 문장을 새로 지어야 하고, 이 일은 스크립트에 맡기기 어렵습니다. 기호 하나를 다른 기호 하나로 바꾸는 쪽은 규칙이 단순해서 자동으로 돌려도 결과가 늘 같습니다. 이 선택의 이유는 이 글의 해석이며 기록에 남은 판단 근거는 아닙니다.
지워지는 기호는 네 개인데 기록에 있는 교정은 `→` 하나입니다. 나머지 ⇒ ➔ ▶ 를 같은 방식으로 바꾸는지는 이 기록에서 확인하지 못했습니다. 소개글에서 실제로 부딪힌 기호가 `→` 였기 때문에 그 하나를 먼저 막은 것으로 읽힙니다(추정: 기록에 선택 이유가 없음).
치환 규칙을 입력 직전 한 곳에 모아 둔 점은 따로 적어 둘 만합니다. 소개글이든 핵심역량이든 입력칸에 들어가는 값은 모두 이 함수를 지나갑니다. 기호가 지워지는 문제가 다른 입력칸에서 또 나와도 고칠 곳은 함수 한 곳입니다. 화면마다 따로 손보면 한 화면은 고치고 다른 화면은 빼먹는 일이 생깁니다.
작성완료 버튼은 왜 세 번째 방법에서야 눌렸는가?
입력과 대조가 끝나면 `작성완료` 버튼(`.evtResumeSave`)을 눌러야 합니다. 저장이 성공하면 `resume-complete/.../open_fl/n/mode/modify` 주소로 화면이 넘어갑니다. 이 이동이 첫 번째 방법을 죽였습니다.

첫 번째 방법인 동기 JS 클릭은 페이지 안에서 곧바로 버튼을 누릅니다. 저장이 성공해 화면이 이동하면 스크립트가 아직 결과를 돌려받지 못한 채 실행 컨텍스트가 사라지고, `Target page ... has been closed` 오류로 죽습니다. 저장은 됐는데 스크립트는 실패로 끝나는 모양입니다.
두 번째 방법인 Playwright 실클릭은 사람이 마우스로 누르듯 버튼을 찾아 누릅니다. 이 버튼은 뷰포트 밖에 놓여 30초를 기다리다 타임아웃이 났습니다. 스크롤을 먼저 해 보았는지는 기록에 없습니다.
이 대목을 읽을 때 한 가지를 구분해 두면 좋습니다. 저장이 됐는지와 스크립트가 성공했는지는 다른 질문입니다. 첫 번째 방법에서는 저장 요청이 서버로 갔을 수 있는데도 스크립트는 오류로 끝납니다. 그래서 스크립트의 종료 상태만 보고 저장 여부를 판단하면 틀립니다. 이번에 요청 기록을 직접 본 이유가 이것입니다.
세 번째 방법은 `setTimeout(() => b.click(), 50)` 으로 클릭을 50밀리초 뒤로 예약하는 방법입니다. 스크립트가 먼저 정상 종료되고, 그 뒤에 클릭이 실행됩니다. 요청 기록에 `POST /zf_user/member/resume-manage/save` 200 이 찍히고 이동으로 끝났습니다.
세 방법의 차이를 한 줄로 줄이면, 앞의 두 방법은 클릭이 일어나는 시점을 스크립트가 쥐고 있고 세 번째는 클릭을 브라우저에 맡깁니다. 저장이 성공하면 화면이 넘어가는 버튼은 클릭하는 쪽이 결과를 받을 수 없으니, 클릭이 끝나기 전에 스크립트가 먼저 빠져나와야 합니다. 50밀리초는 그 사이를 벌려 주는 값입니다.
작성완료가 요청을 아예 보내지 않는 다른 조건도 별도로 기록돼 있습니다. 같은 버튼이 안 눌리는 이유가 하나가 아니라는 뜻입니다.
재측정 결과: 요청 기록으로 확인한 두 가지
재측정은 화면 육안이 아니라 요청 기록으로 했습니다. 작성완료를 예약 클릭으로 바꾼 뒤 저장 요청 `POST /zf_user/member/resume-manage/save` 가 응답 코드 200 으로 끝나는지, 이어서 화면이 완료 주소로 이동하는지를 봤습니다. 둘 다 확인했습니다.

기호 쪽 재측정은 여기서 한계를 밝혀야 합니다. 치환 뒤에 소개글이 몇 자로 읽혔는지는 이 기록에 따로 남아 있지 않습니다. 확인한 범위는 교정 방법과 작성완료가 끝까지 도는 데까지이고, 382자가 382자로 읽혔다는 숫자는 기록하지 못했습니다. 한 번 재고 끝내지 않으려면 다음 실행 때 이 값을 남겨야 합니다.
배치 실행에서 보인 비슷한 오류도 분리해 둡니다. 사람인·잡코리아·원티드 세 곳이 모두 `TargetClosedError` 로 실패한 적이 있는데, 원인을 조사하지 않고 플랫폼마다 하나씩 다시 돌렸더니 세 곳 모두 `ok` 가 나왔습니다. 공유 브라우저의 타이밍 문제로 보이지만(추정: 사이트 차단은 아니라는 근거만 있고 원인 확인은 안 함) 이 글의 두 문제와는 따로 다룹니다.
스크립트가 이런 조용한 실패를 얼마나 일찍 잡는지는 크론 작업 모니터링, 14라운드 점검에도 결함이 안 줄던 이유에서 다른 각도로 다뤘습니다. 저장 전 대조는 같은 문제를 입력 한 건 단위에서 막는 장치입니다.
핵심역량 5줄은 왜 사라졌는가? 원인은 아직 모른다
세 번째 문제는 고치지 못했습니다. 같은 날 17:55 에는 사람인 `MY Career > 핵심역량` 에 5줄이 있었는데, 18:03 에는 서버에서 사라져 있었습니다. 편집 화면, 보기 화면, 목록 화면 세 곳 모두 비어 있었습니다.

가장 먼저 우리 스크립트가 지웠을 가능성을 의심했습니다. 이 가설은 로그로 반증됐습니다. 그 사이 이 스크립트의 저장은 0회였습니다. 저장한 적이 없으니 지울 수도 없었습니다. 그렇다면 누가 지웠는지는 열린 질문으로 남습니다.
사라지기 전 값은 17시 55분 무렵에 만든 점검용 백업 파일에 남아 있었습니다. 이 5줄을 `coo/career/platform-short.yml` 에 올려 스크립트가 관리하는 대상으로 만들고 복원했습니다. 관리 대상이 되면 값이 다시 사라졌을 때 다음 `--dry-run` 이 `핵심역량 N (0자 → …)` 로 보여 줍니다. 원인을 모르는 문제를 원인 없이 감시하는 방법입니다.
같은 문제를 더 일찍 잡으려면 무엇을 바꿔야 하는가?
이 이야기를 개선기로 묶는 이유도 밝힙니다. 개선 폭을 개선율로 말하기는 어렵고, 말할 수 있는 절대값은 382자 대 381자, 30초 대 요청 200, 5줄 대 0줄입니다. 그래서 비율 없이 이 값들만 적었습니다.
이 일에서 가장 값이 컸던 것은 저장 전에 값을 다시 읽어 대조하는 장치였습니다. 장치가 없었다면 1자 차이는 저장 뒤에도 발견되지 않았을 것입니다. 장치가 있었기 때문에 화살표가 지워진다는 사실이 이력서에 올라가기 전에 드러났습니다.
다음은 이 글의 제안이며 아직 실행한 일은 아닙니다. 첫째, 입력칸마다 쓰는 기호를 미리 전부 넣어 보고 다시 읽어 지워지는 기호 목록을 만든다. 둘째, 그 목록을 치환 함수 한 곳에 모은다. 셋째, 대조 결과가 어긋나면 숫자만 남기지 말고 어느 글자가 빠졌는지 위치까지 로그로 남긴다. 이번에도 `A B` 라는 공백 두 칸의 모양이 없었다면 원인을 가르기가 훨씬 길어졌을 것입니다.
자동 입력 스크립트를 처음 짤 때는 입력칸이 값을 그대로 받는다고 가정하기 쉽습니다. 이 일은 그 가정이 한 번 깨진 사례입니다. 가정을 깬 것은 사람의 눈이 아니라 1자의 차이였고, 그 1자를 놓치지 않은 쪽이 대조 단계였습니다.
남는 한계와 다음 점검
이 글이 푼 것은 두 가지이고 못 푼 것은 한 가지입니다. 푼 것의 증거도 완전하지 않아서 한계를 네 줄로 적어 둡니다.
- 핵심역량 5줄이 사라진 원인은 모릅니다. 스크립트가 아니라는 것만 확인했습니다. 다시 사라지면 다음 --dry-run 에서 0자로 드러납니다
- 화살표 치환 뒤 소개글이 몇 자로 읽히는지 값을 기록하지 못했습니다. 다음 실행에서 382자 대 382자를 남겨야 합니다
- 지워지는 기호는 여덟 개를 넣어 본 결과일 뿐입니다. 그 밖의 기호는 확인 필요입니다. 저장 전 대조가 계속 잡아 줍니다
- 작성완료 예약 클릭이 성공 경로에서 동작하는 것만 요청 기록으로 확인했습니다. 저장이 실패한 경우의 응답은 이 글에서 보지 못했습니다
이 글의 세 문제는 서로 다른 층에서 났습니다. 첫째는 입력칸이 값을 바꾼 문제이고 둘째는 버튼을 누르는 타이밍 문제이며 셋째는 서버에 있던 값이 사라진 문제입니다. 층이 다르면 고치는 곳도 다릅니다. 첫째는 입력 직전의 치환 함수, 둘째는 클릭 예약, 셋째는 값을 관리 대상 파일에 올려 두는 감시로 나뉘었습니다. 한 번에 하나씩 원인을 가르고 나서야 각자의 고칠 곳이 보였습니다.
가장 큰 교훈은 숫자 하나가 어긋났을 때 어느 쪽이 틀렸는지 가르는 증거를 먼저 모아야 한다는 점입니다. 382자와 381자는 같은 숫자인데, 공백 두 칸이 그대로 남은 모양을 본 뒤에야 의심이 코드에서 입력칸으로 넘어갔습니다. 채용 사이트나 사내 업무 화면에 같은 방식으로 값을 자동 입력하다 이런 조용한 어긋남을 겪고 있다면 상담에서 상황을 적어 주세요.