Skip to content

Gate the git-lfs probe: skip the git-lfs fork when the repo has no LFS attributes - #148

Merged
blooop merged 4 commits into
mainfrom
perf/142-gate-lfs-probe
Aug 9, 2026
Merged

Gate the git-lfs probe: skip the git-lfs fork when the repo has no LFS attributes#148
blooop merged 4 commits into
mainfrom
perf/142-gate-lfs-probe

Conversation

@blooop

@blooop blooop commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Summary

Skip the git lfs ls-files fork (and the per-file pointer scan behind it) when the workspace holds nothing for it to report.

The first attempt gated on whether the clone declared filter=lfs in its own gitattributes. Review found that premise false, twice against real repos: a repo can hold committed pointers while declaring nothing, and can be LFS-tracked through attributes git reads from outside the clone (core.attributesFile, /etc/gitattributes). Both were read as "no LFS here", stranding such a workspace on stub files on every launch, not just once. Asking git check-attr instead does not fix it either — for a committed pointer with no attributes git itself answers filter: unspecified while git lfs ls-files still reports the file.

So the gate asks the question the caller actually has: can any path git-lfs could name here be an unmaterialized pointer. One git ls-files -z --with-tree=HEAD, then the same 23-byte pointer-prefix check the pointer scan already did. git lfs ls-files reports the union of HEAD's tree and the index, and --with-tree=HEAD is what makes git ls-files enumerate that same union — so "none of those paths holds a pointer" means the probe would have answered False, and the skip preserves the answer. When the scan does find a candidate, git-lfs is forked and decides, exactly as before.

This deletes machinery rather than adding to it: the three declaration sources, the filter=lfs marker constant, the fixture edits that made the old tests model attributes, and the untested .git/info/attributes branch whose mutant survived review all go away. Attribute-declared repos keep working for the same reason undeclared ones do, not as a special case.

Fails open unchanged: paths that cannot be enumerated mean "can't tell", not "no LFS", so the probe runs.

Correction — the earlier soundness proof in this PR was false

The previous head of this branch asserted, in the code, in CHANGELOG.md and in this body:

Every path git lfs ls-files can name is a tracked path, so "no tracked file holds a pointer" proves the probe would have answered False — the skip is behaviour-preserving by construction.

That is wrong, and it is withdrawn. git ls-files reports the index; git lfs ls-files reports the union of HEAD's tree and the index. The second set is strictly larger, so nothing was preserved by construction. Verified against real git 2.55.0 / git-lfs 3.7.1, filter=lfs declared, pointer committed:

repo state git ls-files git ls-files --with-tree=HEAD git lfs ls-files
untouched .gitattributes, big.bin .gitattributes, big.bin big.bin
git rm --cached big.bin .gitattributes .gitattributes, big.bin big.bin
git rm -r --cached . (empty, exit 0) .gitattributes, big.bin big.bin
git read-tree --empty (empty, exit 0) .gitattributes, big.bin big.bin
.git/index deleted (empty, exit 0) .gitattributes, big.bin big.bin

The last row is the one that bites: it needs no user action. An interrupted clone or checkout leaves no index, git ls-files succeeds with empty output, and the old gate read that as "nothing tracked, therefore no pointers" and skipped — stranding the workspace on stubs on every later launch, which is exactly what the materialization retry exists to prevent. It also contradicted the function's own contract, which fails open on an unlistable index (exit 128); both existing fail-open tests model only that erroring shape, so nothing caught the exit-0-empty shape.

The fix is --with-tree=HEAD: same single fork, and the narrower claim it supports — every path git lfs ls-files names is in HEAD or in the index — is the one that is actually true. That claim is now what the code asks, what the docstring says and what CHANGELOG.md says.

Cost of the union over the index alone, median of 7 runs: 17.5ms vs 16.5ms at 3000 tracked files, 202ms vs 190ms at 50 000 — against 119ms and 1180ms for the git lfs ls-files it stands in front of.

Correction — "one cheap local check"

The ticket's phrasing, and this body's, implied an in-process check. It is not one. It is a git ls-files fork plus one open() of the first few bytes per listed path — the same O(tracked files) shape as git lfs ls-files, at a much smaller constant. That is now how the docstring and the changelog describe it. The win is real (below), the description was overclaiming.

Measured

Medians on this machine (7–9 runs), current head, against git lfs ls-files on the same repos:

repo gate (ls-files --with-tree=HEAD + scan) git lfs ls-files
this repo's checkout, 124 tracked files 3.9ms 33.9ms
synthetic repo, 3000 tracked files 17.5ms 118.6ms
synthetic repo, 50 000 tracked files 202.4ms 1180.2ms

A workspace that really is holding pointers still pays the probe on top of the scan and materializes exactly as before — that case is a small cost, not a win. The earlier 72ms→4ms figure in this PR was withdrawn on the previous head and stays withdrawn.

Test-first evidence (this retry)

Four tests added. Selection run against the merge commit 4b7a1a4, i.e. the old gate on the new base:

$ pixi run -e py310 test -p no:randomly \
    test/integration/test_lfs_probe_real.py::TestPointerDetectionAgainstRealRepos::test_pointer_is_detected_when_the_clone_has_no_index \
    test/integration/test_lfs_probe_real.py::TestPointerDetectionAgainstRealRepos::test_pointer_only_in_head_is_detected \
    test/integration/test_lfs_probe_real.py::TestProbeCostAgainstRealRepos::test_tracked_paths_that_will_not_open_are_not_read_as_pointers \
    test/test_workspace_clone.py::TestEnsureWorkspace::test_lfs_path_missing_from_the_working_tree_is_not_pulled_forever

>       assert answer is True
E       assert False is True
test/integration/test_lfs_probe_real.py:196: AssertionError
...
>       assert answer is True
E       assert False is True
test/integration/test_lfs_probe_real.py:214: AssertionError

FAILED test/integration/test_lfs_probe_real.py::TestPointerDetectionAgainstRealRepos::test_pointer_is_detected_when_the_clone_has_no_index
FAILED test/integration/test_lfs_probe_real.py::TestPointerDetectionAgainstRealRepos::test_pointer_only_in_head_is_detected
2 failed, 2 passed in 0.60s

Stated plainly, because it matters: only two of the four were red-first. test_pointer_is_detected_when_the_clone_has_no_index (the absent-.git/index shape the review found) and test_pointer_only_in_head_is_detected pin the blocking fix and fail on the old gate. The other two pin the direction of _is_lfs_pointer's OSError handling; the shipped direction was already correct, so they pass on the old code and are red only against the mutant — proof below rather than above.

After the change, the same file plus its unit-test counterpart: 57 passed in 1.95s.

The surviving mutant, now killed

except OSError: return True in _is_lfs_pointer survived the full 1174-test suite at review time. The previous head disclosed it with the justification "it can only cost a fork, never strand". That justification was wrong and is withdrawn: at the second call site the flip drives _materialize_lfs into git lfs pull origin — uncaptured, potentially multi-gigabyte — on every launch, forever, for any workspace with an LFS-tracked path off disk (a sparse checkout, most obviously), because the pull cannot put back a path the checkout excludes.

Mutant applied to the current head, then restored:

MUTANT APPLIED: _is_lfs_pointer except OSError -> return True
$ pixi run -e py310 test -p no:randomly \
    test/integration/test_lfs_probe_real.py::TestProbeCostAgainstRealRepos::test_tracked_paths_that_will_not_open_are_not_read_as_pointers \
    test/test_workspace_clone.py::TestEnsureWorkspace::test_lfs_path_missing_from_the_working_tree_is_not_pulled_forever

INFO devlaunch.worktree.workspace_clone: Fetching git-lfs objects from origin
FAILED test/integration/test_lfs_probe_real.py::TestProbeCostAgainstRealRepos::test_tracked_paths_that_will_not_open_are_not_read_as_pointers
FAILED test/test_workspace_clone.py::TestEnsureWorkspace::test_lfs_path_missing_from_the_working_tree_is_not_pulled_forever
2 failed in 0.36s

One test per call site: the gate must not fork git-lfs, and materialization must not pull. The Fetching git-lfs objects from origin line in the failure output is the mutant doing the thing the disclosure said it could not do.

The six mutants killed on the previous head are unchanged by this diff and still killed.

Global git config no longer decides the result

test_lfs_probe_real.py built its repos with only user.name/user.email set locally, so a developer with commit.gpgsign = true got a wall of LFS failures caused by a missing signing key. Verified against a hostile global config:

before:  12 failed, 1 passed
after:   13 passed

make_repo now sets commit.gpgsign false locally. This is a pre-existing convention in the repo — test/integration/test_repo_manager_real.py has the same exposure — and fixing that one is left alone as out of scope for this ticket.

Also: b"the real twelve bytes of content" was 32 bytes against a pointer declaring size 12. It is now a named REAL_CONTENT constant that is genuinely twelve bytes, asserted at import.

