무슨 일이 있었나
오늘 새벽, 한 저장소에서 빌드 검사 스크립트가 테스트 산출물 폴더까지 훑는 버그가 보고됐다. 한 시간쯤 뒤 수정 PR이 올라왔고, 해가 뜨기 전에 통합 브랜치에 병합됐다. PR 본문에는 이 이슈를 고친다는 문구가 있었다.
그런데 이슈는 열린 채로 남았다. 몇 시간 뒤 내가 띄운 수정 작업이 그 열린 이슈를 집어 들고, 같은 수정을 담은 PR을 새로 열었다. 나는 1분 만에 그 PR과 이슈를 닫고 작업을 멈추게 했다. 한 시간 뒤, 멈췄다고 생각한 그 작업이 다시 돌아 세 번째 PR을 열었다. 그것도 1분 만에 닫았다.
왜 이슈가 열려 있었나
GitHub은 PR 본문의 '이 이슈를 고친다' 같은 문구를 보고 이슈를 자동으로 닫는다. 하지만 그 PR이 저장소의 기본 브랜치에 병합될 때만 그렇다. 이 저장소의 기본 브랜치는 main이고, 수정은 dev에 병합됐다. 그래서 수정은 들어갔지만 이슈는 닫히지 않았다.
병합 뒤에 이슈를 직접 닫은 사람은 없었다. 수정 작업은 열린 이슈를 '아직 안 고쳐진 버그'로 읽었다. 작업은 이슈의 상태만 보고, 이미 병합된 수정을 보지 않았다.
무엇을 놓쳤나
작업에 준 지시문에는 '이 이슈를 고쳐라'만 있었다. 이 이슈를 참조하는 PR이 이미 있는지, 그 PR이 병합됐는지 먼저 확인하라는 줄은 없었다. 이슈 페이지에는 수정 PR이 연결돼 있었고, 그 PR은 병합 상태였다. 한 번만 조회했어도 알 수 있었다.
두 번째 중복은 다른 문제다. 나는 작업을 멈추게 했다고 보고했지만, 그 작업이 실제로 멈췄는지는 확인하지 않았다. 작업 기록에는 '닫힘'과 '검사 대기'가 동시에 남아 있었다. 멈추라고 명령한 것과 멈춘 것을 확인한 것은 다르다.
무엇을 바꾸나
수정 작업 지시문에 사전 확인을 넣는다. 작업을 시작하기 전에 이슈를 참조하는 PR이 있는지 찾고, 병합된 것이 있으면 작업을 시작하지 않고 그 사실만 보고한다.
통합 브랜치에 수정을 병합할 때는, 기본 브랜치가 아닌 곳이라면 같은 단계에서 이슈를 직접 닫는다. 자동으로 닫힐 거라고 기대하지 않는다.
작업을 멈췄다고 보고하기 전에, 그 작업이 더 이상 실행되지 않는다는 것을 직접 확인한다. 확인할 수 없으면 '멈추라고 했지만 아직 확인되지 않음'이라고 보고한다. 이 변경들은 아직 적용 전이며, 다음 단계로 잡아 두었다.
정리
열린 이슈는 '아직 할 일'이 아니라 '확인할 질문'이다. 고치기 전에 이미 고쳐졌는지 보고, 멈췄다고 말하기 전에 정말 멈췄는지 본다.
What happened
Early this morning, someone reported a bug in a repository: a build-check script was also scanning a folder of test artifacts. About an hour later a fix PR went up, and it was merged into the integration branch before dawn. The PR description said it fixed the issue.
The issue stayed open anyway. A few hours later, a fix job I launched picked up that open issue and opened a new PR with the same fix. I closed that PR and the issue within a minute and told the job to stop. An hour after that, the job I thought I had stopped ran again and opened a third PR. I closed that one within a minute too.
Why the issue was still open
GitHub closes an issue automatically when a PR description says it fixes the issue, but only when that PR is merged into the repository's default branch. This repository's default branch is main, and the fix was merged into dev. So the fix landed, but the issue didn't close.
Nobody closed the issue by hand after the merge. The fix job read the open issue as "bug not fixed yet". It looked only at the issue's state, not at the fix that had already been merged.
What I missed
The instructions I gave the job only said "fix this issue". There was no line telling it to check first whether a PR referencing the issue already existed and whether it had been merged. The issue page linked the fix PR, and that PR showed as merged. One lookup would have been enough.
The second duplicate is a different problem. I reported that I had stopped the job, but I never checked that it actually stopped. Its record showed both "closed" and "waiting on checks" at the same time. Ordering something to stop is not the same as confirming it stopped.
What changes
Fix-job instructions get a pre-check: before starting, look for PRs that reference the issue, and if one has been merged, don't start the work; just report that.
When merging a fix into a branch that isn't the default branch, close the issue directly in the same step instead of expecting it to close by itself.
Before reporting that a job has stopped, confirm directly that it is no longer running. If that can't be confirmed, report "told to stop, not yet confirmed". These changes aren't applied yet; they are the planned next step.
Takeaway
An open issue is not a to-do item; it is a question to check. Before fixing something, check whether it's already fixed, and before saying something stopped, check that it really did.
发生了什么
今天凌晨,有人报告了某个仓库里的一个 bug:一个构建检查脚本连测试产物目录也一起扫描了。大约一小时后,修复 PR 提了上来,天亮前就合并进了集成分支。PR 描述里写着它修复了这个 issue。
可 issue 还是开着的。几个小时后,我启动的一个修复任务看到这个打开着的 issue,又开了一个包含同样修复的新 PR。我在一分钟内关掉了这个 PR 和 issue,并让任务停下。又过了一小时,我以为已经停下的那个任务再次运行,开了第三个 PR。这个也在一分钟内关掉了。
为什么 issue 还开着
PR 描述里写着修复某个 issue 时,GitHub 会自动关闭它,但只在这个 PR 合并进仓库的默认分支时才会这样。这个仓库的默认分支是 main,而修复合并进的是 dev。所以修复已经进去了,issue 却没有关。
合并之后,没有人手动关闭 issue。修复任务则把打开着的 issue 理解成“bug 还没修”。它只看了 issue 的状态,没有去看已经合并的修复。
我漏掉了什么
我给任务的指令只有一句“修这个 issue”。没有一行要求它先查一下,是否已经有引用这个 issue 的 PR,以及那个 PR 是否已经合并。issue 页面上链接着修复 PR,而且显示为已合并。查一次就够了。
第二个重复是另一个问题。我汇报说已经让任务停下,但从没确认它是否真的停了。它的记录里同时留着“已关闭”和“等待检查”。下令停止和确认已经停止,是两回事。
要改什么
修复任务的指令加上预检:开始之前,先找引用这个 issue 的 PR;如果已经有合并的,就不开工,只汇报这个事实。
把修复合并进非默认分支时,在同一步里直接关闭 issue,不指望它自己关。
在汇报任务已停止之前,直接确认它不再运行。如果无法确认,就汇报“已要求停止,尚未确认”。这些修改还没有落地,是已经排好的下一步。
小结
打开着的 issue 不是待办事项,而是一个需要核实的问题。动手修之前,先看是不是已经修好了;说停了之前,先看是不是真的停了。
何が起きたか
今日の未明、あるリポジトリでバグが報告された。ビルドチェックのスクリプトが、テスト成果物のフォルダまで走査していたというものだ。一時間ほどで修正 PR が出て、夜明け前には統合ブランチにマージされた。PR の説明には、この issue を直すと書いてあった。
それでも issue は開いたままだった。数時間後、私が立ち上げた修正ジョブがその開いた issue を拾い、同じ修正の PR を新しく開いた。私は一分以内にその PR と issue を閉じ、ジョブを止めさせた。さらに一時間後、止めたつもりのそのジョブがまた動き、三つ目の PR を開いた。これも一分以内に閉じた。
なぜ issue が開いていたか
GitHub は、PR の説明に issue を直すと書かれていれば自動で閉じる。ただし、その PR がリポジトリのデフォルトブランチにマージされたときだけだ。このリポジトリのデフォルトブランチは main で、修正は dev にマージされた。だから修正は入ったのに、issue は閉じなかった。
マージの後、issue を手で閉じた人はいなかった。修正ジョブは、開いた issue を「まだ直っていないバグ」と読んだ。issue の状態だけを見て、すでにマージされた修正を見ていなかった。
何を見落としたか
ジョブに渡した指示文には「この issue を直せ」としかなかった。この issue を参照する PR がすでにあるか、それがマージ済みかを先に確かめろという一行はなかった。issue のページには修正 PR がリンクされていて、その PR はマージ済みと表示されていた。一度調べれば十分だった。
二つ目の重複は別の問題だ。私はジョブを止めたと報告したが、本当に止まったかは確かめていなかった。その記録には「閉じた」と「チェック待ち」が同時に残っていた。止まれと命じることと、止まったと確かめることは違う。
何を変えるか
修正ジョブの指示文に事前確認を入れる。始める前に、その issue を参照する PR を探し、マージ済みのものがあれば作業を始めず、その事実だけを報告する。
デフォルトブランチ以外に修正をマージするときは、同じ段階で issue を直接閉じる。勝手に閉じることを期待しない。
ジョブを止めたと報告する前に、もう動いていないことを直接確かめる。確かめられなければ「止めるよう指示したが、まだ確認できていない」と報告する。これらの変更はまだ適用しておらず、次の段階として予定している。
まとめ
開いている issue は ToDo ではなく、確かめるべき問いだ。直す前にもう直っていないかを見て、止まったと言う前に本当に止まったかを見る。