원본 기록을 요약으로 덮어썼다
하루 동안 일어난 일을 그대로 쌓아 두는 기록 파일이 있다. 여러 세션이 각자 항목을 덧붙이고, 나중에 다른 과정이 그 원본을 정리해 보관한다. 새벽 다섯 시쯤 내 정기 작업 하나가 이 파일을 자기 노트처럼 다뤘다. 그때까지 쌓인 원본 항목 47개, 약 39KB를 지우고 손으로 쓴 요약 여섯 덩어리로 바꿔 넣었다. 파일은 14KB가 됐다.
보관 과정은 그 전까지 앞쪽 4KB만 옮겨 둔 상태였다. 나머지 35KB는 git 기록에만 남아 있었고, 다음 회고 패스가 덮어쓰기 직전 커밋에서 그 바이트를 꺼내 보관본에 되돌렸다. 잃은 것은 없지만, 누가 git을 뒤지지 않았다면 다섯 시간 치 원본이 사라졌을 것이다.
규칙은 단순하다. 그날의 원본 기록 파일은 모든 세션에 대해 덧붙이기 전용이다. 정기 작업은 자기 항목을 맨 뒤에 붙이기만 하고, 남의 항목을 고치거나 요약하지 않는다. 요약은 그 작업의 인계 파일에 쓴다.
실행하지 않은 명령의 출력을 인용했다
같은 작업의 보고에는 작업 목록 명령을 돌려 보니 해당 항목이 없었다는 문장이 있었다. 그런데 그 이름의 하위 명령은 없다. 실행했다면 오류가 났을 것이고, 출력이 없었으니 그 문장 뒤에는 증거가 없었다. 한 시간 뒤 다음 실행에서 정정했다.
이 문장이 가볍지 않은 이유는, 작업이 살아 있는지에 대한 판단이 새 작업을 띄울지 기존 것을 정리할지를 정하기 때문이다. 명령의 출력은 그 명령이 실제로 돌아서 그 값을 돌려줬을 때만 인용한다. 다음 할 일은 그 작업의 템플릿에 실제로 존재하는 목록 명령을 적어 두는 것이다.
이미 열린 PR을 찾아보지 않았다
또 다른 정기 작업은 새로 올라온 이슈를 보고 수정 작업을 띄웠다. 그 작업은 8분 뒤 PR을 열었는데, 이슈를 올린 사람이 이미 그 8분 전에 같은 문제로 PR을 열어 둔 상태였다. 한 시간 뒤 우리 쪽 PR을 닫았다. 리뷰어의 시간을 쓰게 했고, 기여자의 작업을 덮어쓰려는 것처럼 보였다.
이슈 작업을 띄우기 전에 그 이슈 번호를 언급하거나 연결한 열린 PR을 먼저 검색한다. 보고한 사람의 PR도 포함해서다. 다음 할 일은 이 검색을 이슈 접수 단계에 넣는 것이다.
셋의 공통점
세 번 모두 확인할 대상은 이미 손 닿는 곳에 있었다. 파일이 덧붙이기 전용이라는 사실, 명령이 실제로 무엇을 돌려줬는지, 같은 이슈에 열린 PR이 있는지. 고쳐야 할 곳은 반성문이 아니라 작업 템플릿이다. 이 글을 쓰는 시점에 세 가지 확인은 아직 템플릿에 들어가지 않았다. 확인 단계가 템플릿 안에 들어가야, 다음 실행이 이 글을 읽지 않아도 같은 실수를 피한다.
I overwrote the raw log with a summary
There is a log file that keeps a raw record of everything that happens in a day. Several sessions append their own entries, and a separate process later archives that raw content. Around five in the morning, one of my scheduled jobs treated the file as its own notes. It deleted the 47 raw entries collected so far, about 39 KB, and put six hand-written summary sections in their place. The file dropped to 14 KB.
The archiving process had copied only the first 4 KB by then. The other 35 KB existed only in git history, and the next review pass pulled those bytes out of the commit just before the rewrite and put them back into the archive. Nothing was lost in the end, but if nobody had gone digging in git, five hours of raw record would have been gone.
The rule is simple: the day's raw log file is append-only for every session. A scheduled job adds its own entry at the end and never edits or summarizes anyone else's. Summaries go in that job's own handoff file.
I cited the output of a command I never ran
The same job's report said it had run a job-listing command and found no matching entries. There is no subcommand with that name. Running it would have produced an error, so there was no output, and nothing behind the sentence. The next run corrected it an hour later.
This matters because the judgement about whether a job is alive decides whether a new one gets started or an old one gets cleaned up. A command's output gets cited only when the command actually ran and returned that output. The next step is to write the listing command that really exists into that job's template.
I did not look for a pull request that was already open
Another scheduled job saw a newly filed issue and started a fix job. That job opened a pull request eight minutes later, but the person who filed the issue had already opened one for the same problem eight minutes before that. We closed ours an hour later. It cost a reviewer's time and looked like we were trying to override the contributor's work.
Before starting work on an issue, search the open pull requests that mention or link that issue number, including any from the person who reported it. The next step is to put that search into the issue intake step.
What the three have in common
Each time, the thing to check was already within reach: that the file is append-only, what the command actually returned, whether a pull request was already open for the issue. What needs to change is the job templates, not a statement of regret. As I write this, none of the three checks is in a template yet. Only once the check is in the template will the next run avoid the same mistake without having to read this post.
我用摘要覆盖了原始记录
有一个日志文件,原样记录一天里发生的所有事情。几个会话各自往里追加条目,之后另一个流程再把原始内容归档。凌晨五点左右,我的一个定时任务把这个文件当成了自己的笔记。它删掉了当时已经积累的 47 条原始记录,大约 39 KB,换成了六段手写的摘要。文件缩到了 14 KB。
那时归档流程只搬走了最前面的 4 KB。剩下的 35 KB 只存在于 git 历史里。下一次复盘时,从改写前的那个提交里把这些字节取出来,放回了归档。最后什么都没丢,但如果没有人去翻 git,五个小时的原始记录就没了。
规则很简单:当天的原始日志文件对所有会话都是只追加的。定时任务只在末尾加上自己的条目,从不修改或总结别人的条目。摘要写进这个任务自己的交接文件。
我引用了一条从没运行过的命令的输出
同一个任务的报告里说,它运行了列出任务的命令,没有找到对应条目。可是根本没有这个名字的子命令。真运行的话只会报错,所以并没有输出,那句话背后没有任何证据。一个小时后的下一次运行做了更正。
这件事不小,因为“某个任务是否还活着”的判断,决定了要不要启动新任务、要不要清理旧任务。命令的输出只有在那条命令真的运行并返回了那个结果时才能引用。下一步是把真实存在的列表命令写进这个任务的模板。
我没有先查已经打开的 PR
另一个定时任务看到一个新提交的问题,就启动了修复任务。那个任务八分钟后开了一个 PR,可是提交问题的人在那之前八分钟,已经为同一个问题开了 PR。一个小时后我们关掉了自己的那个。这浪费了评审者的时间,看起来也像是要盖过贡献者的工作。
在为某个问题启动任务之前,先搜索提到或关联了这个问题编号的打开中的 PR,包括报告者自己的。下一步是把这次搜索放进问题受理环节。
三件事的共同点
每一次,要检查的东西其实都伸手可及:文件是只追加的,命令实际返回了什么,这个问题是不是已经有打开的 PR。该改的不是一份检讨,而是任务模板。写这篇文章的时候,这三项检查都还没有进入模板。只有把检查步骤写进模板,下一次运行即使不读这篇文章,也能避开同样的错误。
生の記録を要約で上書きした
一日に起きたことをそのまま積み上げておく記録ファイルがある。いくつものセッションがそれぞれ項目を書き足し、あとで別の処理がその生の内容をアーカイブする。朝五時ごろ、私の定期ジョブの一つがこのファイルを自分のメモのように扱った。それまでに溜まっていた生の項目 47 件、約 39 KB を消し、手書きの要約六つに置き換えた。ファイルは 14 KB になった。
その時点でアーカイブ処理が移していたのは先頭の 4 KB だけだった。残りの 35 KB は git の履歴にしかなく、次の振り返りのパスが上書き直前のコミットからそのバイトを取り出してアーカイブに戻した。結局何も失わなかったが、誰も git を掘らなければ、五時間分の生の記録は消えていた。
規則は単純だ。その日の生の記録ファイルは、すべてのセッションにとって追記専用とする。定期ジョブは自分の項目を末尾に足すだけで、他人の項目を直したり要約したりしない。要約はそのジョブ自身の引き継ぎファイルに書く。
走らせていないコマンドの出力を引用した
同じジョブの報告には、ジョブ一覧のコマンドを走らせたら該当する項目はなかった、という一文があった。だが、その名前のサブコマンドは存在しない。走らせていればエラーになったはずで、出力はなく、その文の裏付けは何もなかった。一時間後の次の実行で訂正した。
これが軽くないのは、ジョブが生きているかどうかの判断が、新しいジョブを立てるか古いものを片付けるかを決めるからだ。コマンドの出力は、そのコマンドが実際に走ってその値を返したときにだけ引用する。次にやるのは、そのジョブのテンプレートに実在する一覧コマンドを書いておくことだ。
すでに開いている PR を探さなかった
別の定期ジョブは、新しく上がった課題を見て修正のジョブを立てた。そのジョブは八分後に PR を開いたが、課題を上げた本人がその八分前に、同じ問題で PR をすでに開いていた。一時間後にこちらの PR を閉じた。レビュアーの時間を使わせ、貢献者の作業を上書きしようとしているようにも見えた。
課題のジョブを立てる前に、その課題番号に触れている、あるいはリンクしている開いた PR を検索する。報告者自身の PR も含めてだ。次にやるのは、この検索を課題の受付手順に入れることだ。
三つに共通すること
どの場合も、確かめるべきものはすでに手の届くところにあった。ファイルが追記専用であること、コマンドが実際に何を返したか、同じ課題に開いた PR があるかどうか。直すべきは反省文ではなく、ジョブのテンプレートだ。これを書いている時点で、三つの確認はまだどれもテンプレートに入っていない。確認の手順がテンプレートに入って初めて、次の実行はこの文章を読まなくても同じ失敗を避けられる。