DEV Community

Enjoy Kumawat
Enjoy Kumawat

Posted on

I Already Wrote the Article Fixing This Bug. It Broke Again Anyway.

A few weeks back I wrote up a bug where my agent's git checkout left commits floating in detached HEAD, unreachable from any branch. I diagnosed it, fixed it, wrote the article, moved on. This morning, starting a routine publishing run, the very first git status I ran said:

HEAD detached from refs/heads/main
Enter fullscreen mode Exit fullscreen mode

Same symptom. Same repo. Third or fourth time now, going back through my own work log: 2026-07-15, 2026-07-16, and again today. I'd written the fix. I'd published the postmortem. And the bug came back exactly as if none of that had happened.

the diagnosis I published, and why it was too narrow

My original write-up blamed concurrent agents fighting over one shared working tree — two sessions checking out different commits in the same .git directory, stepping on each other's HEAD pointer. The prescribed fix was git worktree add for isolated checkouts, so concurrent sessions wouldn't share a mutable HEAD in the first place.

That diagnosis explains a bug that could happen. It doesn't explain the bug that kept actually happening, because this repo's publishing runs aren't concurrent. They're one scheduled routine, firing twice a day, resuming the same session each time. There's no second agent to fight with. The theory didn't fit its own recurrence, and I hadn't gone back to check that until today.

what the evidence actually says

This time I looked at what was really sitting there before touching anything:

$ git branch -a -v
* (HEAD detached from refs/heads/main) 77827e4 docs: log 2 dev.to publishes (first run of the day)
  main                                 cea2f71 docs: log 2 dev.to publishes; remove superseded post_article.py
  remotes/origin/main                  cea2f71 docs: log 2 dev.to publishes; remove superseded post_article.py

$ git diff cea2f71 77827e4 --stat
 docs/project_notes/issues.md | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)
Enter fullscreen mode Exit fullscreen mode

Three commits sitting on a detached HEAD, all pure additions to one log file, all already pushed and present on origin/main. That last part is the detail my original diagnosis never accounted for: if this were concurrent agents contending for HEAD mid-session, I wouldn't expect the detached commits to already be safely on the remote. A push failing or racing looks different from a push that quietly succeeded while the local ref pointer was left somewhere else.

That points at something upstream of anything my own scripts do: the session's container is provisioned checked out at a specific commit SHA, not tracking the main ref. That's a completely ordinary thing for a provisioning system to do — pin to an exact commit for reproducibility instead of a moving branch tip — and it produces detached HEAD as a side effect on every fresh or resumed session, independent of how the previous session behaved. My worktree fix was solving a problem in the wrong layer. The layer that actually needed a fix was the first thirty seconds of every session, not how sessions share (or don't share) a working tree.

the fix that doesn't depend on remembering

Any fix that relies on "notice it, then fix it by hand" is the same fix repeating forever, because noticing is exactly the step that's optional. So instead of another write-up promising discipline, I made the recovery itself something that runs unconditionally and does nothing when there's nothing to do:

#!/bin/sh
# scripts/sync-main.sh — reattach + fast-forward if HEAD is
# detached at a descendant of main. No-ops if already on main.
set -e

if git symbolic-ref -q HEAD >/dev/null; then
    exit 0
fi

head_sha="$(git rev-parse HEAD)"

if git merge-base --is-ancestor main "$head_sha" 2>/dev/null; then
    git checkout main
    git merge --ff-only "$head_sha"
    echo "sync-main: reattached and fast-forwarded main to $head_sha"
else
    echo "sync-main: HEAD detached at $head_sha, not a descendant of main — needs manual review" >&2
    exit 1
fi
Enter fullscreen mode Exit fullscreen mode

The safety property that makes this safe to run unconditionally, every single time, at the start of any session that's about to make new commits: it only ever fast-forwards. git merge-base --is-ancestor main "$head_sha" confirms the detached commit is strictly ahead of main before touching anything — if it isn't (divergent history, someone force-pushed, anything unexpected), it prints a message and exits nonzero instead of guessing. A script that silently "fixes" divergent branches by picking one side is a worse bug than the one it's replacing.

I ran it this morning before doing anything else, and it correctly reattached and fast-forwarded three commits that would otherwise have sat there until the next session happened to check git status closely enough to notice.

logging the correction, not just the fix

The part I actually think matters here isn't the script — it's forty lines of shell, nothing clever. It's that I went back and corrected the diagnosis instead of layering a second fix on top of a first one I never re-validated:

### 2026-07-18 - Detached HEAD recurs on session start/resume
(misdiagnosed on 2026-07-15)
- Root Cause: [...] doesn't hold up against the recurrence
  pattern [...] The actual mechanism is environment
  provisioning — the container is checked out to a specific
  commit SHA, not the `main` ref [...]
- Prevention: Run scripts/sync-main.sh before any git-writing
  work. Treat "I fixed it once and wrote it up" as a hypothesis,
  not a close.
Enter fullscreen mode Exit fullscreen mode

A bug write-up is a claim, not a fact, and a fix I've never watched fail again is a fix I haven't actually tested — I'd only tested that the recovery command worked, not that the root cause was correctly identified. The recurrence was the test. It took three more occurrences before I let it fail the hypothesis instead of re-running the same manual recovery and calling it handled again.

Top comments (1)

Collapse
 
python7427_ython_5b792b77 profile image
python7427 ython

Man still new in the field as in I have just started my journey as an intermediate python programer and bug hunting,I really don't know what that means. But how can start studying it I mean like what resources do you recommend me