fix(list): Keep merge analysis sandbox-safe - #3512
Conversation
worktrunk-bot
left a comment
There was a problem hiding this comment.
Redirecting the object-writing plumbing (write-tree, commit-tree, merge-tree) into a temporary object DB is the right core fix for a read-only .git/objects, and the tests around it are thorough. One design concern before this lands, though — the change also disables wt list's persistent caches in every repo, not just read-only sandboxes.
collect() now wraps the repo in with_temporary_object_directory() unconditionally (src/commands/list/collect/mod.rs), and that call succeeds in any normal repo, so temp-object mode is the default for every wt list and every wt switch picker (both route through collect::collect). In that mode, write_json_for_repo and write_with_lru (src/cache.rs) skip all persistent writes — the disk sha_cache (ahead/behind, merge-base, merge-tree-conflicts, merge-add-probe, diff-stats, is-ancestor, has-added-changes), the CI-status cache, the summary cache, and the MaxPrNumber ratchet.
That contradicts two contracts this PR leaves in place and doesn't update:
src/git/repository/mod.rs(Caching): "Expensive, worth persisting across invocations (merge-tree, patch-id, diff stats, ahead/behind) → the disksha_cache; content-addressed by SHA, so never stale."- The
ahead_behinddocstring insrc/git/repository/diff.rs: "wt listpopulates this per branch (and in bulk … when most entries are cold); subsequent runs are pure cache reads."
The concrete cost: after this change, back-to-back wt list runs re-fork merge-base/merge-tree per row (the merge_base_by_sha docstring cites 10–25 ms/row on macOS) and re-fetch CI status over the network instead of reading the warm cache the previous run would have written — a regression on exactly the hot/network-sensitive paths CLAUDE.md's network section guards.
Root cause looks like a conflation of two orthogonal things: a read-only object database needs object writes redirected (which you've done), but the disk sha_cache lives under .git/wt/cache — a different location — and is content-addressed by SHA, so its entries are valid regardless of which object store computed them. There's no correctness reason to skip them. And write_json already "[d]egrades silently on any failure" (src/cache.rs), so a genuinely read-only .git/wt/cache never produced an error to begin with — the skip isn't buying sandbox-safety there either.
Suggested direction: keep the temp-object redirection scoped to the object-writing plumbing, but decouple persistent-cache skipping from uses_temporary_object_directory() — either drop the gate and rely on write_json's existing silent degradation, or gate on actual cache-dir writability. If disabling list's cross-run persistence is actually intended, the two docstrings above (and the network-behavior notes in CLAUDE.md) need updating to match — per CLAUDE.md, system docstrings must be kept current.
This is a maintainer design call rather than a clear-cut bug, so flagging for @max-sixty rather than blocking.
Anchors
- Unconditional wrap:
let (temporary_object_repo, temporary_object_error) = match repo.with_temporary_object_directory()incollect()(src/commands/list/collect/mod.rs). - The gate:
write_json_for_repoand the earlyreturninwrite_with_lru(src/cache.rs). - Callers of
collect::collect:src/commands/list/mod.rs(wt list) andsrc/commands/picker/mod.rs(wt switchpicker). - Statusline is unaffected — it calls
list::populate_item(&repo, …)with the plain repo, so its CI-status writes still persist; only thecollect()path changed. That asymmetry (statusline caches CI, list no longer does) is itself worth a second look.
d9d7dcf to
8c42250
Compare
worktrunk-bot
left a comment
There was a problem hiding this comment.
The rework addresses my earlier caching concern well — object-writing plumbing is now redirected into the temp object DB via the run_object_store_command helpers, the persistent sha_cache under .git/wt/cache is no longer gated off (the new test_list_temporary_object_store_preserves_persistent_cache_writes pins that), and the in-memory merge_tree cache is keyed by object_store_cache_id so temp and persistent stores don't hand each other tree SHAs that only exist in one store. The scoping (only WouldMergeAdd/MergeTreeConflicts/WorkingTreeConflicts skipped when the temp store can't be created, everything read-only still runs) reads correctly. Two CI failures to clear before this can land:
lint (cargo fmt) — one formatting diff in the new test, suggestion inline.
codecov/patch is red on the new plumbing. Uncovered added lines:
src/git/repository/mod.rs— theGIT_OBJECT_DIRECTORY-already-set branch (rev-parse --git-path objects) and theGIT_ALTERNATE_OBJECT_DIRECTORIESinherit-and-append branch inwith_temporary_object_directory, plus the command-failureErrarm inrun_object_store_command.src/git/repository/working_tree.rs— the non-temp (None) arm and the command-failureErrarm ofWorkingTree::run_object_store_command.
The env-var branches are the interesting gap: they're the sandbox-inheritance case this PR is meant to handle, and they're straightforward to exercise by setting GIT_OBJECT_DIRECTORY / GIT_ALTERNATE_OBJECT_DIRECTORIES in a test before calling with_temporary_object_directory() and asserting the temp store still resolves objects. The error arms and the None arm are defensible as low-value. (The panic! arms flagged in integration.rs are test-internal unreachable branches — ignore those.) codecov/patch gates merge here per CLAUDE.md even though GitHub marks it non-required.
Happy to push a commit with the fmt fix and a test covering the env-var branches if you'd like.
|
Thanks—the temporary object database and persistent caches are independent concerns. I removed the cache suppression, added a regression proving merge-conflict cache persistence, rebased onto current |
8c42250 to
e56648d
Compare
|
Fixed: |
worktrunk-bot
left a comment
There was a problem hiding this comment.
The rework looks good and the earlier design/caching concern is fully addressed — thanks for the thorough tests. One thing before this can land: codecov/patch is still red on this commit (it completed at 12:37Z, just after your last comment, so you may not have seen it yet). codecov/patch gates merge here per CLAUDE.md even though GitHub marks it non-required.
The env-var regression you added exercises the absolute GIT_OBJECT_DIRECTORY path, so path.is_absolute() is true and the else { self.discovery_path.join(path) } branch in with_temporary_object_directory — the relative-path case — is never hit. That plus a couple of defensive arms are the remaining uncovered lines.
Still-uncovered added lines (from the Codecov compare API)
Worth covering:
with_temporary_object_directory(src/git/repository/mod.rs) — theelse { self.discovery_path.join(path) }arm. A second variant of your inheritance test that pointsGIT_OBJECT_DIRECTORYat a relative path (git'srev-parse --git-path objectsechoes it verbatim) would exercise it.
Defensible-but-still-counted (CLAUDE.md: "remove unused code … where falling through to a general handler suffices" is a valid resolution too):
- The command-failure
Errarm ofRepository::run_object_store_commandand ofWorkingTree::run_object_store_command. - The non-temp (
else { command }) arm ofWorkingTree::run_object_store_command— in thewt listpath this helper only runs on a repo that already holds a temp object dir (theWorkingTreeConflictstask is dropped viarequires_object_store()when the temp store can't be created), so that arm is effectively unreachable in production. Either drop it (let the caller assume the temp env) or add a test that calls it on a plain repo.
Ignore: the three MergeTreeOutcome::Conflict => panic!(...) arms in the #[cfg(test)] mod merge_tree_cache_tests test are unreachable-by-design (the topology merges cleanly), and the lone brace flagged in collect/mod.rs is a region artifact inside the warning block your test_list_skips_object_probes_when_temporary_store_is_unavailable already covers.
Happy to push a commit adding the relative-path test case and either covering or trimming the two run_object_store_command defensive arms if you'd like — otherwise flagging for @max-sixty on the coverage-gate call.
e56648d to
5c9f6e3
Compare
Run existing object-writing list probes in a temporary object directory so conflict and integration analysis remains available with a read-only object database. Keep generated objects and derived cache entries out of persistent repository state; when temporary storage is unavailable, skip only merge analysis. Co-Authored-By: GPT-5.6 Sol <codex@openai.com>
5c9f6e3 to
ad76cd0
Compare
|
@worktrunk-bot please can you explain this PR and its tradeoffs? |
What it solves
How it works
Two supporting pieces make it correct:
Tradeoffs
The design concern I raised first time — that the rework also disabled the persistent caches in every repo — was resolved in the rebased branch, and the coverage gap on the env-var inheritance branches is now closed. All required checks plus |
|
ok, let's close this; it's quite bad that everything is no longer cached across runs even when it is writable — is the case with this PR? |
|
Good question to check before closing — the answer is no: this PR does not disable cross-run caching in the writable case. That was the regression in my first review (the original branch suppressed the persistent caches everywhere), but it was removed in the rebase, so the current state is:
So the caching objection doesn't apply here. The real remaining tradeoff is the design one from my earlier comment: temp-object mode is unconditional for every Want me to still close it, or leave it open pending that decision? Happy to do either. |
fix(list): Keep merge analysis sandbox-safe
🧢 Changes
☕️ Reasoning
wt listis observational, but its existing merge and conflict probes create Git objects. That made otherwise useful inventory incomplete in managed read-only sandboxes and could leave generated analysis artifacts behind.This removes a real-world agentic coding blocker: existing list analysis remains useful without elevation or persistent object-store changes.
🧪 Reproduction
With the repository object database read-only:
Before this fix, merge analysis could report:
✅ Verification
Developed with carefully directed, manually reviewed AI assistance.
Co-Authored-By: GPT-5.6 Sol codex@openai.com