Skip to content

[Bug]: Checkpoint capture fails with exit 1 on every turn when the project folder is gitignored by a parent repo #13712

Closed

Description

@kevvykevwin

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

If the project folder sits inside a git repo whose .gitignore ignores that folder, checkpoint capture fails on every turn. The common way to end up here is an "allowlist" ignore file in a parent repo:

mkdir -p repo/sub && cd repo && git init
printf '/*\n!/.gitignore\n' > .gitignore
git add .gitignore && git commit -m init
touch sub/a.txt

Then open repo/sub as a T3 Code project and send any message.

Expected behavior

T3 should notice that the project folder is gitignored and either turn checkpoints off for that project with a single clear notice ("this folder is ignored by git, so checkpoints are unavailable") or skip capture silently. It should not snapshot the folder with git add -f, because that would pull in the .venv, caches and secrets the ignore rules exist to keep out.

Actual behavior

Every turn shows:

VCS process failed in GitVcsDriver.checkpoints.captureCheckpoint: git (/path/to/repo/sub) exited with 1 - Process exited with a non-zero status.

I think this is the cause, though I haven't read the capture code: when you pass git add an ignored path by name, it exits with status 1 even though nothing actually went wrong. The same thing happens with a scratch index:

$ GIT_INDEX_FILE=/tmp/idx git add -A -- sub; echo "exit=$?"
The following paths are ignored by one of your .gitignore files:
sub
hint: Use -f if you really want to add them.
exit=1

git check-ignore -q <project-path> before capture would catch this case cheaply.

Possibly related: #2017, which reports the same error on every turn with no cause identified, and #13187 and #8873, which are other cases where a project folder that isn't a clean working-tree root breaks checkpoint capture.

Impact

Minor bug or occasional failure

Version or commit

T3 Code (Alpha) 0.0.42

Environment

macOS 26.6.2, git 2.54.0 (Apple Git-157), Claude Code provider

Logs or stack traces

VCS process failed in GitVcsDriver.checkpoints.captureCheckpoint: git (/path/to/repo/sub) exited with 1 - Process exited with a non-zero status.

