Skip to content

A pre-push hook in the target repo blocks every push, hides its reason, and the branch is then deleted #55

Closed

Description

@JacobStephens2

This was generated by AI during triage.

What happened

A run on Educational-Travel-Adventures/tourbot#1176 (thirdshift v0.1.0, commit 33415ce, git 2.52.0) failed at the push. It left no branch on origin, and the local worktree and branch were deleted:

thirdshift: pushing issue-1176
thirdshift: could not push the failed run's work: git push origin issue-1176 failed: error: failed to push some refs to 'https://github.com/Educational-Travel-Adventures/tourbot.git'
thirdshift: cleaning up the worktree and local branch issue-1176
thirdshift: git push origin issue-1176 failed: error: failed to push some refs to 'https://github.com/Educational-Travel-Adventures/tourbot.git'

The remote didn't reject anything. The target repo has a pre-push hook in .git/hooks, and git worktrees share it. The hook runs the repo's full PHPUnit suite with coverage and exits 1 if it fails, or if vendor/bin/phpunit is missing. A fresh worktree has no vendor/ because it's gitignored, so the hook can never pass there. The hook was written for humans pushing from the main checkout.

The session's two commits (the work) survived only as unreachable objects and had to be recovered by hand from the object store.

Three problems

  1. Pushes run the repo's hooks. Worktree::push (src/worktree.rs:99) and commit_and_push (src/failed_run.rs:106) both call git push origin <branch> without --no-verify. The failure commit already skips hooks ("a hook that rejects the commit would strand the work"), but the push right after it doesn't, so the same stranding happens one step later.
  2. The hook's explanation is dropped. With git 2.52, a pre-push hook's output goes to git's stdout. Git::run (src/git.rs:25) only puts stderr in its error, so the message becomes a bare error: failed to push some refs. Checked with a throwaway worktree pushing to a local bare repo:
    $ git push <bare> branch 2>/dev/null     # stdout only
    🚀 Running comprehensive PHPUnit tests before push...
    ❌ PHPUnit not found. Please run 'composer install' first.
    $ git push <bare> branch >/dev/null      # stderr only
    error: failed to push some refs to '<bare>'
    
  3. Cleanup deletes the branch even after both pushes failed. When the failed-run push doesn't land, the work exists nowhere except the local branch, and that gets deleted.

Reproduce

  1. In a target repo, add .git/hooks/pre-push containing #!/bin/sh\necho "hook says no"; exit 1.
  2. Run thirdshift on any issue in that repo.
  3. Expected: the branch is pushed, or at least the error shows "hook says no" and the branch is kept. Actual: the error shows only failed to push some refs, and the branch is deleted.

Suggested fix

  • Pass --no-verify to both git push calls. The session already runs the tests, and CI is the gate for the PR, so a local hook shouldn't decide whether the work reaches origin. Alternatively, keep hooks on the happy-path push but always skip them in commit_and_push, matching the commit just before it.
  • When git fails, include stdout in the error as well as stderr, or at least the last few non-empty lines of each.
  • If the failed-run push fails, keep the local branch (and ideally the worktree) and print its name and head SHA, rather than cleaning up.
  • Regression test: a target repo whose pre-push hook exits 1. Assert that the branch reaches origin, or that the error includes the hook's output and the branch still exists locally.

Related, not this issue

The same run failed first because the implement session called ScheduleWakeup (a 1800s fallback to "push when the full suite finishes") and then ended, since nothing wakes a headless session back up. Its background test task was killed. That is a separate issue about the session prompt and allowed tools.

Activity

  1. JacobStephens2 commented on Sep 25, 2026

    @JacobStephens2
    OwnerAuthor

    This was generated by AI during triage.

    Agent Brief

    Category: bug
    Summary: A git hook in the target repo must not block thirdshift's pushes, git failures must show why, and a failed run must never delete work that exists nowhere else

    Current behavior:

    • Pushes run the target repo's hooks. Both the Issue branch push (Worktree::push) and the failed-run push (the commit_and_push step of the Failed run path) run git push origin <branch> without --no-verify. A target repo whose pre-push hook fails in a fresh worktree therefore blocks every run. For example, a hook that runs a test suite needing a gitignored vendor/ can never pass. The failed-run commit already skips hooks, but the push right after it doesn't.
    • The reason is hidden. When git fails, Git::run puts only stderr in its error. With git 2.52, a pre-push hook's output goes to git's stdout, so the error shows only error: failed to push some refs to '<url>'.
    • Work is deleted. After the failed-run push fails, cleanup still removes the worktree and the local Issue branch. The session's commits then survive only as unreachable objects.

    Desired behavior:

    • Every git push thirdshift makes skips the target repo's hooks (--no-verify). The session runs the tests and CI gates the PR, so a local hook doesn't decide whether work reaches origin.
    • When any git command fails, its error includes git's stdout as well as its stderr, so a hook's or git's explanation reaches the user. Keep it bounded, for example the last several non-empty lines of each stream. Say nothing extra when a stream is empty.
    • If the Failed run path could not push the branch, cleanup keeps the local Issue branch and worktree. thirdshift prints a line saying they were kept, with the branch name, head SHA and worktree path, so the user can recover or push by hand. Cleanup behaves as it does now when the push succeeded, or when there was nothing to push.

    Key interfaces:

    • Worktree::push(): pushes with --no-verify.
    • The Failed run path's commit-and-push step: pushes with --no-verify. Whether it succeeded must be visible to the cleanup that follows, for example through FailedRun or the push's result, instead of only being logged as a progress line.
    • Git::run(): its error message on failure includes stdout as well as stderr. Its success return value (trimmed stdout) is unchanged.
    • The worktree and branch cleanup at the end of a run: skipped when the failed-run push didn't land.

    Acceptance criteria:

    • With a target repo whose .git/hooks/pre-push is #!/bin/sh / echo "hook says no" / exit 1, a run pushes the Issue branch to origin anyway.
    • A test calls Git::run directly with a git command that fails and writes a known line to stdout. For example, git push without --no-verify to a local bare repo, from a repo whose pre-push hook echoes a marker and exits 1. The returned error contains both the marker line and git's failed to push some refs stderr line.
    • When the failed-run push fails (for example because origin is unreachable or a pre-receive hook rejects it), the local Issue branch and worktree still exist after thirdshift exits, and stderr has a line naming the branch, head SHA and worktree path.
    • When the failed-run push succeeds, or there is nothing to push, cleanup removes the worktree and local branch as it does today.
    • Existing tests pass. cargo clippy and cargo fmt --check are clean.

    Out of scope:

    • The implement session ending on ScheduleWakeup in a headless run, with its background task killed. That was the other cause of the tourbot#1176 failure, and it's a separate issue.
    • Skipping hooks on commits other than the failed-run commit, which already skips them.
    • Changing how the Failed run path converts the PR to a draft.
    • Any change to target repos' hooks.
  2. added
    ready-for-agentFully specified, ready for an AFK agent
    and removed
    needs-triageMaintainer needs to evaluate this issue
    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

    bugSomething isn't workingready-for-agentFully specified, ready for an AFK agent

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions