TL;DR
A host with git-lfs uninstalled but still declared in git config causes ox to read 130-byte LFS pointer files as if they were session transcripts. Summaries come out empty, validation fails, the daemon retries forever, and the resulting metadata churn produces an unresolvable rebase conflict that permanently blocks ledger sync.
ox doctor passed 76+ checks throughout and never once flagged the missing binary.
Five distinct defects below. #1 and #3 are the high-value fixes.
Environment
- ox
v0.11.1 and v0.12.0 — reproduced on both; upgrading did not fix it
- git-lfs: absent at time of failure;
3.7.1 after fix
- macOS 14 (Darwin 23.6.0), amd64
- Impact: a shared team ledger stopped syncing for all members; self-repair could not converge
Defect 1 — ox doctor does not detect configured-but-missing git-lfs
Observed state
$ which git-lfs
(nothing — not in PATH, no homebrew keg)
$ git config --global --get-regexp 'filter.lfs'
filter.lfs.clean git-lfs clean -- %f
filter.lfs.smudge git-lfs smudge -- %f
filter.lfs.process git-lfs filter-process
filter.lfs.required true
git is configured to require LFS filters while the binary implementing them does not exist — the residue of an uninstall. Every LFS-backed file then checks out as an unsmudged pointer:
$ stat -f%z sessions/<session>/raw.jsonl
129
$ head -1 sessions/<session>/raw.jsonl
version https://git-lfs.github.com/spec/v1
Impact
Every session transcript on the machine was unreadable, yet ox doctor reported 76 passed / 1 failed, where the single failure was the downstream rebase symptom rather than the cause. The condition persisted long enough to accumulate 40+ unpushable commits.
Requested fix
Add a doctor check: if filter.lfs.* is configured (especially filter.lfs.required=true) but git-lfs is not on PATH, fail loudly with remediation. This is a two-line check that would have caught the problem immediately.
Defect 2 — Infinite retry when summarizing an LFS pointer
The daemon repeatedly attempted to summarize pointer files, each attempt failing validation:
"summary_status": "failed_validation",
"validation_error": "content validation failed: title too short (0 chars, minimum 3)",
"summary_attempts": 2
Each retry emitted a finalize session commit. Observed 21 duplicate finalize session commits across only 5 sessions (each finalized 4–5×). Daemon logs reported session meta titles: bumped=0 errored=53.
Requested fix
A file whose first line is version https://git-lfs.github.com/spec/v1 is trivially detectable. Treat it as a hard, non-retryable precondition failure — mark unrecoverable on first occurrence rather than looping. The terminal unrecoverable state already exists; it is simply reached far too late.
Defect 3 — ox doctor: auto-commit strips meta.json fields, generating unresolvable conflicts
This is the actual wedge mechanism, and it is independent of LFS — it can recur on any machine regardless of git-lfs state.
ox doctor: auto-commit ledger changes rewrites sessions/*/meta.json removing summary_status, validation_error, and summary_attempts, while origin retains them. Both sides edit the same lines → deterministic content conflict:
<<<<<<< HEAD
"summary_status": "failed_validation",
"validation_error": "content validation failed: title too short (0 chars, minimum 3)",
"summary_attempts": 2,
=======
>>>>>>> <sha> (ox doctor: auto-commit ledger changes)
Every git pull --rebase conflicted on the same 6 files and aborted. The ledger reached ahead 40 / behind 41 and could not push. ox sync and ox doctor --fix both reproduced the abort verbatim — doctor reports this as Ledger branch status (reconcile failed) [auto-fix], but the auto-fix cannot fix it.
Data-loss trap
The blocking commit carried real payloads — rendered plans under data/plans/, and a 199-file data/github/ migration. Git's on-screen hint suggests git rebase --skip, which destroys that work. A user following the displayed guidance loses data.
Requested fix
auto-commit must not strip fields it does not own or understand.
- Give
sessions/*/meta.json a merge strategy (union/custom driver), or make the failure-tracking fields single-writer.
- Never surface
rebase --skip as guidance for a commit carrying payload files.
Defect 4 — ox doctor --fix silently regresses tracked skill files when the binary is older than the files
Running ox doctor --fix under v0.11.1 rewrote a repo-tracked skill backwards:
- `ox code search`, decision-record work to `ox decision enrich`.
+ `ox code search`.
---
-<!-- ox-hash: 1db18042d6cb ver: 0.12.0 -->
+<!-- ox-hash: 1db18042d6cb ver: 0.11.1 -->
The ox-hash is byte-identical across the change while the frontmatter description differs — so the hash does not cover the full file and is not a valid change-detection signal. Left uncaught, this commits a silent content regression authored by whichever teammate last ran the older binary.
Requested fix
- Refuse to write a skill file when the binary version is older than the
ver: recorded in that file.
- Make
ox-hash cover the entire file, including frontmatter.
Defect 5 (minor) — daemon races manual ledger repair; stale error state
The daemon auto-starts on ox doctor / session start and re-enters its own rebase, leaving the ledger in detached HEAD mid-rebase. Divergence counts changed between consecutive commands, and one repair loop observed zero iterations because the state moved underneath it. There appears to be no lock.
Separately, ox status continued reporting:
[error] ledger: ledger is stuck in a broken rebase state.
Run '... rebase --abort' manually or 'ox doctor --fix' to recover. [CONFIRM REQUIRED]
after the rebase had completed and the tree was clean and fully synced — i.e. cached error state that is never invalidated on success.
Reproduction
- On a host with
git-lfs installed, let ox record sessions (transcripts stored as LFS).
- Uninstall
git-lfs without clearing filter.lfs.* from global git config.
- Let the daemon attempt to summarize any LFS-backed session.
- Observe: empty summaries →
title too short (0 chars) → unbounded retries → duplicate finalize session commits → meta.json conflict → ledger cannot push. ox doctor never identifies the cause.
Workaround (please consider documenting)
ox doctor --fix cannot recover this. The manual steps that worked:
A. Unwedge the ledger — with ox daemon status reporting Not running, otherwise it races you:
git rebase origin/main
# for each conflicted sessions/*/meta.json, take ORIGIN's side.
# NOTE the inversion: during a rebase, --ours = the commit being rebased ONTO (origin).
git checkout --ours -- <file> && git add <file>
GIT_EDITOR=true git rebase --continue
Took 13 iterations; divergence collapsed 40/41 → 0/0. Do not rebase --skip (see Defect 3).
B. Restore LFS. Four prerequisites — and the ledger ships with none of them:
brew install git-lfs && git lfs install --local
git config --unset remote.origin.promisor # ledger is cloned blob:none;
git config --unset remote.origin.partialclonefilter # LFS scans die on "missing object"
git config lfs.transfer.batchSize 5 # else the LFS server returns HTTP 413
# the ledger has NO .gitattributes at all — declare patterns repo-locally:
cat > .git/info/attributes <<'EOF'
sessions/**/raw.jsonl filter=lfs diff=lfs merge=lfs -text
sessions/**/context-trace.jsonl filter=lfs diff=lfs merge=lfs -text
sessions/**/session.md filter=lfs diff=lfs merge=lfs -text
sessions/**/summary.md filter=lfs diff=lfs merge=lfs -text
EOF
git lfs fetch origin main
rm <pointer files> && git checkout -- sessions/ # `git lfs checkout` no-ops: git sees the pointer as clean
Result: 1427 objects / 159 MB fetched, 2077 files materialized, 0 pointers remaining.
Two traps worth documenting
- Scope the attribute patterns. A bare
*.md filter=lfs also drives the clean filter, so the daemon would convert plain files (e.g. data/plans/*.md) into LFS pointers on the next commit — corrupting the ledger for all teammates. Verify with git check-attr filter -- <path>.
git status being clean is not evidence of materialization. Files written by the daemon during the pass remain pointers and need a second round. Verify by grepping file heads for version https://git-lfs.
Suggested priority
| # |
Defect |
Priority |
Rationale |
| 1 |
doctor misses missing git-lfs |
P0 |
Two-line check; prevents the entire cascade |
| 3 |
auto-commit strips meta.json fields |
P0 |
The actual wedge; LFS-independent; risks data loss via --skip |
| 2 |
infinite retry on pointer input |
P1 |
Bounds the damage |
| 4 |
doctor --fix version regression |
P1 |
Silent content regression into shared repos |
| 5 |
daemon race / stale status |
P2 |
Makes diagnosis confusing |
Suggested docs addition
The ledger has no .gitattributes and is cloned blob:none. Either ship LFS attributes with the ledger, or document that transcripts require the four-step setup above. Today, nothing tells a user their transcripts are unreadable — they simply get empty summaries forever.
TL;DR
A host with
git-lfsuninstalled but still declared in git config causes ox to read 130-byte LFS pointer files as if they were session transcripts. Summaries come out empty, validation fails, the daemon retries forever, and the resulting metadata churn produces an unresolvable rebase conflict that permanently blocks ledger sync.ox doctorpassed 76+ checks throughout and never once flagged the missing binary.Five distinct defects below. #1 and #3 are the high-value fixes.
Environment
v0.11.1andv0.12.0— reproduced on both; upgrading did not fix it3.7.1after fixDefect 1 —
ox doctordoes not detect configured-but-missinggit-lfsObserved state
git is configured to require LFS filters while the binary implementing them does not exist — the residue of an uninstall. Every LFS-backed file then checks out as an unsmudged pointer:
Impact
Every session transcript on the machine was unreadable, yet
ox doctorreported 76 passed / 1 failed, where the single failure was the downstream rebase symptom rather than the cause. The condition persisted long enough to accumulate 40+ unpushable commits.Requested fix
Add a doctor check: if
filter.lfs.*is configured (especiallyfilter.lfs.required=true) butgit-lfsis not onPATH, fail loudly with remediation. This is a two-line check that would have caught the problem immediately.Defect 2 — Infinite retry when summarizing an LFS pointer
The daemon repeatedly attempted to summarize pointer files, each attempt failing validation:
Each retry emitted a
finalize sessioncommit. Observed 21 duplicatefinalize sessioncommits across only 5 sessions (each finalized 4–5×). Daemon logs reportedsession meta titles: bumped=0 errored=53.Requested fix
A file whose first line is
version https://git-lfs.github.com/spec/v1is trivially detectable. Treat it as a hard, non-retryable precondition failure — markunrecoverableon first occurrence rather than looping. The terminalunrecoverablestate already exists; it is simply reached far too late.Defect 3 —
ox doctor: auto-commitstripsmeta.jsonfields, generating unresolvable conflictsThis is the actual wedge mechanism, and it is independent of LFS — it can recur on any machine regardless of git-lfs state.
ox doctor: auto-commit ledger changesrewritessessions/*/meta.jsonremovingsummary_status,validation_error, andsummary_attempts, while origin retains them. Both sides edit the same lines → deterministic content conflict:Every
git pull --rebaseconflicted on the same 6 files and aborted. The ledger reached ahead 40 / behind 41 and could not push.ox syncandox doctor --fixboth reproduced the abort verbatim —doctorreports this asLedger branch status (reconcile failed) [auto-fix], but the auto-fix cannot fix it.Data-loss trap
The blocking commit carried real payloads — rendered plans under
data/plans/, and a 199-filedata/github/migration. Git's on-screen hint suggestsgit rebase --skip, which destroys that work. A user following the displayed guidance loses data.Requested fix
auto-commitmust not strip fields it does not own or understand.sessions/*/meta.jsona merge strategy (union/custom driver), or make the failure-tracking fields single-writer.rebase --skipas guidance for a commit carrying payload files.Defect 4 —
ox doctor --fixsilently regresses tracked skill files when the binary is older than the filesRunning
ox doctor --fixunderv0.11.1rewrote a repo-tracked skill backwards:The
ox-hashis byte-identical across the change while the frontmatter description differs — so the hash does not cover the full file and is not a valid change-detection signal. Left uncaught, this commits a silent content regression authored by whichever teammate last ran the older binary.Requested fix
ver:recorded in that file.ox-hashcover the entire file, including frontmatter.Defect 5 (minor) — daemon races manual ledger repair; stale error state
The daemon auto-starts on
ox doctor/ session start and re-enters its own rebase, leaving the ledger in detached HEAD mid-rebase. Divergence counts changed between consecutive commands, and one repair loop observed zero iterations because the state moved underneath it. There appears to be no lock.Separately,
ox statuscontinued reporting:after the rebase had completed and the tree was clean and fully synced — i.e. cached error state that is never invalidated on success.
Reproduction
git-lfsinstalled, let ox record sessions (transcripts stored as LFS).git-lfswithout clearingfilter.lfs.*from global git config.title too short (0 chars)→ unbounded retries → duplicatefinalize sessioncommits →meta.jsonconflict → ledger cannot push.ox doctornever identifies the cause.Workaround (please consider documenting)
ox doctor --fixcannot recover this. The manual steps that worked:A. Unwedge the ledger — with
ox daemon statusreporting Not running, otherwise it races you:Took 13 iterations; divergence collapsed 40/41 → 0/0. Do not
rebase --skip(see Defect 3).B. Restore LFS. Four prerequisites — and the ledger ships with none of them:
Result: 1427 objects / 159 MB fetched, 2077 files materialized, 0 pointers remaining.
Two traps worth documenting
*.md filter=lfsalso drives the clean filter, so the daemon would convert plain files (e.g.data/plans/*.md) into LFS pointers on the next commit — corrupting the ledger for all teammates. Verify withgit check-attr filter -- <path>.git statusbeing clean is not evidence of materialization. Files written by the daemon during the pass remain pointers and need a second round. Verify by grepping file heads forversion https://git-lfs.Suggested priority
git-lfsauto-commitstripsmeta.jsonfields--skipdoctor --fixversion regressionSuggested docs addition
The ledger has no
.gitattributesand is clonedblob:none. Either ship LFS attributes with the ledger, or document that transcripts require the four-step setup above. Today, nothing tells a user their transcripts are unreadable — they simply get empty summaries forever.