Repository navigation
Progress reporting on the startup scan - #1000
Merged
Merged
Conversation
This was referenced Aug 20, 2026
thekevinscott
force-pushed
the
claude/tackle-957-lrm0z6
branch
from
August 21, 2026 02:16
7864bae to
b663404
Compare
Captures dirsql#957's contract before any implementation exists: a long startup scan draws a labeled progress line on stderr, erases it, and leaves one summary line saying what the run cost -- while a piped run stays byte-for-byte silent so `| jq` pipelines and CI logs are unchanged. `DIRSQL_PROGRESS` is the gate (`auto` / `always` / `never`); `always` is what makes the drawn output assertable from a subprocess without a pty. Three tests fail on the empty stderr they capture today; the three silence tests pass now and guard the property through the change. skip-changelog: red test commit, no observable change yet Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019gWqG2prbmJg5Pz4caGjnn
Turns the red e2e green. A cold scan over a large corpus was silent: the walk visits every file, then each matched file costs an `on-file` round trip plus whatever the table's `ddl` fires on insert. On a big tree that is minutes with nothing on the terminal, and no way to tell a slow scan from a wedged one. Both phases now draw a counter rewritten in place -- the walk a running count (it cannot know a total until it has found one), the ingest a fraction -- erased when the phase ends and replaced by one summary of what it cost. Three constraints shaped it: - A pipe stays silent. The default consults `stderr.is_terminal()` before writing a byte, so `| jq`, `2>` redirects and CI logs are byte-for-byte unchanged. `DIRSQL_PROGRESS=always` forces it on, `=never` off. - Fast work stays invisible. Under the default a phase draws nothing until it has run half a second, so a quick scan leaves the terminal exactly as it found it. - No new dependencies. The line is a `\r`-rewritten counter, not a drawn bar: a bar wants the terminal width and every portable way to ask for it is a crate. A counter reads the same at any width. The reporter lives in the core, driven from `prepare_resolved` and `finish_build`, so it governs an index built from any SDK rather than only the CLI -- and `DIRSQL_PROGRESS` is read there too, not by a launcher. `scan_directory` keeps its signature and delegates to a new `scan_directory_reporting` with a no-op callback, so benches and in-workspace callers are untouched. Fixes #957 (core half; the embeddings-plugin half is its own slice). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019gWqG2prbmJg5Pz4caGjnn
`cargo mutants --in-diff` over this branch's diff reports one survivor: `seen += 1` -> `seen *= 1` in `scan_directory_reporting`. The walk would report a frozen `0 files` forever and every test still passed, because the e2e only asserted the phase label. Fixed with an assertion, not a weaker test: the walk now has to summarize the four files it actually reaches (three fixture files plus the `.dirsql.toml`). 49 mutants, 0 missed after this. Worth recording how the survivor surfaced: the repo's `unit mutation` gate reports `no surviving mutants (0 mutant(s) tested)` on this diff, locally and in CI, over 662 inserted lines of rust source -- the signature AGENTS.md says to treat as a gate bug rather than a pass. Run directly, `cargo mutants` finds 49. See the PR body. skip-changelog: test-only, no observable change Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019gWqG2prbmJg5Pz4caGjnn
thekevinscott
force-pushed
the
claude/tackle-957-lrm0z6
branch
from
August 21, 2026 15:12
b663404 to
4312081
Compare
Merged
8 tasks done
This was referenced Sep 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #957 (core half). The other half is #1001, implemented in #1002, stacked on this branch.
Problem
A cold scan over a large corpus is silent. The walk visits every file, then each matched file costs one
on-fileround trip plus whatever the table'sddlfires on insert. With #956's vector-index build that is anembed()round trip per ingested row — 41k rows on the reference corpus, minutes of wall time, nothing on the terminal. There is no way to tell a slow scan from a wedged one.What this does
Both phases now draw a counter on stderr, rewritten in place:
The walk reports a running count (it cannot know a total until it has found one); the ingest reports a fraction. When a phase ends its line is erased and replaced by one summary of what it cost:
stdout is untouched on every path.
Three constraints shaped it
stderr.is_terminal()before writing a byte, so| jq,2>redirects and CI logs are byte-for-byte unchanged. Two of the six e2e tests exist only to pin that.\r-rewritten counter rather than a drawn bar: a bar wants the terminal width and every portable way to ask for it is a crate. A counter reads the same at any width.The knob
DIRSQL_PROGRESS, read by the core rather than by a launcher, so it governs an index built from any SDK:autoalways,1,truenever,0,falseCase-insensitive, whitespace-trimmed; anything unrecognized reads as
auto, so a typo cannot stop a scan from running.alwaysskipping the warmup is deliberate and does double duty: it is the "watch my redirected run" affordance, and it is what makes the drawn output assertable from a subprocess e2e test without a pty.Notes for review
scan_directorykeeps its signature and delegates to a newscan_directory_reporting(root, matcher, &mut on_file), so the benches and the ten call sites intests/scanner.rsare untouched.impl Drop for Progresserases the live line, so an error mid-ingest cannot leave a half-drawn counter under the error message the caller is about to print.prepare_resolvedandfinish_buildmay run on different threads for the async bindings.progress=argument. That would be exactly the per-SDK surfacePARITY.mdexists to prevent; the env var is the whole knob.Red/green
6bc3b4cpushed the e2e first. CI went red onIntegration tests(run 32417694440):test result: FAILED. 3 passed; 3 failed— the three progress assertions failing on the empty stderr they captured, the three silence assertions passing. That commit also reddenedLint, because the new test file had not been rustfmt'd; fixed in the implementation commit.eed9548is the implementation, which flips those three green.0 mutant(s) testedon this diffWorth a maintainer's eye, because it is not specific to this PR.
rust / Unit mutation — changed linespasses here in 18 seconds with:over a diff with 662 inserted lines under
packages/rust/src, with the right base (BASE: 63d786008f…) and the right scan path (packages/rust). AGENTS.md says to treat exactly that signature as a bug rather than a pass. It reproduces locally withnpx -y testing-conventions unit mutation --language rust --base origin/main packages/rust, so it is not a CI artifact.Run directly against the same diff, cargo-mutants finds plenty:
And the one survivor was real:
seen += 1→seen *= 1inscan_directory_reporting, i.e. the walk could report a frozen0 filesforever with every test still green, because the e2e only asserted the phase label. Fixed in7864baewith an assertion, not a weaker test — the walk now has to summarize the four files it actually reaches. 0 missed after that.I have not touched the gate; it is upstream (testing-conventions) and out of scope here. Flagging it because everything the rust
mutationgate has passed since it landed may have been passing this way.Manual run
AGENTS.md asks for the feature to be exercised, not just tested. Over 41 files with a 50 ms-per-file hook:
Under a real pty (
script -qec …, no env var set) the ingest phase starts reporting at10/41— half a second in, as designed — and the walk phase, which finishes instantly, stays completely silent. Piped with no env var, stderr is empty.E2E Verification
--testsruns every suite underpackages/rust/tests/, including the newscan_progress_e2e)packages/python/e2e-attestations/claude-tackle-957-lrm0z6.jsonreceipt writtenpackages/ts/e2e-attestations/claude-tackle-957-lrm0z6.jsonreceipt writtencargo test --manifest-path packages/rust/Cargo.toml --features cli --lib→ 705 passed (25 new)cargo test --manifest-path packages/rust/Cargo.toml --features cli --tests→ all suites passedcd packages/python && uvx testing-conventions e2e attest 'uv run python -m pytest tests/e2e/ -x -q'cd packages/ts && uvx testing-conventions e2e attest 'pnpm test:e2e'cargo clippy --workspace -- -D warnings,cargo fmt --all -- --check→ cleancargo mutants --in-diff … --package dirsql --features cli→ 0 missed after7864baepackages/rust/srcchange stales both bindings' attestations via[e2e].extra_scope.Changelog / Migrations
packages/rust/changelog.d/2026-08-20-scan-progress-reporting.mdpackages/rust/migrations.d/2026-08-20-scan-progress-reporting.md— required, because this changes runtime behavior without an API change: a build that used to write nothing to stderr may now write to it.DIRSQL_PROGRESS=neveris the documented restore.Parity
PARITY.mdupdated. No drift: the reporter lives in the shared core and is driven fromprepare_resolved/finish_build, so all three SDKs got it in the same commit and none carries its own copy. No follow-up issues needed for lagging SDKs.