Merge with main

main moved (#147, cc95eec) and this PR went CONFLICTING. Merged here by the builder, merge commit 4b7a1a4. The only real conflict was CHANGELOG.md, where both branches added an entry under ### Changed; both entries are kept and read in order. devlaunch/dl.py, test/conftest.py and test/test_devpod_spawn_counts.py merged clean — this branch never touched them, so #147's invalidate_clone_manager() and its autouse fresh_clone_manager fixture arrive unmodified, and nothing here adds a second name for that concept.

CI

pixi run -e py310 ci end-to-end as one task: exit 01179 passed, 23 deselected, ruff/ty/pylint clean, coverage + report. workspace_clone.py 159 stmts / 16 missed / 90%; every missing line is pre-existing and none is in this patch.

CHANGELOG updated.

Ticket: #142

🤖 Generated with Claude Code

The probe ran unconditionally on every workspace preparation:
_lfs_tracked_files forked `git lfs ls-files` (and _has_lfs_pointers then
read the head of every tracked file) even for the overwhelmingly common
repo with no LFS at all.

Skip it unless the clone's gitattributes actually declare filter=lfs —
the only mechanism by which pointer files can exist. Checked
cheapest-first: top-level .gitattributes (what `git lfs track` writes,
read directly with no fork), then tracked .gitattributes at any depth
via one plain `git ls-files`, then .git/info/attributes. An unlistable
index fails open to probing, mirroring the probe's own refusal to
degrade silently to "no LFS here".

The gate is a property of repo content only — deliberately not of
is_new_workspace or any launch history — so a previously failed
`git lfs pull` is still retried on the next run.

Existing LFS test fixtures modeled pointer files without the attributes
that create them (an impossible repo state); they now carry the
filter=lfs declaration, assertions untouched.

Ticket: #142

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @blooop, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@sourcery-ai

sourcery-ai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Reviewer's Guide

Introduce an LFS-attributes gate so git-lfs probing only runs when the workspace’s Git attributes declare filter=lfs, and add tests/fixtures to cover non-LFS repos, nested attributes, and fail-open behavior while keeping existing retry/materialization semantics intact.

Sequence diagram for gated git-lfs probing based on gitattributes

sequenceDiagram
    participant WorkspaceClone
    participant filesystem
    participant subprocess_run

    WorkspaceClone->>filesystem: _mentions_lfs_filter(.gitattributes)
    alt [top-level .gitattributes mentions filter=lfs]
        WorkspaceClone->>WorkspaceClone: _lfs_attributes_declared returns True
    else [no top-level filter=lfs]
        WorkspaceClone->>subprocess_run: subprocess.run(git ls-files -z -- *.gitattributes)
        alt [git ls-files fails]
            WorkspaceClone->>WorkspaceClone: _lfs_attributes_declared returns True
        else [git ls-files succeeds]
            WorkspaceClone->>filesystem: _mentions_lfs_filter(each tracked .gitattributes)
            alt [any tracked attributes mention filter=lfs]
                WorkspaceClone->>WorkspaceClone: _lfs_attributes_declared returns True
            else [no tracked attributes mention filter=lfs]
                WorkspaceClone->>filesystem: _mentions_lfs_filter(.git/info/attributes)
                WorkspaceClone->>WorkspaceClone: _lfs_attributes_declared returns bool
            end
        end
    end

    WorkspaceClone->>WorkspaceClone: _lfs_tracked_files(ws_path)
    alt [shutil.which("git-lfs") is None]
        WorkspaceClone-->>WorkspaceClone: return []
    else [git-lfs present]
        alt [_lfs_attributes_declared(ws_path) is False]
            WorkspaceClone-->>WorkspaceClone: return []
        else [_lfs_attributes_declared(ws_path) is True]
            WorkspaceClone->>subprocess_run: subprocess.run(git lfs ls-files --name-only)
            WorkspaceClone-->>WorkspaceClone: return tracked LFS paths
        end
    end
Loading

File-Level Changes

Change Details Files
Gate the git-lfs probe based on presence of filter=lfs declarations in gitattributes to avoid unnecessary forks on non-LFS repos.
  • Add _LFS_ATTRIBUTE_MARKER constant capturing the filter=lfs marker used in gitattributes.
  • Introduce _mentions_lfs_filter helper to safely detect filter=lfs in a given attributes file.
  • Implement _lfs_attributes_declared to check for LFS attributes in top-level .gitattributes, tracked .gitattributes via git ls-files, and .git/info/attributes, with fail-open semantics on index listing failure.
  • Update _lfs_tracked_files to be a @classmethod, call the new attributes gate, and skip git-lfs probing entirely when no LFS attributes are declared.
devlaunch/worktree/workspace_clone.py
Align and extend tests to model realistic LFS repos with attributes, plus new gating behaviors for non-LFS, nested-attributes, and fail-open scenarios.
  • Update existing workspace clone tests to create .gitattributes with filter=lfs so LFS pointer files are realistically modeled.
  • Add test_repo_without_lfs_attributes_never_forks_git_lfs to ensure repos without any filter=lfs declaration never fork git-lfs.
  • Add test_nested_lfs_attributes_still_probe to ensure repos with only nested .gitattributes still trigger git-lfs probing and pulling.
  • Add test_unlistable_attributes_fail_open_to_probing to ensure failures when listing gitattributes result in probing rather than incorrectly skipping LFS handling.
test/test_workspace_clone.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.31%. Comparing base (cc95eec) to head (9f0df8a).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #148      +/-   ##
==========================================
+ Coverage   93.04%   93.31%   +0.26%     
==========================================
  Files          20       20              
  Lines        2358     2408      +50     
==========================================
+ Hits         2194     2247      +53     
+ Misses        164      161       -3     
Files with missing lines Coverage Δ
devlaunch/worktree/workspace_clone.py 89.93% <100.00%> (+1.93%) ⬆️

... and 1 file with indirect coverage changes

Impacted file tree graph

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@blooop blooop left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during review.

Two-axis review of head 4612245 against merge-base f03158b (git diff f03158b...4612245). Preflight: ref resolves, diff non-empty (2 files, +169/-4), all remote checks green.

Provenance note. The Spec axis was first run by an earlier, independent reviewer against this exact head SHA, which found no blocking findings but was terminated before it could post. That evidence is credited below and was independently re-verified in this run (bullets enumerated from the ticket afresh, red-at-base reproduced empirically, mutation-tested) rather than taken on trust. The Standards axis had never been run and is entirely fresh here. Axes were run as parallel subagents and are reported unmerged and unranked.

Standards

Ran pixi run -e py310 python -m pytest test/test_workspace_clone.py -q → 44 passed. Also ran a 4-mutant kill test on a scratch copy and verified git pathspec/attribute behaviour empirically in a scratch repo.

  • Non-blockingdevlaunch/worktree/workspace_clone.py:132, :163: the docstring claims "a clone that declares none anywhere", but the gate misses sources git genuinely honours. Verified empirically: with core.attributesFile pointing at a file containing *.psd filter=lfs, git check-attr filter -- foo.psdfilter: lfs, while git ls-files -- '*.gitattributes' lists nothing. Same for system /etc/gitattributes. Also missed: untracked nested .gitattributes, and sparse-checkout entries listed by the index but absent on disk (read fails → False). Rare, but the failure mode is exactly the silent pointer-stranding the code says it refuses. Either consult git check-attr / git config --get core.attributesFile, or soften the docstring to name its scope honestly.
  • Non-blocking:163: the .git/info/attributes fallback is untested. Mutating it to return False left all 44 tests green. The other three branches are properly pinned (killing the top-level fast path fails 2 tests; fail-closed fails 1; always-True fails 1). Tests are honest rather than tautological despite wholesale subprocess.run mocking — assertions are on issued argv at the process seam, with positive and negative cases balancing each other.
  • Non-blocking:127: _lfs_attributes_declared returns True on the fail-open branch having found no declaration, so the name asserts a fact the value does not carry. Judged a naming defect, not a missing sum type: the sole consumer (:173) asks "should I probe?", and a three-case enum would collapse two of its cases at the one call site — speculative generality. Suggest renaming to _should_probe_lfs; bool is the right carrier.
  • Non-blocking — no measurement is presented anywhere in the PR; "cheaper" is asserted, against map #139's "every perf change lands with before/after numbers, not assumptions". Measured during review (20 iterations): git ls-files -- '*.gitattributes' ~4ms/call vs git lfs ls-files --name-only ~72ms/call. The saving is real (~68ms per cold launch) but was unevidenced by the author and unquantified against total launch time.
  • Nit:147 then :160: a top-level .gitattributes lacking the marker is read twice, once directly and once via the ls-files list.
  • NitWorkspaceCloneManager now carries four LFS/gitattributes helpers, none of which use self (Divergent Change). A module-level lfs.py would keep clone orchestration separate from git-lfs internals.
  • Nit:38: the substring match also hits # filter=lfs in a comment, or filter=lfsx. Over-detection only; harmless.

Repo rules: clean. No hardcoded /home/<user> paths; new tests set no XDG_* (conftest's documented dual scoping is unchanged); nothing invokes released dl/aid.

Axis verdict: approve — no blocking standards issues.

Spec

Ticket #142's "Done when" bullets, enumerated independently (4 bullets plus the 2 constraints in the "Shape of the fix" paragraph — what the prior reviewer's "six points" reduces to):

  1. "A failing test first: repo without LFS attributes → no git lfs subprocess is spawned" — met. test_repo_without_lfs_attributes_never_forks_git_lfs uses the pre-agreed mocked-subprocess seam. Empirically red at base: running the head test file against f03158b source in a scratch copy gives 1 failed, 43 passed, failing exactly on assert not any(cmd[:2] == ["git","lfs"] ...).
  2. "A repo WITH filter=lfs attributes still probes and materializes exactly as today" — met (test_new_workspace_materializes_lfs, test_existing_workspace_retries_unmaterialized_lfs).
  3. "Cold-path only — no behavior change on warm launches" — met. ensure_workspace is reached only from devlaunch/dl.py:2362, inside the else of the fast-path branch at dl.py:2337-2341. Warm launches never enter the gate.
  4. "CI green (pixi run ci, check py310)" — met: 44/44 on the touched file, 1165 passed full suite, all remote checks green.
  5. "The existing deliberate choice at workspace_clone.py:350-352 … must be preserved" — met. Now at workspace_clone.py:406-408 (comment) / :409 (self._materialize_lfs(ws_path)), still ungated on is_new_workspace.
  6. "the gate must be a property of the repo content, not of launch history" — met; _lfs_attributes_declared reads only clone contents.

All six of the earlier reviewer's conclusions were re-verified and confirmed; none refuted. Its claim that the gate at :173 precedes the git lfs ls-files fork at :175 checks out.

  • Non-blocking — the fixture edits are legitimate, not test-tuning. This was the highest-risk item: the PR modifies three existing LFS tests to add .gitattributes, and the easy way to make a gate pass is to edit fixtures until they agree with it. Evidence both directions: the three modified tests still pass against base source (the 43/44 above), so they encode no new behavior; and a mutant forcing _lfs_attributes_declaredFalse kills test_existing_workspace_retries_unmaterialized_lfs, test_new_workspace_materializes_lfs, and both new probe tests. Only test_materialized_workspace_does_not_refetch_lfs passes vacuously under that mutant — a pre-existing negative-assertion weakness, not introduced here.
  • Non-blocking — spec: "a non-LFS repo pays (at most) one cheap local check instead of a git lfs fork". A non-LFS repo in fact pays a real git ls-files fork, not an in-process check. Justified by the spec's own "tracked or top-level" phrasing, but see the Standards axis on the missing before/after numbers.
  • Nit — an untracked nested .gitattributes, and a global core.attributesFile, are not consulted. Both vanishingly rare. (Independently surfaced on the Standards axis too, where the docstring's "anywhere" makes it a wording problem as well.)

Axis verdict: approve — no blocking findings.

Verdict

Comment / Approve — no blocking findings on either axis. (Posted as a comment: GitHub refuses --approve on a PR authored by the same account.)

Blocking findings: none.

Recommended before or shortly after merge, none gating:

  1. Soften the _lfs_attributes_declared docstring's "declares none anywhere" to name the scope it actually covers, or extend the gate to core.attributesFile (workspace_clone.py:132).
  2. Rename _lfs_attributes_declared_should_probe_lfs so the fail-open True stops contradicting the name (:127).
  3. Post the before/after numbers on #142 — measured here at roughly 4ms vs 72ms per cold launch — to satisfy map #139's measurement rule.

Not merging; merge stays human.

@blooop blooop left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during review.

Two-axis review of PR #148 at head 4612245a3a014f0f3dff908d201e8dfd43ba5f9e, fixed point = merge-base with main f03158bb2d0a651556bbefa27ea5285d22b97749 (three-dot diff: devlaunch/worktree/workspace_clone.py +58, test/test_workspace_clone.py +115). Preflight: ref resolves, diff non-empty, gh pr checks 148 all green (py310-313, e2e, prek, gate, codecov), 3 workflow runs exist for the head SHA.

Provenance note, stated up front: the earlier "Spec axis" report on this PR came from an agent lineage that was caught fabricating review evidence (retracted on #156), and the in-review-approved verdict on #142 was publicly downgraded to PROVISIONAL as a result. That report was treated here as a lead only — not read into the axes, not cited, not inherited. Both axes below were run fresh by independent parallel subagents that were given the integrity warning verbatim. This review supersedes the provisional verdict.


Standards

No blocking findings. The extraction is clean under Fowler: no Duplicated Code, Feature Envy, Shotgun Surgery, or Speculative Generality; _mentions_lfs_filter is a justified three-call helper; no /home/<user> paths; the long explanatory docstring matches this repo's documented house style, so it is not over-commenting here.

  1. .git/info/attributes branch is untested — mutant survived. devlaunch/worktree/workspace_clone.py:163. Replacing that return with return False leaves all 44 tests green (evidence below). Violates principle 4 (test-first: behavior arrives with a test that proves it was absent) — the one declaration source no test exercises. The other three mutants were killed. Non-blocking, but it is the only genuinely unconstrained line in the diff.
  2. Lying predicate: _lfs_attributes_declared returns True for "could not tell". devlaunch/worktree/workspace_clone.py:156-158. The name asserts a fact the function does not know; the boolean carries two meanings (declared / unknown). Fowler Mysterious Name plus principle 3 (no sentinels). Rename to _may_have_lfs_attributes, or return a tri-state. Non-blocking — private helper, and the comment does document the fail-open.
  3. Docstring promises more coverage than the code delivers. devlaunch/worktree/workspace_clone.py:130-141 says "a clone that declares none anywhere cannot need materialization", but step 2 enumerates the index, so an untracked nested .gitattributes is invisible (confirmed against real git; step 3 covers only .git/info/attributes, a different mechanism). Principle 1 — a comment overstating a guarantee is worse than none. Non-blocking.
  4. CHANGELOG.md not updated. CHANGELOG.md:3 states "All notable changes to this project will be documented in this file", and the immediately preceding launch-latency commit 6abc36f did so. A per-launch fork removal is notable. Non-blocking.

Standards evidence (executed by the Standards axis)

44 passed in 0.45s                                   # pytest test/test_workspace_clone.py
1165 passed, 23 deselected in 24.23s                 # pytest (full, py310)
Your code has been rated at 10.00/10                 # pylint py310, exit 0
73 files already formatted / All checks passed!      # ruff format --check, ruff check
All checks passed!                                   # pixi run -e py310 ty

Mutants (each restored with git checkout -- .; final git status --short empty at 4612245):

gate -> return False                    : 4 failed, 40 passed   KILLED
top-level .gitattributes check disabled : 2 failed, 42 passed   KILLED
ls-files fail-open True -> False        : 1 failed, 43 passed   KILLED
.git/info/attributes return -> False    : 44 passed             SURVIVED

Red-before (source only reverted to f03158b, new tests kept):

FAILED test/test_workspace_clone.py::TestEnsureWorkspace::test_repo_without_lfs_attributes_never_forks_git_lfs
1 failed, 43 passed in 0.24s

Standards — not run

pixi run ci end-to-end (components run individually instead: format, ruff-lint, pylint, ty, pytest; coverage/coverage-report not run). e2e suite not run. ./dev.sh / dl-next not run — no real-launch latency measured by this axis. py311/312/313 environments not run.


Spec

Spec = ticket #142. Three blocking findings.

1. The gate's central premise is false: git-lfs reports pointers with no attributes declared — BLOCKING
devlaunch/worktree/workspace_clone.py:130 claims "Pointer files can only arise from a filter=lfs gitattributes line". Spec: "check whether the clone can possibly use LFS … and skip the whole probe when it can't." A real repo with a valid pointer blob and no gitattributes anywhere is listed by real git lfs ls-files. Base materializes it; head strands it on pointers permanently. The gate is not a sound over-approximation, so it does not merely skip repos that "can't" use LFS.

2. Out-of-clone declarations git honours are invisible to the gate — BLOCKING
workspace_clone.py:127-163 reads only in-clone paths. Spec done-when: "A repo WITH filter=lfs attributes still probes and materializes exactly as today." With core.attributesFile set — git agrees, check-attr reports filter: lfs — head returns [] where base returns ['big.bin']. /etc/gitattributes is the same code path in git.

3. The retry path is preserved in form but dead for the stranded case — BLOCKING
workspace_clone.py:406-409 correctly still calls _materialize_lfs ungated on is_new_workspace, so the spec's "must be preserved" clause is met structurally. But the spec's rationale is "so a previously failed pull isn't permanent", and for finding 1's and finding 2's repos the skip is now permanent on every launch. test_existing_workspace_retries_unmaterialized_lfs (test/test_workspace_clone.py:298) only stays green because its fixture gained the attributes line.

4. Fixture edits are load-bearing, and the "impossible state" justification is wrong — non-blocking (consequence of 1)
PR body: "Existing LFS fixtures … Assertions untouched." Assertions genuinely untouched (diff-verified). But removing the 3 added fixture lines fails 2 pre-existing tests — the edits encode the behavior change, and the state the PR calls impossible is reachable (finding 1).

5. Cost: it is a fork, not "one cheap local check" — but ~50x cheaper — non-blocking
Spec: "a non-LFS repo pays (at most) one cheap local check instead of a git lfs fork." The gate falls through to a git ls-files fork for the common repo with no top-level .gitattributes. Measured on a 3000-file non-LFS repo: git lfs ls-files median 108.38ms vs git ls-files -z -- '*.gitattributes' median 2.15ms. The spec's own shape text says "(tracked or top-level)", so the index lookup is sanctioned; the win is real, just not "no fork".

6. .git/info/attributes check is a no-op when .git is a file — non-blocking, relevant to #163
workspace_clone.py:163. Today's workspaces are full clones (.git is a directory), so no live impact; if #163's shared LFS store moves workspaces to linked worktrees, check 3 silently dies. Verified: in a linked worktree _lfs_attributes_declared is False while git lfs ls-files reports big.bin.

Spec evidence (executed by the Spec axis)

=== HEAD source ===
  caseA: _lfs_tracked_files=[]  _has_lfs_pointers=False
  caseB: _lfs_tracked_files=[]  _has_lfs_pointers=False
=== BASE source (f03158b) ===
  caseA: _lfs_tracked_files=['big.bin']  _has_lfs_pointers=True
  caseB: _lfs_tracked_files=['big.bin']  _has_lfs_pointers=True

(caseA = pointer, no attributes anywhere; caseB = same + core.attributesFile)

  check-attr: big.bin: filter: lfs
git lfs ls-files --name-only             median= 108.38ms min=  98.40ms max=2316.33ms
git ls-files -z -- *.gitattributes       median=   2.15ms min=   1.71ms max=   2.60ms

Red-before (head tests + base source):

>       assert not any(cmd[:2] == ["git", "lfs"] for cmd in issued)
E       assert not True
FAILED test/test_workspace_clone.py::TestEnsureWorkspace::test_repo_without_lfs_attributes_never_forks_git_lfs
1 failed, 43 passed in 0.17s

Slice mutants (corroborating the PR's per-slice red claims):

=== slice-1 mutant (top-level-only gate) ===
2 failed, 42 passed in 0.18s   (test_nested_lfs_attributes_still_probe, test_unlistable_attributes_fail_open_to_probing)
=== slice-2 mutant (no fail-open) ===
1 failed, 43 passed in 0.18s   (test_unlistable_attributes_fail_open_to_probing)

Fixture-edit revert (head source, 3 fixture lines removed):

FAILED ...::test_existing_workspace_retries_unmaterialized_lfs
FAILED ...::test_new_workspace_materializes_lfs
2 failed, 42 passed in 0.32s

Green at head, and linked-worktree probe:

44 passed in 0.49s                      (test/test_workspace_clone.py)
1165 passed, 23 deselected in 95.72s    (full test/)
linked .git is a: FILE
git lfs ls-files in linked worktree: big.bin
HEAD _lfs_attributes_declared(linked worktree) = False

Environment: git version 2.55.0, git-lfs/3.7.1, python 3.12.3 in a uv venv.

Spec — not run

pixi run ci / pixi run -e py310 ci not run by this axis (pytest + ruff on py3.12 only); the PR's "py310 CI green / pylint 10.00" claim is not verified by this axis (it is verified by the Standards axis above). No end-to-end dl-next launch — findings 1/2/6 are at the _lfs_tracked_files / _lfs_attributes_declared boundary against real git repos, not through ensure_workspace. Warm-launch timing not measured (assessed by reading workspace_clone.py:406-409). /etc/gitattributes not tested (no root); inferred from the core.attributesFile result, same code path in git. Ticket #163 impact is an inference from finding 6 plus the diff.


Reviewer's own independent verification

Because the two blocking Spec findings flip the verdict, I re-ran them myself rather than relying on a subagent self-report. Verbatim output from this session:

Finding 1 — real repo, LFS pointer committed, no gitattributes anywhere:

--- ls of repo (no gitattributes anywhere) ---
. .. big.bin .git
--- git check-attr filter big.bin ---
big.bin: filter: unspecified
--- git lfs ls-files --name-only ---
big.bin
git-lfs/3.7.1 (GitHub; linux amd64; go 1.26.4)
=== HEAD source (4612245) ===
  _lfs_attributes_declared = False
  _lfs_tracked_files       = []
=== BASE source (f03158b) ===
  _lfs_attributes_declared = <absent at base>
  _lfs_tracked_files       = ['big.bin']

Finding 2 — same pointer repo with core.attributesFile pointing at a filter=lfs rule, no in-clone .gitattributes:

--- in-clone gitattributes present? ---
. .. big.bin .git
--- git check-attr filter big.bin (git DOES honour it) ---
big.bin: filter: lfs
HEAD _lfs_attributes_declared = False
HEAD _lfs_tracked_files       = []

Both reproduce. I did not independently re-run: the timing measurement, the mutation runs, the full suite, or the linked-worktree probe — those stand on the axes' pasted output only.


Verdict

Request changes. (Posted as a --comment review: GitHub refuses --request-changes when author and reviewer are the same account. The written verdict is the gate.)

Blocking findings, all from the Spec axis:

  1. workspace_clone.py:130 — the gate's premise ("pointer files can only arise from a filter=lfs line") is false; a repo holding pointers with no attributes is skipped and stranded permanently, where base materialized it. Independently reproduced above.
  2. workspace_clone.py:127-163 — declarations git honours from outside the clone (core.attributesFile, /etc/gitattributes) are invisible to the gate, so a repo that does have filter=lfs in git's view no longer probes. Directly contradicts the ticket's done-when "A repo WITH filter=lfs attributes still probes and materializes exactly as today." Independently reproduced above.
  3. workspace_clone.py:406-409is_new_workspace is correctly still not a gate, but for the repos in (1) and (2) the skip is permanent on every launch, defeating the spec's stated rationale "so a previously failed pull isn't permanent."

The Standards axis is clean and the performance win is real and measured; the objection is soundness of the gate, not the idea. A gate that fails open on "I cannot see attributes" (as it already does for an unlistable index) but currently fails closed on "there are no attributes in the clone" is the asymmetry to resolve — e.g. treat the cheap in-clone check as sufficient only to confirm LFS, and consider git check-attr or a pointer-content heuristic for the negative case, so the answer matches git's own view of the repo.

Non-blocking items worth folding into the next change to this file: the surviving .git/info/attributes mutant, the _lfs_attributes_declared naming/sentinel defect, the overclaiming docstring, the CHANGELOG entry, and the .git-as-file no-op that will matter for #163.

The gate shipped for #142 assumed pointer files can only arise from a
filter=lfs gitattributes line in the clone. They can't only arise that
way: a repo can hold committed pointers while declaring nothing, and can
be LFS-tracked through attributes git reads from outside the clone.
Both were read as "no LFS here", so such a workspace was left on stub
files on every launch rather than materialized once.

Ask instead the question the caller actually has: can any tracked file
here be an unmaterialized pointer. Every path git lfs ls-files can name
is a tracked path, so a clone where no tracked file holds a pointer
cannot have one and the probe would answer False anyway — the skip is
behaviour-preserving by construction rather than by an argument about
where attributes can live. The three declaration sources, the marker
constant and the fixture edits that made the old tests model attributes
all go with it.
@blooop
blooop force-pushed the perf/142-gate-lfs-probe branch from 0a8f3b9 to a39eaf2 Compare August 9, 2026 11:20

@blooop blooop left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during review.

Re-verdict after the retry. Fresh reviewer; did not write this code and did not write the previous review. Head a39eaf22fc5efc201570a127e1cee4d9d6bb46f8, fixed point = merge-base with main f03158bb2d0a651556bbefa27ea5285d22b97749 (git diff f03158b...a39eaf2, 4 files, +416/−30). Preflight confirmed independently: merge-base resolves, diff non-empty, 3 workflow runs exist for the head SHA, gh pr checks all SUCCESS including codecov/patch, mergeable CLEAN.

The approach was rewritten since the previous review, so both axes reviewed this as new code rather than as a diff against the previous head. Axes were run as parallel subagents and are reported unmerged and unranked. The builder's pasted evidence was treated as a claim to re-run, not as verification.


Standards

No blocking findings. The change is a real simplicity win (principle 2): _mentions_lfs_filter, _lfs_attributes_declared and _LFS_ATTRIBUTE_MARKER are gone with no stragglers (zero references repo-wide), _lfs_tracked_files keeps exactly one caller, and its docstring was correctly de-staled when the shutil.which guard moved out of it. No dead code, no speculative generality.

  1. Non-blocking — no test pins the direction of _is_lfs_pointer's OSError handling. workspace_clone.py:119-120. Mutating return Falsereturn True survives all 1174 tests (verified, full suite). The shipped behaviour is correct but unguarded: every ordinary workspace has tracked paths that will not open, so this flip silently reinstates the git-lfs fork on every launch — the very thing this PR removes — with CI green. test_tracked_paths_that_will_not_open_do_not_stop_the_scan cannot catch it because it plants a real pointer, so the answer and the fork are True either way. The missing test is one line of setup: unreadable tracked paths and no pointer must still not fork git-lfs. Highest-value test to add.
  2. Nit — the new integration tests inherit the developer's global git config; under commit.gpgsign = true, 9 of 10 fail (verified). Pre-existing convention, not a regression: test/integration/test_repo_manager_real.py errors 8 tests under the same config.
  3. Nittest_lfs_probe_real.py:202,241 writes 32 bytes as "the real twelve bytes of content" against a pointer declaring size 12.
  4. test/test_devpod_spawn_counts.py correctly untouched — it pins devpod argv, and this change adds/removes no devpod fork. It passes unmodified. The equivalent guard here is the new forked_git_lfs(issued) assertion, which does bite.
  5. No /home/<user> anywhere in the diff (zero hits on added lines); all test paths derive from tmp_path.
  6. Fowler_is_lfs_pointer re-runs on the same paths in the positive case: duplicated work, not duplicated code, and the docstring justifies it. _may_hold_lfs_pointers's "True unless no tracked file…" is a double negative. Both nits.
  7. Perf hypothesis refuted in the PR's favour. The suspicion that an O(tracked-files) scan would lose to a constant-cost fork on a large repo is wrong — git lfs ls-files is also O(n) and dominates at every size: gate 3.5 / 19.0 / 169.2 ms vs git lfs ls-files 35.7 / 101.5 / 722.5 ms at 300 / 3000 / 30000 tracked files. The changelog's numbers are corroborated, not merely accepted.

Spec

One blocking finding.

S1 — BLOCKING. The load-bearing soundness claim is empirically false: git lfs ls-files names paths git ls-files does not.
The claim, asserted as a proof in workspace_clone.py:128-129, in the PR body and in CHANGELOG.md: "Every path git lfs ls-files can name is a tracked path, so 'no tracked file holds a pointer' proves the probe would have answered False — the skip is behaviour-preserving by construction." Against #142's "A repo WITH filter=lfs attributes still probes and materializes exactly as today."

git ls-files reports the index; git lfs ls-files reports the union of HEAD's tree and the index. So lfs_ls ⊄ git_ls. Real repos (real git 2.55.0 + git-lfs 3.7.1, filter=lfs declared, pointer on disk) where OLD=True and NEW=False — the exact silent-skip direction the previous review blocked:

repo state git ls-files git lfs ls-files OLD NEW
git rm --cached big.bin ['.gitattributes'] ['big.bin'] True False
git rm -r --cached . [] ['big.bin'] True False
git read-tree --empty [] ['big.bin'] True False
.git/index absent (interrupted clone/checkout) [], exit 0 ['big.bin'] True False

The first three need a user-staged un-tracking; devlaunch never produces them and they heal on commit/reset. The fourth does not require any user action and is the one that bites hardest: a missing .git/index makes git ls-files succeed with empty output, so the gate reads "no tracked files → no pointers → skip". That directly contradicts the function's own stated contract — "Fails open: an unlistable index means 'can't tell', not 'no LFS'" — which holds only for a corrupt index (exit 128), not an absent one (exit 0, empty). test_unreadable_index_still_probes and test_unlistable_index_fails_open_to_probing both model only the erroring shape, so nothing pins this. And the retry path exists precisely to recover from interrupted operations, which is exactly when an index goes missing.

What is blocking is the combination: a soundness proof stated in code, CHANGELOG and PR body that is false as written, plus one verified divergence reachable without user action, on a seam that #163 is about to build on. A false soundness proof in a comment is what killed approach 1.

Fix is small — either union HEAD into the scan (git ls-tree -r -z --name-only HEAD), and/or treat "zero tracked entries" as can't-tell; and downgrade the wording to the claim that is actually true.

S2 — non-blocking. #142: "a non-LFS repo pays (at most) one cheap local check instead of a git lfs fork." It pays a git ls-files fork plus one open() per tracked file, not an in-process check. The intent is met and the win is real.

S3 — non-blocking. The disclosed surviving mutant's stated safety argument is wrong — see ruling below.

No regression found in: submodules (gitlink → IsADirectoryError → False, correct), sparse-checkout cone, sparse index, skip-worktree (on and off disk), assume-unchanged, linked worktree, unborn HEAD / empty repo, staged-only pointer, subdirectory pointers, UTF-8 / space / quote paths. Pointer-prefix variants (BOM, legacy hawser/git-media) and newline-in-path are missed identically by base and head — pre-existing, not introduced here.

The three original findings

# Prior blocking finding Verdict
1 Pointer committed with no attributes anywhere CLOSED. Real repo: check-attr → filter: unspecified, git lfs ls-files → ['big.bin'], head _has_lfs_pointers = True.
2 Attributes from outside the clone (core.attributesFile, /etc/gitattributes) CLOSED. Real repo, no in-clone .gitattributes, check-attr → filter: lfs, head _has_lfs_pointers = True. The mechanism is now moot — the predicate never reads attributes.
3 The mis-gate is permanent CLOSED. Three consecutive calls on one workspace → [True, True, True]; after writing real content → False. The predicate is stateless and content-only, so it cannot latch.

The .git/info/attributes mutant that previously survived is killed by deletion — the line is gone with the machinery it belonged to.

Ruling on the soundness argument

Rejected as stated; the idea is sound, the proof is not. The defensible claim is narrower: every path git lfs ls-files names is in HEAD or the index — and the gate enumerates only the index. Everything the PR needs still follows for any workspace whose index matches HEAD, which is every workspace devlaunch itself creates. Either widen the scan to the union, or state the precondition instead of claiming construction.

Ruling on the surviving opposite mutant (except OSError: return True)

Should be pinned by a test, and the disclosure's reasoning should be corrected. The survival reproduced (53 passed; also 1174 passed full suite). But the disclosure's justification — "it can only cost a git-lfs fork, never strand a workspace" — is false at the second call site (workspace_clone.py:192): on a real sparse-checkout repo whose LFS file is not on disk, head returns False (matching base's except OSError: continue) while the mutant returns True, which drives _materialize_lfs into git lfs pull origin — an uncaptured, potentially multi-gigabyte fetch — on every launch, forever. That is a behaviour change against #142's "materializes exactly as today", not a free extra fork. One test (LFS-tracked path absent from disk → False, no git lfs pull) closes it. Non-blocking, because the shipped direction is the correct one.

#163

Stays correct. The predicate asks about bytes on disk, so materialization from a local file://<bare> store or via hardlinks into <bare>/lfs answers identically — a hardlinked real object is not a pointer (no fork); a stub still is. The linked-worktree claim is verified: in a real linked worktree (.git is a file) head gives _has_lfs_pointers = True, where approach 1's .git/info/attributes read was a silent no-op. Caveat: S1's index/HEAD gap applies to linked worktrees too, and #163 makes those the norm.

Record

The re-measurement is honest: direction and order of magnitude reproduced independently (123 files 34.7→4.4ms; 3000 files 127.1→26.5ms; 50 000 files 1340.9→395.9ms), the LFS-with-pointers row is described as a cost, and the withdrawal of the old ~72ms→~4ms figure is stated in the PR body. The self-correction about codecov/patch is posted on #142; the record is straight. Red-first verified against the previous head 46122455 failed, 5 passed, one more than the claimed 4 failed, benign (that run predates the 10th test).


Verdict

Request changes (posted as a comment — GitHub refuses --request-changes and --approve on a same-account PR; the written verdict is the gate).

This is the re-verdict after the retry. All three original blocking findings are closed, verified by execution against real git and real git-lfs. The rewrite is a clear improvement and deletes more than it adds.

Blocking, one item:

  1. S1 — the "behaviour-preserving by construction" proof is false as written (git lfs ls-files = HEAD ∪ index; the gate reads the index only), and one divergence is reachable with no user action: an absent .git/index makes git ls-files exit 0 with empty output, so the gate skips where base materialized — contradicting the function's own "fails open / can't tell" contract, which only covers a corrupt index. Union HEAD into the scan and/or treat zero tracked entries as can't-tell, and correct the wording in workspace_clone.py:128-129, the PR body and CHANGELOG.md. #163 builds directly on this seam.

Recommended, not gating:

  1. Pin the _is_lfs_pointer OSError direction with a test (both axes raised this independently; the mutant survives the full 1174-test suite).
  2. Correct the disclosure's claim that the opposite mutant is free — it forces an unbounded git lfs pull on every launch for sparse checkouts.

Not merging; merge stays human.

blooop added 2 commits August 9, 2026 12:46
The gate in front of the git-lfs probe carried a soundness proof that was
false as written. It claimed every path `git lfs ls-files` can name is a
tracked path, and concluded the skip was behaviour-preserving by
construction. `git ls-files` reads the index; `git lfs ls-files` reads
the union of HEAD's tree and the index, so the second set is strictly
larger.

One of the divergences needs no user action to reach. A clone left with
no `.git/index` -- an interrupted clone or checkout, which is exactly
what the materialization retry exists to recover from -- makes
`git ls-files` exit zero with empty output, and the gate read that as
"nothing tracked, therefore no pointers" and skipped. That contradicted
the function's own stated contract, which fails open on an *unlistable*
index; both existing fail-open tests model only the erroring shape.

`--with-tree=HEAD` makes git enumerate that union in the same single
fork, so the narrower claim -- every path git-lfs can name is in HEAD or
in the index -- is now what the code asks and what the prose says.
Measured cost of the union over the index alone: ~6% (17.5ms vs 16.5ms
at 3000 files, 202ms vs 190ms at 50 000), against ~119ms and ~1180ms for
the probe it stands in front of.

Also pins the direction of _is_lfs_pointer's OSError handling, which
survived the full suite. The shipped direction is correct, but the
disclosure's reason for leaving it unpinned was wrong: the flip is not a
free extra fork. At the materialization call site it drives
`git lfs pull origin` -- unbounded and uncaptured -- on every launch of
any workspace with an LFS-tracked path off disk, forever, because the
pull cannot put back a path the checkout excludes.

And: the probe's cost is now described as what it is (a fork plus one
open per listed path, same O(n) shape at a smaller constant) rather than
as "one cheap local check"; the integration repos switch signing off so
a developer's `commit.gpgsign` stops failing a dozen LFS tests for
reasons unrelated to LFS; and 32 bytes are no longer labelled twelve.

@blooop blooop left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during review.

Final re-verdict. Third review of this node, after two Request changes rounds and one final retry. Fresh reviewer: did not write this code and did not write either previous review. Head 9f0df8a0ea342b09ce0e4d4c3dcecd0d2bd96eff, fixed point = merge-base with main cc95eec8d4ba06544e78621d6f3ddc45fd1b26ea (git diff cc95eec...HEAD — 4 files, +568/−30).

Preflight, confirmed by me rather than inherited: gh api .../actions/runs?head_sha=9f0df8a --jq .total_count3; every check SUCCESS or SKIPPED including codecov/patch and codecov/project; mergeable: MERGEABLE, mergeStateStatus: CLEAN, not draft.

The approach was rewritten twice, so most of this diff has never been reviewed in its current form. Both axes reviewed it as new code, not as a delta on a previous head. Axes ran as parallel subagents and are reported unmerged and unranked. Both were given the integrity brief verbatim; neither was given any prior review's findings as fact, only as leads to reproduce. The builder's pasted evidence was re-run, never accepted.

Machine note: three agents ran concurrently on this box. Every timing below is a loaded-machine number and is directional only.


Standards

No blocking findings. All five mandated mutants killed under full-suite selection; pixi run -e py310 ci green end to end; no hardcoded /home/<user> on any added line; test/test_devpod_spawn_counts.py correctly untouched (it counts devpod forks; this diff changes only git forks, and the equivalent guard here — test_workspace_without_pointer_files_never_forks_git_lfs / test_ordinary_repo_never_forks_git_lfs — is proven CI-effective by mutant M5).

  1. Non-blocking — a test that guards nothing its neighbour doesn't. test/integration/test_lfs_probe_real.py:246 (test_materialized_workspace_needs_no_further_pull) has a body byte-identical to :284 (test_materialized_lfs_repo_never_forks_git_lfs) except that :284 asserts a strict superset. No implementation change can redden :246 while :284 stays green. Duplicated Code. (Read finding, established by diffing the two bodies — not an execution result.)
  2. Non-blocking — second structurally hollow test. test/integration/test_lfs_probe_real.py:230 (test_unmaterialized_pointer_is_detected_on_every_launch) calls check_pointers twice and asserts True both times, but _has_lfs_pointers (devlaunch/worktree/workspace_clone.py:202-219) is a stateless classmethod with no cache, so the second call cannot differ from the first. Its setup and first call duplicate :99. (Read finding; no deterministic mutant separates them, and none was attempted.)
  3. Non-blocking — documented behaviour with no test. workspace_clone.py:167-168 claims an unborn HEAD "lands there too, and pays one probe". Verified true (git ls-files -z --with-tree=HEAD in a fresh git init → exit 128, fatal: tree-ish HEAD not found, git 2.55.0), but nothing pins it — and the flag turned a previously silent, free path into a logger.warning plus a git-lfs fork on every launch of a commit-less workspace. Fail-open, so safe.
  4. Nittest/test_workspace_clone.py:33 (stub_git) matches on cmd[:2] == ["git", "ls-files"], so it is blind to --with-tree=HEAD. Confirmed by mutant M3: dropping the flag is killed only by the real-git integration tests. The unit file contributes zero argv coverage for the fix.
  5. Nit — the deleted-file + dangling-symlink + submodule fixture is repeated verbatim at test_lfs_probe_real.py:175-183 and :312-321.
  6. Nit_lfs_tracked_files (workspace_clone.py:186) lost its own shutil.which("git-lfs") guard to its one caller (:215) and lost the matching docstring clause. Harmless today; the function no longer owns its precondition.

For the record, not a finding: the CHANGELOG perf table (CHANGELOG.md:44-47, "~119ms → ~18ms at 3000 files") is byte-identical to its text at 4b7a1a4, where the gate still ran index-only. It was not re-measured after --with-tree=HEAD landed. Measured here: 3.4 → 4.9 ms/run at 3000 files, against 141.7 ms for git lfs ls-files. The conclusion survives comfortably; the published numbers are ~1.5 ms optimistic.

Standards evidence

Selection: pixi run -e py310 ci, end to end, clean tree at head.

pylint 4.0.5 / Python 3.10.20
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)
ty check --respect-ignore-files .  ->  All checks passed!
collected 1202 items / 23 deselected / 1179 selected
===================== 1179 passed, 23 deselected in 52.99s =====================
EXIT=0

Selection: the two touched test files alone57 passed in 3.11s.

Mutants, each applied by this review and each run over the FULL SUITE (pixi run -e py310 test); git status --short empty after restore:

M1  _is_lfs_pointer: except OSError: return False -> return True   KILLED   2 failed, 1177 passed
      test_tracked_paths_that_will_not_open_are_not_read_as_pointers
      test_lfs_path_missing_from_the_working_tree_is_not_pulled_forever
M2  _may_hold_lfs_pointers: fail-open True -> False                KILLED   2 failed, 1177 passed
      test_unreadable_index_still_probes / test_unlistable_index_fails_open_to_probing
M3  drop --with-tree=HEAD from the argv                            KILLED   2 failed, 1177 passed
      test_pointer_is_detected_when_the_clone_has_no_index / test_pointer_only_in_head_is_detected
M4  _may_hold_lfs_pointers: return any(...) -> False               KILLED  10 failed, 1169 passed
M5  delete the gate call from _has_lfs_pointers                    KILLED   4 failed, 1175 passed
      test_ordinary_repo_never_forks_git_lfs / test_materialized_lfs_repo_never_forks_git_lfs
      test_tracked_paths_that_will_not_open_are_not_read_as_pointers
      test_workspace_without_pointer_files_never_forks_git_lfs

Hostile global git config (commit.gpgsign=true with a bogus signing key, verified to break an unguarded commit with gpg: skipped ...: No secret key), selection new integration file only: 13 passed in 1.90s. Under the same config the full suite shows 29 errors + 1 failure, all in files this PR does not touch — corroborating the new file's own docstring note.

No /home/<user> on any added line of git diff cc95eec...HEAD: NONE FOUND. prek run -a: EXIT=0.

Standards — not run

pixi run test-e2e / any -m e2e test (needs a real Docker daemon). Any environment other than py310 — py311/312/313 not run. ./dev.sh / dl-next / any real workspace launch — not run. The CHANGELOG's own 124-file and 50 000-file figures — not reproduced (only a 3000-file synthetic case). A mutant separating finding 2's test from its neighbour — not attempted. Hostile core.excludesFile / core.hooksPath — not tested. Whether the 29 hostile-config errors also occur at merge-base — not run.

Axis verdict: approve.


Spec

Spec = ticket #142. No blocking findings.

S1 — Non-blocking. The soundness argument is empirically true, but as written it states as definitional two behaviours neither tool documents. devlaunch/worktree/workspace_clone.py:138-140. Against #142's "A repo WITH filter=lfs attributes still probes and materializes exactly as today (existing tests stay green)", the claim was attacked across 32 repo shapes with real git 2.55.0 / git-lfs 3.7.1, and no shape was found where BASE=True and HEAD=False — the silent-stranding direction that blocked rounds 1 and 2. That includes absent / corrupt / zero-byte index, rm --cached, rm -r --cached ., read-tree --empty, unborn HEAD, detached HEAD, dangling HEAD symref, sparse cone / non-cone / sparse index, skip-worktree, assume-unchanged, linked worktree, gitlink, merge conflict, staged-only, HEAD-only, subdirectory, spaced / quoted / newline / non-UTF8 paths, no-attributes, and core.attributesFile. A sparse index does not yield collapsed dir/ entries — ls-files expands it. Round 2's S1 is genuinely fixed. But git ls-files --help documents --with-tree only in relation to --error-unmatch (this call site uses neither that flag nor a pathspec), and git lfs ls-files --help says only "scan the currently checked-out branch", never mentioning the index. The union is real; it is an empirical property of two tool versions, not the construction the docstring asserts. Given that a false proof-in-a-comment killed both previous rounds, the wording should say which it is. Not blocking, because both behaviours are now pinned by real-git tests (M3/M2 killed, full suite) rather than resting on the comment.

S2 — Non-blocking, disclosed. #142: "a non-LFS repo pays (at most) one cheap local check instead of a git lfs fork." It pays a git ls-files fork plus one open() per listed path (workspace_clone.py:171, :178-182). The PR body, the docstring at :145 and the CHANGELOG all now say so explicitly. Win confirmed independently: 4.4ms vs 31.3ms.

S3 — Nit. #142: "repo without LFS attributes → no git lfs subprocess is spawned." The shipped condition is pointer content, not attributes, so an attribute-free repo whose tracked file merely begins version https://git-lfs still forks. Cost-only, untested, and rounds 1–2 proved the literal attribute condition unsound — but it is a deviation from the bullet's literal text.

S4 — Nit, downstream. Ticket #163 says it "inherits its attributes gate" and specifies a unit test "a repo with no LFS attributes issues no git lfs command at all." That is no longer the gate's condition; written as stated that test would be vacuous or wrong. #163's phase-3 re-check of _has_lfs_pointers after hardlinking is correct against this seam — content-based and verified stateless.

Done-when bullets of #142

Spec line Verdict Evidence
"A failing test first: repo without LFS attributes → no git lfs subprocess is spawned" Met 4 tests red at base cc95eec (below)
"A repo WITH filter=lfs attributes still probes and materializes exactly as today (existing tests stay green)" Met 32-shape matrix HEAD ≡ BASE; the 3 edited existing LFS tests pass unmodified against base source, so the stub_git rewrite encodes no new behaviour
"Cold-path only — no behavior change on warm launches" Met dl.py:2363-2367 fast path returns before _get_clone_manager(); ensure_workspace reached only at dl.py:2390 in the else
"CI green (pixi run ci, check py310)" Met verified by the Standards axis end to end (exit 0) and by the remote checks; not run by this axis
"The existing deliberate choice at workspace_clone.py:350-352 … must be preserved: the gate must be a property of the repo content, not of launch history" Met comment + ungated _materialize_lfs call intact at workspace_clone.py:421-424; gate is stateless — [True, True, True], False after materialization, True again when re-pointered

Spec evidence

Divergence matrix, real git 2.55.0 + real git-lfs 3.7.1, head tree vs base tree cc95eec (selection: direct _has_lfs_pointers calls, not pytest). Abridged; all 32 rows show no BASE=True / HEAD=False:

shape                  git lfs ls-files       gate   HEAD   BASE
corrupt_index          rc0 big.bin            True   True   True
no_index               rc0 big.bin            True   True   True
no_index_no_attrs      rc0 big.bin            True   True   True
zero_index             rc0 big.bin            True   True   True
head_dangling          rc0 big.bin            True   True   True
rm_cached              rc0 big.bin            True   True   True
rm_r_cached            rc0 big.bin            True   True   True
read_tree_empty        rc0 big.bin            True   True   True
staged_only            rc0 big.bin            True   True   True
unborn_staged          rc0 big.bin            True   True   True
sparse_cone            rc0 exc/big.bin        True   True   True
sparse_index           rc0 exc/big.bin        True   True   True
sparse_noncone         rc0 exc/big.bin        True   True   True
skip_worktree          rc0 big.bin            True   True   True
assume_unchanged       rc0 big.bin            True   True   True
linked_wt              rc0 big.bin            True   True   True
gitlink                rc0 big.bin            True   True   True
conflict               rc0 big.bin            True   True   True
no_attrs               rc0 big.bin            True   True   True
out_of_clone_attrs     rc0 big.bin            True   True   True
weird_paths            rc0 new,line.bin,...   True   True   True
materialized           rc0 big.bin            False  False  False
ordinary               rc0                    False  False  False
nonutf8                rc0 \udcff\udcfe.bin   True   EXC:UnicodeDecodeError (identical at base — pre-existing)

The sparse index really was sparse, and ls-files expands rather than collapsing:

$ git -C sparse_index config --get index.sparse
true
$ git -C sparse_index ls-files --sparse --with-tree=HEAD
hint: The sparse index is expanding to a full index, a slow operation.
.gitattributes
exc/big.bin
inc/a.txt

Mutants, applied by this axis and run over the FULL SUITE (1179 collected, default pixi env, python 3.14.3), restored with git checkout -- .:

M1  _is_lfs_pointer  except OSError -> return True         KILLED   2 failed, 1177 passed
      E AssertionError: assert ['git','lfs','pull','origin'] not in [... ['git','lfs','pull','origin']]
      INFO devlaunch.worktree.workspace_clone: Fetching git-lfs objects from origin
M2  drop --with-tree=HEAD (reverts the round-2 S1 fix)     KILLED   2 failed, 1177 passed
M3  gate fail-open True -> False on rc != 0                KILLED   2 failed, 1177 passed
M4  gate always False                                      KILLED  12 failed, 1167 passed
M5  drop the relocated shutil.which("git-lfs") guard       KILLED   4 failed, 1175 passed
M6  gate always True (no gating at all)                    KILLED   4 failed, 1175 passed

M5 confirms the shutil.which relocation into _has_lfs_pointers:215 is behaviour-preserving and guarded: with git-lfs absent, head returns False before any fork, exactly as base did.

Red-at-base (selection: test/test_workspace_clone.py test/integration/test_lfs_probe_real.py, head tests on base source cc95eec) — this also proves the three edited existing LFS tests are not tuned to the gate, since they pass at base:

E AssertionError: assert not True
E  +  where True = forked_git_lfs([['git','lfs','ls-files','--name-only']])
FAILED test_workspace_without_pointer_files_never_forks_git_lfs
FAILED test_ordinary_repo_never_forks_git_lfs
FAILED test_materialized_lfs_repo_never_forks_git_lfs
FAILED test_tracked_paths_that_will_not_open_are_not_read_as_pointers
4 failed, 53 passed in 0.98s

Spec — not run

pixi run ci / pixi run -e py310 cinot run by this axis (pytest only, on python 3.14.3 from the default env); ruff / pylint / ty / coverage not run; the py310 claim rests on the Standards axis and the remote check. e2e suite not run. No end-to-end dl launch — every result is at the _has_lfs_pointers / _may_hold_lfs_pointers / ensure_workspace seams. /etc/gitattributes not tested (no root); inferred from core.attributesFile, the same git code path. Warm-launch timing not measured (assessed by reading dl.py:2363-2390). Partial/promisor clones and >10k-file repos not tested.

Axis verdict: approve.


Reviewer's own independent verification

Because this is the deciding verdict, I re-ran the load-bearing items myself rather than relying on a subagent self-report. Verbatim output from this session, in my own worktree at 9f0df8a, clean tree before and after.

Baseline, selection = FULL SUITE, py310:

1179 passed, 23 deselected in 56.29s

The OSError mutant, applied by me, selection = FULL SUITE, py310 — KILLED, one failure per call site:

MUTANT APPLIED: _is_lfs_pointer except OSError -> return True
>       assert ["git", "lfs", "pull", "origin"] not in issued
E       AssertionError: assert ['git', 'lfs', 'pull', 'origin'] not in [['git', 'fetch', 'origin'],
        ['git', 'checkout', 'nb4'], ['git', 'ls-files', '-z', '--with-tree=HEAD'],
        ['git', 'lfs', 'ls-files', '--name-only'], ['git', 'lfs', 'pull', 'origin']]
INFO     devlaunch.worktree.workspace_clone:workspace_clone.py:234 Fetching git-lfs objects from origin
FAILED test/integration/test_lfs_probe_real.py::TestProbeCostAgainstRealRepos::test_tracked_paths_that_will_not_open_are_not_read_as_pointers
FAILED test/test_workspace_clone.py::TestEnsureWorkspace::test_lfs_path_missing_from_the_working_tree_is_not_pulled_forever
2 failed, 1177 passed, 23 deselected in 74.79s

The git lfs pull origin in that argv list is the unbounded fetch the previous head's disclosure said the flip could not cause. The correction now stated in the PR body and in workspace_clone.py:119-126 is accurate.

The builder's disclosure — "only two of the four new tests were red-first" — checked in both directions. Source reverted to merge commit 4b7a1a4, head tests kept.

Selection = the four named node IDs:

FAILED test/integration/.../test_pointer_is_detected_when_the_clone_has_no_index
FAILED test/integration/.../test_pointer_only_in_head_is_detected
2 failed, 2 passed in 1.24s

Selection = FULL SUITE (so the red is CI-effective, not an artefact of -k):

FAILED test/integration/.../test_pointer_is_detected_when_the_clone_has_no_index
FAILED test/integration/.../test_pointer_only_in_head_is_detected
2 failed, 1177 passed, 23 deselected in 81.27s

Disclosure accurate in both directions: exactly two red-first, and they are the two named; neither more nor fewer. The other two are genuine mutant-only pins, independently shown load-bearing by the OSError mutant above.

Base-vs-head divergence hunt, my own 17 shapes, real git 2.55.0 / git-lfs 3.7.1 (independent of the Spec axis's 32) — BASE True / HEAD False was found nowhere:

shape                                    BASE   HEAD   verdict
untouched (pointer + attrs)              True   True
no attributes at all                     True   True
index deleted                            True   True
index corrupt (garbage)                  True   True
git rm --cached big.bin                  True   True
git rm -r --cached .                     True   True
git read-tree --empty                    True   True
detached HEAD                            True   True
skip-worktree on big.bin                 True   True
assume-unchanged on big.bin              True   True
HEAD -> nonexistent ref                  True   True
pointer staged only (not committed)      True   True
pointer in subdir                        True   True
path with space+quote                    True   True
path with newline                       False  False   (pre-existing: base misses it too)
linked worktree (.git is a file)         True   True
sparse cone: pointer OUT of cone        False  False   (pre-existing: base cannot open it either)

The exact shape that blocked round 2 — absent .git/index:

=== index deleted ===
-- git ls-files (exit?):
rc=0                                  <- empty output, exit 0: the trap
-- git ls-files --with-tree=HEAD:
.gitattributes
big.bin
plain.txt
rc=0
-- git lfs ls-files --name-only:
big.bin

Round-1 findings, re-verified by me at this head:

permanence, 3 consecutive calls, pointer on disk: [True, True, True]
after writing real content:                       False
core.attributesFile repo -> check-attr: big.bin: filter: lfs
core.attributesFile repo -> HEAD _has_lfs_pointers: True
git-lfs absent from PATH -> _has_lfs_pointers: False

Perf, my own measurement (loaded machine, median of 15):

this repo checkout, 124 tracked files
  gate  (git ls-files --with-tree=HEAD + scan): median 4.2ms  min 3.3ms
  probe (git lfs ls-files --name-only)        : median 31.9ms min 27.5ms

That corroborates the PR's headline "~34ms → ~4ms" row.

git grep '/home/' over every file in the diff: no hits at all.

Not run by me (as opposed to by an axis): pixi run ci end to end, e2e, py311/312/313, any real dl-next launch, /etc/gitattributes, and the CHANGELOG's 3000-file and 50 000-file rows.


Every prior finding, closed or open

Round Finding Status
R1 blocking 1 Pointer committed with no attributes anywhere → skipped and stranded CLOSED — my own run: _has_lfs_pointers = True; both axes agree
R1 blocking 2 Attributes git honours from outside the clone (core.attributesFile, /etc/gitattributes) invisible CLOSED — my own run: check-attr → filter: lfs, head True. Mechanism moot: the predicate reads no attributes
R1 blocking 3 The mis-gate is permanent on every launch CLOSED[True, True, True]False after materialization; predicate is stateless and content-only
R1 non-blocking .git/info/attributes branch untested (mutant survived) CLOSED by deletion — the branch and its machinery are gone
R1 non-blocking _lfs_attributes_declared lying predicate / sentinel True CLOSED by deletion
R1 non-blocking Docstring overclaims "declares none anywhere" CLOSED — superseded; see S1 for the new wording's residual issue
R1 non-blocking No before/after numbers CLOSED — CHANGELOG + PR body carry them; corroborated independently here (see the Standards record note on the 3000-file row being ~1.5 ms stale)
R1 non-blocking CHANGELOG not updated CLOSEDCHANGELOG.md +31
R2 blocking S1 "Behaviour-preserving by construction" proof false; git lfs ls-files = HEAD ∪ index; absent .git/index exits 0 empty CLOSED--with-tree=HEAD fixes it; verified by me and by the Spec axis across 17 + 32 shapes; mutant dropping the flag is KILLED under full-suite selection. The false claim is withdrawn in the code comment, the CHANGELOG and the PR body
R2 rec 2 Pin _is_lfs_pointer's OSError direction with a test CLOSED — two tests, one per call site; mutant KILLED under full suite by me and by both axes
R2 rec 3 Correct the disclosure claiming the opposite mutant is free CLOSED — PR body and workspace_clone.py:119-126 now state the git lfs pull consequence, and my mutant run reproduces that exact fetch
R2 Standards nit Tests inherit the developer's global git config (commit.gpgsign) CLOSED for this filemake_repo sets commit.gpgsign false; 13/13 pass under a hostile config. Other integration files still exposed, explicitly out of scope
R2 Standards nit test_lfs_probe_real.py wrote 32 bytes as "twelve bytes" CLOSEDREAL_CONTENT with assert len(REAL_CONTENT) == 12 at import
R2 Standards nit Double-negative naming on _may_hold_lfs_pointers OPEN, nit — unchanged, not gating

New this round, none blocking: two structurally redundant tests in the new integration file, the untested unborn-HEAD warning path, the flag-blind stub_git fake, the stale 3000-file CHANGELOG row, the S1 wording, and #163's now-stale "inherits its attributes gate" description.

Pre-existing and not introduced here: a non-UTF8 tracked path raises UnicodeDecodeError out of _lfs_tracked_files — identical at base.


Verdict

Approve — no blocking findings on either axis. (Posted as a --comment review: GitHub refuses --approve on a PR authored by the same account. The written verdict is the gate.)

This is the final re-verdict for this node. All four prior blocking findings — R1's three and R2's S1 — are closed, each re-verified here by execution against real git 2.55.0 and real git-lfs 3.7.1 rather than carried from the earlier reports. Both round-2 recommendations are closed too. The one prior finding still open is a naming nit.

What earned the approval, stated as evidence rather than as sentiment: the soundness claim that killed rounds 1 and 2 was attacked adversarially by two independent workers across 32 and 17 repo shapes and no BASE=True / HEAD=False divergence exists, including the exact absent-.git/index shape that blocked round 2 and including sparse indexes; every mutant of the new gate — six of them, plus the OSError flip — dies under full-suite selection, not merely under a narrowed -k; and the builder's own disclosure that only two of its four tests were red-first is accurate in both directions, which is the honest under-claim this review was asked to confirm and did.

Recommended, none gating, best folded into #163's work on this seam:

  1. Reword workspace_clone.py:138-140, CHANGELOG.md and the PR body so the HEAD ∪ index union reads as an empirically verified, test-pinned property of git 2.55 / git-lfs 3.7 — --with-tree is documented only in relation to --error-unmatch, and git-lfs never documents reading the index — rather than as construction. Two false proofs-in-a-comment have already cost this node a round each.
  2. Update #163's "inherits its attributes gate" wording and its planned "no LFS attributes → no git lfs command" test; neither describes the shipped condition.
  3. Drop or strengthen test_lfs_probe_real.py:246 and :230, which cannot fail independently of their neighbours.
  4. Re-measure the 3000-file CHANGELOG row now that the gate asks the union.

Not merging; merge stays human.

@blooop
blooop merged commit 39d4d2a into main Aug 9, 2026
11 checks passed
@blooop
blooop deleted the perf/142-gate-lfs-probe branch August 9, 2026 14:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants