fix(git): treat ref=HEAD with unresolved HEAD as empty tree#3177
fix(git): treat ref=HEAD with unresolved HEAD as empty tree#3177
Conversation
The empty-repo fallback in get_valid_ref relied on _repo_has_commits to
decide whether to short-circuit ref="HEAD" to the empty tree. That check
returns True for repos that have *any* commits anywhere — so an orphan
branch (HEAD pointing to a ref with no commit, while other branches do
have commits) still ran git rev-parse --verify HEAD^{commit} and failed
with:
HTTP 400 Bad Request
{"detail":"Git command failed: git --no-pager rev-parse --verify
'HEAD^{commit}'"}
surfacing in the agent-canvas Changes tab whenever a workspace was in
that state.
Replace the pre-check with a tighter try/except around the rev-parse
itself: when override == "HEAD" and rev-parse fails, fall back to
GIT_EMPTY_TREE_HASH (empty repo *or* orphan branch — anything where
HEAD has no commit yet). Non-"HEAD" overrides still raise so typo'd
branches/SHAs aren't silently turned into "no changes".
This also drops the extra git rev-list --count call on the ref="HEAD"
happy path.
Tests:
- New SDK regression: ref="HEAD" on an orphan branch returns untracked
files as ADDED (mirrors existing empty-repo test).
- New SDK regression: non-"HEAD" unresolved ref still raises
GitCommandError.
- New end-to-end router test: GET /api/git/changes?ref=HEAD on an
orphan-branch repo returns 200 (was 400 before this fix).
Co-authored-by: openhands <openhands@all-hands.dev>
Python API breakage checks — ✅ PASSEDResult: ✅ PASSED |
REST API breakage checks (OpenAPI) — ✅ PASSEDResult: ✅ PASSED |
all-hands-bot
left a comment
There was a problem hiding this comment.
Taste Rating: 🟢 Good taste
Clean bug fix that actually simplifies the code. Replacing the _repo_has_commits pre-check with direct exception handling eliminates the orphan-branch edge case without adding complexity. Comprehensive test coverage at both SDK and router levels.
[RISK ASSESSMENT]
- [Overall PR]
⚠️ Risk Assessment: 🟢 LOW
Bug fix that makes existing git operations more robust. No API changes, no breaking changes. Affects read-only git inspection logic with excellent test coverage.
VERDICT:
✅ Worth merging: Fixes real user-reported issue with cleaner implementation
KEY INSIGHT:
Elegant solution that eliminates a special case by handling the failure directly rather than trying to predict it.
all-hands-bot
left a comment
There was a problem hiding this comment.
✅ QA Report: PASS
The PR successfully fixes the orphan branch bug and achieves its stated goal.
Does this PR achieve its stated goal?
Yes. The PR fixes the HTTP 400 error that occurred when calling /api/git/changes?ref=HEAD on an orphan branch. Before this fix, users saw GitCommandError: git --no-pager rev-parse --verify 'HEAD^{commit}' when HEAD pointed to an unborn branch. After this fix, the same scenario returns HTTP 200 with untracked files as ADDED changes. The fallback to empty-tree comparison is correctly limited to ref="HEAD" only, so typo'd branch names still raise errors as expected.
| Phase | Result |
|---|---|
| Environment Setup | ✅ Dependencies installed, project builds successfully |
| CI Status | |
| Functional Verification | ✅ Bug reproduced, fix verified end-to-end, regression guards working |
Functional Verification
Test 1: Reproduce the bug (BEFORE fix)
Setup: Created a git repo with commits on main, then switched to an orphan branch with unborn HEAD.
Command (parent commit 3a7a97a):
from openhands.sdk.git.git_changes import get_changes_in_repo
changes = get_changes_in_repo("/tmp/test_repo", ref="HEAD")Result:
❌ GitCommandError: Git command failed: git --no-pager rev-parse --verify 'HEAD^{commit}'
Exit code: 128
Stderr: fatal: Needed a single revision
This confirms the bug existed: the old code tried to resolve HEAD, failed, and raised an exception that surfaced as HTTP 400 in the Changes tab.
Test 2: Verify the fix (AFTER fix)
Command (this PR commit 55f096c):
from openhands.sdk.git.git_changes import get_changes_in_repo
changes = get_changes_in_repo("/tmp/test_repo", ref="HEAD")Result:
✅ SUCCESS: ref='HEAD' returned 2 changes
Paths: {'committed.txt', 'untracked.txt'}
The fix works: get_valid_ref catches the GitCommandError, detects override == "HEAD", and falls back to GIT_EMPTY_TREE_HASH. Changes are returned successfully instead of raising an exception.
Test 3: Verify regression guard (non-HEAD refs still raise)
Command:
changes = get_changes_in_repo("/tmp/test_repo", ref="not-a-real-branch-name")Result:
✅ GitCommandError raised as expected
Error: Git command failed: git --no-pager rev-parse --verify 'not-a-real-branch-name^{commit}'
The fix correctly preserves strict validation for non-"HEAD" overrides. Typo'd branch names are not silently swallowed.
Test 4: Verify agent-server endpoint
Command:
GET /api/git/changes?path=/tmp/test_repo&ref=HEADResult:
HTTP 200 OK
[
{"status": "ADDED", "path": "committed.txt"},
{"status": "ADDED", "path": "untracked.txt"}
]
The end-to-end fix works through the HTTP API. Before this PR, the same request returned HTTP 400 with the git command error message.
Test 5: Verify new tests pass
All three new tests added in this PR pass:
✅ test_get_changes_in_repo_ref_head_on_orphan_branch_returns_untracked_as_added
✅ test_get_changes_in_repo_invalid_non_head_ref_still_raises_after_fix
✅ test_git_changes_query_param_ref_head_on_orphan_branch_returns_200Issues Found
🟡 Minor: Pre-commit check failing due to E731 style violation in test file (lambda instead of def). See inline comment.
|
@OpenHands fix the precommit |
|
I'm on it! rbren can track my progress at all-hands.dev |
Co-authored-by: openhands <openhands@all-hands.dev>
|
Fixed in ca076f9. Validation:
This comment was created by an AI agent (OpenHands) on behalf of the user. |
|
Since my last summary, there have been no additional code or PR changes. Checklist:
|
|
@OpenHands fix failing gh action |
|
I'm on it! rbren can track my progress at all-hands.dev |
|
https://github.com/OpenHands fix failing gh action |
|
@OpenHands fix failing gh action |
|
Uh oh! There was an unexpected error starting the job :( |
|
https://github.com/OpenHands fix failing gh action |
|
@OpenHands fix failing gh action |
|
I'm on it! rbren can track my progress at all-hands.dev |
|
Investigated the failing This comment was created by an AI agent (OpenHands) on behalf of the user. |
|
OpenHands encountered an error: Request timeout after 30 seconds to https://airwhdwzlmiupcvy.prod-runtime.all-hands.dev/api/conversations/1df593cc-4006-43db-93af-7b78e7c8ba76/ask_agent See the conversation for more information. |
The empty-repo fallback in get_valid_ref relied on _repo_has_commits to decide whether to short-circuit ref="HEAD" to the empty tree. That check returns True for repos that have any commits anywhere — so an orphan branch (HEAD pointing to a ref with no commit, while other branches do have commits) still ran git rev-parse --verify HEAD^{commit} and failed with:
HTTP 400 Bad Request
{"detail":"Git command failed: git --no-pager rev-parse --verify
'HEAD^{commit}'"}
surfacing in the agent-canvas Changes tab whenever a workspace was in that state.
Replace the pre-check with a tighter try/except around the rev-parse itself: when override == "HEAD" and rev-parse fails, fall back to GIT_EMPTY_TREE_HASH (empty repo or orphan branch — anything where HEAD has no commit yet). Non-"HEAD" overrides still raise so typo'd branches/SHAs aren't silently turned into "no changes".
This also drops the extra git rev-list --count call on the ref="HEAD" happy path.
Tests:
Why
Summary
Issue Number
How to Test
Video/Screenshots
Type
Notes
Agent Server images for this PR
• GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server
Variants & Base Images
eclipse-temurin:17-jdknikolaik/python-nodejs:python3.13-nodejs22-slimgolang:1.21-bookwormPull (multi-arch manifest)
# Each variant is a multi-arch manifest supporting both amd64 and arm64 docker pull ghcr.io/openhands/agent-server:ca076f9-pythonRun
All tags pushed for this build
About Multi-Architecture Support
ca076f9-python) is a multi-arch manifest supporting both amd64 and arm64ca076f9-python-amd64) are also available if needed