Activity

  1. juliusmarminge commented on Sep 25, 2026

    @juliusmarminge
    Member

    Triage

    Confirmed on current main (ed809f7ad). This is a real server checkpoint bug. Not already fixed. v0.0.42 (719a76ca1) already runs git add -A -- . from the project directory, and the commits since that tag do not handle an ignored project folder. Reproduced on Linux with git 2.43.0. The reported Apple Git 2.54.0 message is the same hint.

    What you reported. The project folder sits inside a git repo whose ignore rules exclude that folder (an allowlist .gitignore is the usual way to get there). Every finished turn reports:

    VCS process failed in GitVcsDriver.checkpoints.captureCheckpoint: git (/path/to/repo/sub) exited with 1 - Process exited with a non-zero status.
    

    The thread never gets a checkpoint, so the turn diff and Go back stay unavailable. The agent itself keeps working.

    Your diagnosis is right. The command T3 actually runs is git add -A -- . with cwd set to the project directory, not git add -A -- sub from the parent. Both forms exit 1 with The following paths are ignored by one of your .gitignore files. Naming the ignored directory is what fails. git rev-parse --is-inside-work-tree still prints true, so T3 treats the folder as a normal git project and tries to capture it.

    What the code does

    1. An ignored subdirectory still counts as a git repo, so capture runs.

    resolveCheckpointCwd only skips the workspace when repository detection fails:

    if (!cwd) {
    return undefined;
    }
    if (!(yield* checkpointStore.isGitRepository(cwd))) {
    return undefined;
    }
    return cwd;

    Detection is git rev-parse --is-inside-work-tree:

    const isInsideWorkTree: VcsDriver.VcsDriver["Service"]["isInsideWorkTree"] = (cwd) =>
    gitCommand(
    vcsProcess,
    "GitVcsDriver.isInsideWorkTree",
    cwd,
    ["rev-parse", "--is-inside-work-tree"],
    {
    allowNonZeroExit: true,
    timeoutMs: 5_000,
    maxOutputBytes: 4_096,
    },
    ).pipe(Effect.map((result) => result.exitCode === 0 && result.stdout.trim() === "true"));

    From inside the ignored folder that command prints true, and --show-toplevel is the parent repo. There is no per-project way to turn checkpoints off.

    2. Staging names the project directory, which Git then refuses.

            const stageFiles = (exclusions: ReadonlyArray<string>) =>
              execute({
                operation,
                cwd: input.cwd,
                // Preserve absent skipped entries, but capture present nonignored files outside the cone.
                args: [
                  ...indexConfig,
                  ...durableWrite,
                  "add",
                  ...(sparseCheckout ? ["--sparse"] : []),
                  "-A",
                  "--",
                  ".",
                  ...exclusions,
                ],
                env: commitEnv,
              });

    On v0.0.42 the same pathspec is already there, without the later sparse and durable-write flags:

    yield* execute({
    operation,
    cwd: input.cwd,
    args: ["add", "-A", "--", "."],
    env: commitEnv,
    });

    The . pathspec is required for a project that is a subdirectory of a larger repo. Dropping it is the wrong fix: git add -A with no pathspec updates the entire working tree (confirmed: from the ignored folder it staged a modified file in a sibling directory). git add -f is also the wrong fix. In the allowlist repro it staged sub/.venv/secret along with the project file.

    3. The nested-repo recovery does not catch this.

    On main, a failed git add looks for untracked nested repos and retries with exclusions (#12181). That scan is git ls-files --others --exclude-standard, which prints nothing for an ignored directory, so the original error is rethrown. v0.0.42 does not have this recovery at all. The exit is not retried either: #11665 only retries a lock file or a missing path, and paths are ignored matches neither.

    4. The activity hides Git's real message.

    Turn completion records checkpoint.capture.failed with summary Checkpoint capture failed and this error as the detail:

    yield* captureCheckpointFromTurnCompletion(event).pipe(
    Effect.catch((error) =>
    Effect.flatMap(nowIso, (createdAt) =>
    appendCaptureFailureActivity({
    threadId: event.threadId,
    turnId,
    detail: error.message,
    createdAt,
    }).pipe(Effect.catch(() => Effect.void)),
    ),
    ),
    );

    VcsProcessExitError replaces stderr with Process exited with a non-zero status. Git's own text (The following paths are ignored… / Use -f if you really want to add them.) is discarded. That opacity is #4380 and #12224, not this bug.

    The baseline capture on thread.message-sent, thread.turn-start-requested, and turn.started fails too. Those misses are only a reactor warning (checkpoint reactor failed to process input), because the baseline never sticks. The visible activity is once per completed turn.

    Reproduction

    Allowlist ignore, then git add -A -- . from the ignored folder, including after read-tree HEAD into a private index (the capture sequence):

    git check-ignore -q -- .   # exit 0, ignored
    git add -A -- .             # exit 1
    The following paths are ignored by one of your .gitignore files:
    sub
    
    git add -A                  # exit 0, but stages the whole parent work tree
    git add -f -A -- .          # exit 0, and stages .venv/secret
    

    The same exit 1 happens for a sub/ rule. A normal subdirectory, a subdirectory whose files are ignored while the directory itself is not (sub/*), and a /* ignore with !sub/ and !sub/** all exit 0. git check-ignore -q -- . at the repo root exits 1.

    One sibling the check-ignore probe misses: if files inside the folder were committed and the directory was ignored afterward, git check-ignore -q -- . exits 1, but git add -A -- . still exits 1, even when the only edit is one of those tracked files. git add -u -- . stages the tracked edit and leaves new ignored files out. The reported allowlist folder has nothing tracked, so check-ignore catches it.

    Not a duplicate

    No open issue covers checkpoint capture of a project directory that a parent repo gitignores.

    Fix direction

    Skip capture when the project directory itself is ignored. Do not git add -f, and do not publish the parent HEAD tree in its place. git add -u exits 0 in the fully ignored folder and write-tree then writes the parent tree. diffCheckpoints diffs those whole trees with no path limiter, so the thread diff would be the rest of the repo and still would not contain the ignored files.

    1. Before stageFiles, from the project cwd, run git check-ignore -q -- .. Exit 0 means this folder is ignored. Skip the capture and record one notice that checkpoints are unavailable because the folder is ignored by git. Do not append checkpoint.capture.failed again on later turns.
    2. On the tracked-then-ignored variant above, check-ignore stays silent while git add -A -- . still exits 1. Fall back to git add -u -- . for that failure so tracked edits are captured and ignored untracked files are not. Do not use -f.
    3. Test both layouts with a real git repo: the allowlist repro must not call git add and must not write a checkpoint ref; a subdirectory that is not ignored must still snapshot only that subdirectory. A third case, tracked files inside a directory that was ignored later, must capture the tracked edit via git add -u and must not stage a new ignored file.

    Workaround

    The agent can keep working. Checkpoints, the turn diff, and Go back stay unavailable for this project until a fix ships.

    Opening the parent repository stops the exit 1, because capture no longer names the ignored directory. Files inside the ignored folder are still left out of the snapshot. To checkpoint that folder it has to be a path Git will track (negation rules, or its own repository). Forcing the add would pull in .venv, caches, and secrets.

    Accepting as a server checkpoint bug.

  2. added
    bugSomething is broken or behaving incorrectly.
    acceptedfeature request accepted
    via-triageFiled through npx t3 triage
    on Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedfeature request acceptedbugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions