Skip to content

Undetected git-lfs misconfiguration silently breaks all session summarization and permanently wedges the ledger #710

Description

@vikgup

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

  1. On a host with git-lfs installed, let ox record sessions (transcripts stored as LFS).
  2. Uninstall git-lfs without clearing filter.lfs.* from global git config.
  3. Let the daemon attempt to summarize any LFS-backed session.
  4. 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

  1. 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>.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions