Split-commit captured the staged diff with `git diff --cached --binary`,
whose stdout is hard-capped at GIT_COMMAND_OUTPUT_LIMIT_BYTES (8 MiB) by
readCappedText. A single large binary (base85-encoded inline) crossed the
cap; the capture was truncated silently, so files sorting after the binary
were absent from the parsed diff and stage.hunks threw a misleading
`No diff found for <path>` naming an innocent file.
Surface truncation as GitCommandResult.truncated, add a requireComplete
diff option that throws the new GitOutputTruncatedError instead of
returning a silently truncated diff, and have runSplitCommit request a
complete diff and abort with a clear message pointing at the real cause.
Fixes#8897
- Bun's inotify-backed fs.watch permanently stops delivering events after
observing git's atomic HEAD.lock -> HEAD rename in the watched directory
(oven-sh/bun#24875), so the directory-watch fix for issue #8412 still froze
the status-line branch on Linux after the first switch; CI caught it as a
30s timeout in status-line-vcs-refresh.test.ts.
- Added git.head.watch: fs.watchFile stat-poll of the HEAD path (reftable dir
for reftable repos) with a disposer; path-based polling survives inode swaps
on every platform.
- Status line and footer now consume the helper; the footer previously bound
fs.watch to the HEAD file inode and died after one switch on all platforms.
- Dropped the FSWatcher error-listener plumbing (StatWatcher has no error
mode) and reworked the watcher lifecycle tests to the stat-poll contract;
verified the atomic-rename regression test passes on Linux bun 1.3.14 in
Docker where it previously timed out.
- Add comprehensive Nix flake definitions, derivations, modules, and CI workflows.
- Update tests and executables to resolve binaries from PATH rather than absolute paths.
- Ensure byte reproducibility and zeroed timestamps in embedded dashboard archives.
- Add handling for Nix-managed installations in CLI update checks.
A deleted/nonexistent cwd also makes Bun.spawn throw ENOENT; only claim
'git is not installed.' when the working directory actually exists, else
name the missing directory. Addresses the Codex P2 review on #6237.
The read-only git helpers spawned `git` directly and only inspected the
result exit code. When git is absent from PATH, Bun's spawn throws ENOENT
(uv_spawn 'git') at launch, which escaped as an unhandled rejection and
crashed the process (e.g. Windows without git, relying on WSL git).
Translate a missing-binary launch failure into a non-zero GitCommandResult
in the async git() wrapper and into a null degrade in the sync reftable ref
readers, matching the existing non-zero-exit handling. Mutating/checked
commands keep surfacing the clean "git is not installed." error.
Fixes#6169
rev-parse --git-common-dir resolves symlinks while ensureIsolation derives
sourceCommonDir lexically from the session cwd (resolveRepository walks
path.resolve'd components). On any symlinked repo path (macOS /tmp,
symlinked project dirs) the lexical comparison missed, detachGitDir
returned "independent", and the parent-mutation leak silently survived.
Realpath both sides before comparing; regression test drives the gate
through a symlink alias.
- Match the rcopy worktree-add registration via realpath: git canonicalizes
the admin gitdir back-reference (macOS /var -> /private/var), so the
lexical comparison missed it and left a stale registration in the source
repo's worktree list.
- Carry core.fileMode so an explicit filemode=false source does not read as
mode-changed files in the detached isolation.
- Carry core.splitIndex and the sharedindex.* files referenced by a split
source index; restoring the raw index without them broke every git read.
- Carry the source shallow boundary file so history traversal over the
borrowed object DB stops at the boundary instead of failing.
- Regression test covering all three carries.
A fresh `git init` in detachGitDir dropped core.sparseCheckout and the
sparse-checkout patterns, and rebuilding the index via write-tree/
read-tree discarded skip-worktree bits. Files intentionally absent from
a sparse working tree then read as deletions, which delta capture could
apply back to the parent.
detachGitDir now restores the index verbatim (preserving skip-worktree,
assume-unchanged, and exact stage entries) and carries
core.sparseCheckout, core.sparseCheckoutCone, and info/sparse-checkout
into the detached .git before restoring the index. Falls back to
read-tree HEAD only when the source had no index.
Fixes#6003
A linked git worktree with an unborn HEAD (a fresh/orphan branch with no
commits) still shares the parent's common dir, so an isolated task's
first branch and commit would write into the parent repo. The previous
early return on a missing HEAD SHA left that shared metadata intact.
detachGitDir now severs unborn worktrees too: `git init -b <branch>`
preserves the checked-out branch name, ref freezing is gated on a born
HEAD, and the rcopy worktree registration is still removed.
Fixes#6003
Copy isolation backends (reflink/apfs/btrfs/zfs/block-clone/rcopy)
materialise the worktree by duplicating its `.git` verbatim. When the
parent is a linked git worktree its `.git` is a pointer file, so the
isolation shared the parent's HEAD/index/ref namespace: a task's
`git checkout`/`commit` moved the parent's branch, and the rcopy
`git worktree add` path stacked task branches in the shared namespace.
`ensureIsolation` now runs `git.detachGitDir` after `isoStart`, turning
each isolation into a standalone repo with a frozen HEAD/refs/index
snapshot that borrows the source object database via
`objects/info/alternates`. Isolated git ops stay private, every task
branch is parented on the requested base, and patch/branch capture
(`git fetch <merged>`) still resolves objects.
Fixes#6003
- Ran commit host completion before commit-agent session disposal so mnemopi/autolearn teardown cannot preempt a valid proposal.
- Converted missing commit-agent host outputs and split-plan gaps into thrown errors so omp commit cannot resolve into exit 0 without creating a commit.
- Preserved caller GPG_TTY state instead of forcing a bogus signing TTY in git and non-interactive subprocess environments.
Fixes#4794
An intermediate commit whose net effect is already on HEAD (redundant
change, or 3-way merged to HEAD by "theirs == ours") stopped the
sequencer with "The previous cherry-pick is now empty" and was
treated as a hard conflict. mergeTaskBranches aborted the whole range,
marked the branch failed, and dropped every remaining non-overlapping
commit.
Add cherryPick.skip and cherryPick.isEmptyError to the git namespace,
then in mergeTaskBranches' catch classify the failure before aborting:
loop --skip while the error stderr matches the "now empty" phrase so
consecutive empties advance the sequencer; fall through to abort/fail
on the first non-empty error (genuine conflict with unmerged files).
Fixes#4438
- Added GIT_NETWORK_TIMEOUT_MS (30 min) for clone/fetch with an overridable timeoutMs option; local plumbing keeps the 5-minute cap.
- Migrated fetch() from a positional AbortSignal to an options object.
Failed stash-pop cleanup now invokes git clean with literal pathspecs for
stash-derived untracked paths. Filenames such as `:(glob)*` are valid POSIX
filenames and valid Git pathspec magic; passing them as ordinary pathspecs with
`-x` could delete unrelated ignored artifacts that were never stashed and are
not recoverable from the preserved stash.
Extend the fallback regression with a literal `:(glob)*` stash file and an
ignored `build.log` that must survive cleanup.
Fixes#4175
When a task branch adds ignore rules for a path that was untracked in the
user's stashed WIP, a failed stash pop can restore the file and then leave it
hidden from normal status after reset. Default `git clean -fd -- <path>` does
not remove ignored files, so the partial restore could still leak into later
isolated task baselines.
Add an includeIgnored clean mode and use `git clean -fdx -- <stash path>` for
failed stash-pop cleanup. Extend the fallback regression so the task branch adds
.gitignore for the restored untracked path and verify both normal and ignored
status return clean.
Fixes#4175
A failed `git stash pop --index` can restore unrelated untracked files before
exiting on a tracked conflict while still preserving the stash entry. The
previous fallback only reset tracked/index state, leaving those untracked files
in the working tree for subsequent task baselines.
Record the top stash entry's untracked paths before popping and clean exactly
those paths if the pop fails after preflight. Add a regression that forces the
fallback branch and verifies the worktree returns clean with the stash preserved.
Fixes#4175
mergeTaskBranches and applyNestedPatches both stashed dirty WIP, cherry-picked
task branches, then called `git stash pop` in a finally block. On conflict git
left stage 1/2/3 unmerged entries in .git/index with no MERGE_HEAD to abort;
neither call cleaned up. The corrupted index persisted indefinitely, and every
subsequent overlay-isolated task inherited it through the lower layer —
captureRepoDeltaPatch then emitted `diff --cc` (combined merge format) that
git apply rejects with "No valid patches in input", failing every downstream
task merge with 'Branch merge failed before a task branch could be created'.
Fix at the git API level: git.stash.tryPop now runs `git apply --3way --check`
on `git stash show -p --binary stash@{0}` before popping (`--3way` matches
what git stash pop does internally, so context that drifted after cherry-pick
is still accepted). Preflight failure short-circuits — stash entry preserved,
index untouched. Preflight pass falls through to pop; if pop still leaves
unmerged entries (mode-only or delete/modify conflicts the preflight can miss),
a `reset --hard HEAD" fallback restores the merged HEAD without losing the
cherry-picked commits (stash is preserved by git on failed pop, so the user's
WIP stays recoverable).
Both call sites now share this contract via git.stash.tryPop.
Fixes#4175
captureRepoDeltaPatch records the delta against `HEAD + WIP`, so the
patch's context lines and blob SHAs reference the WIP-modified files.
commitPatchToBranchWorktree then tried to apply that patch to a fresh
worktree pinned at HEAD, which failed hard whenever the WIP-side file
was missing from HEAD's index (untracked WIP files, staged-new WIP
files) or when --3way could not resolve an overlap.
commitPatchToBranchWorktree now tries plain apply first, then `--3way`
(which cleanly subtracts WIP via the shared ODB blob for tracked files),
and only when both fail replays baseline WIP into the temp worktree so
the delta's context lines up, rewinding WIP-only files afterward so
they never leak into the branch commit.
Added git.ls.tree helper for the WIP-only-file filter and a set of
regression tests covering the untracked, staged-new, and overlap
scenarios.
Fixes#4136
git apply --3way --check exits 0 even when the real apply would write conflict markers and unmerged index stages, so the previous fix left the worktree dirty on conflicting patches while only flipping changesApplied to false.
Dropped --3way for patch-mode merge and used a --reverse --check probe instead: it succeeds only when the target state is already present (true no-op) and reads without touching the worktree. Conflicts fall through to the normal --check + apply path, which rejects them before writing anything.
Added regression coverage for the conflict scenario asserting the worktree stays clean, and for the fresh apply path.
Forced non-interactive credential env for git and gh subprocesses, added a default timeout, and capped captured stdout/stderr with a truncation marker. Added regression coverage for prompt env, output capping, and timeout cleanup.
Fixes#4072
- Added a helper to build the Git process environment that explicitly clears common ambient Git environment variables.
- Applied the new environment builder to both async and synchronous Git commands to prevent environment bleeding from parent processes.
Dirty isolated baselines can be accidentally committed by subagents that run git add -A. Fetching the raw isolation HEAD then cherry-picking the range would replay that baseline WIP into parent history.
Add a dirty-baseline replay path that rewrites each agent commit against the captured baseline tree, preserving the agent commit message and author while excluding staged, unstaged, and untracked changes that existed before isolation started. Clean baselines still use the raw git fetch path, and nested-only changes keep returning patches without creating an empty root branch.
Add a regression for baseline staged + untracked WIP committed by the agent, asserting the task branch contains only the agent file and parent WIP remains staged/untracked after merge.
Fixes#3842
Applied isolated branch patches with three-way fallback when unrelated parent dirt appears in patch context.
Surfaced branch preparation failures instead of reporting no changes.
Fixes#3841
- Added `git.repo.linkedWorktreeSync` to identify and resolve git worktree metadata without spawning subprocesses.
- Updated `StatusLineComponent` to detect linked worktrees and resolve project/worktree context names.
- Modified path segment rendering to collapse nested git worktree paths and display the worktree name when it diverges from the active branch.
- Introduced `icon.worktree` symbol across themes to visually distinguish git worktree paths.
- Update `shouldRetry` to treat `EISDIR` and `ENOTDIR` as terminal errors, preventing unnecessary retries when encountering Git reference directory conflicts.
- Add a test suite to verify graceful resolution of branches in scenarios where a packed ref conflicts with a directory path in the filesystem.
Programmatic `git push` operations should not follow annotated tags configured by a user's `push.followTags = true`. This setting can lead to push failures if the remote rejects tag creation (e.g., in PR-head forks), even if the branch update itself is valid.
Adding `--no-follow-tags` explicitly overrides this user setting, ensuring only the specified refspec is pushed and preventing rejections.
Bare-repository worktrees resolve their git common dir to the bare repo
itself (for example /repos/foo.git), not to a directory literally named
.git. The first fix only collapsed non-bare linked worktrees, so bare
worktree layouts still fell back to each individual worktree root.
Return the shared commonDir for bare repositories from both
repo.primaryRoot and repo.primaryRootSync, update project-label docs,
and cover two worktrees attached to one bare repository in the
hindsight bank regression test.
Fixes#2232