Commit Graph
1 Commits
Author SHA1 Message Date
Neil 311325b9ea fix(worktrees): explain the object-store failure instead of leaking git's argv
What the user hits today: creating a workspace on a branch whose repository is
missing objects fails with a raw dump of git's stderr glued to the command Orca
ran — "Error invoking remote method 'worktrees:create': Error: Command failed:
git worktree add /Users/<name>/dev/worktrees/<branch> '<branch>' fatal: unable
to read tree (0413351...)". It leaks the user's home directory and full argv
into a toast (and into bug reports), and it says nothing about what to do next.
The create-time preflight does not catch it either: it peels the branch with
`^{commit}`, which reads only the commit object, so a repo whose commit object
survives while its root tree does not passes the preflight cleanly and then dies
inside the checkout.

After this change the same failure reads: "Orca could not create this workspace
because the repository object database is missing objects. Git reported: unable
to read tree (0413351...). The commit for "<branch>" is present but its root
tree object is missing, so Git could not check any files out. Run git fsck in
the repository to confirm what is missing, then re-fetch from the remote or
re-clone to restore it." No path, no argv. The sidebar row keeps the short title
and the creation panel shows the full diagnosis, which the row would truncate.

Not a preflight. Peeling `^{tree}` on every create costs a whole extra git
process (~15 ms on a 2.9 GiB repo) and still proves nothing about subtrees or
blobs, so it would slow every success for a partial guarantee. Git's stderr from
the failed command stays the authority; the `^{commit}` / `^{tree}` / promisor
probes run only after it fails, and only to add detail.

Evidence discipline, because this is exactly where the claim can outrun the
observation: the "commit is present but its root tree is missing" sentence is
emitted only when the commit peel actually succeeded, since a failed `^{tree}`
peel is equally what an unreadable *commit* produces. Probes distinguish "git
answered no" (exit 1 with empty stderr) from "we never got an answer" (any other
status, or an exit 1 git explained on stderr — mode-000 objects, dead SSH
transport, killed process), and the latter degrades to the neutral wording
rather than asserting absence. Partial-clone detection has the same three-way
verdict.

Covered on every create path:
- `worktree add` dies reading the tree directly.
- Sparse creates pass --no-checkout, so add exits 0 and the failure lands in the
  follow-up checkout; probes there target the repo, which outlives the rollback.
- SSH/remote creates diagnose client-side against the repo on the execution
  host, so no wire change and any relay version benefits.
- Diagnosis probes are bounded (10s): on a partial clone the `^{tree}` peel can
  trigger a promisor fetch, and an unreachable remote would otherwise turn a
  bounded failure into a create that never settles. The remote path passes the
  bound explicitly rather than inheriting the mux's 30s ambient default.
- Redacted messages keep the `git_failed` telemetry bucket, and unreadable-object
  creates still classify as `permission_denied`; the classifier reads the raw git
  text off the error's cause chain, which never leaves the main process.
- The renderer runs the same classifier, so a pre-fix host or older relay that
  still rethrows git verbatim gets redacted client-side.

Tests were written first and verified failing against the raw-leak behavior. The
regex set is pinned by a real-git reproduction that builds a repo, deletes its
root tree, and asserts on the actual binary's stderr rather than remembered
wording — including the sparse path, where git 2.44 words the same missing root
tree as "unable to parse commit".
2026-08-25 05:56:35 -07:00