You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I have searched existing issues and this is not a duplicate.
Bug Description
When Agent Server is configured with OH_CONVERSATION_RUNTIME=docker, creating a conversation with worktree=true produces a linked Git worktree that is valid inside the conversation container but immediately appears as prunable from the host.
In the reproduced case, the host repository is bind-mounted into the conversation container as /workspace. The inner Agent Server then creates the requested linked worktree under /tmp/conversation-worktrees/<conversation-id>/workspace.
The worktree’s .git file points to:
gitdir: /workspace/.git/worktrees/workspace
while the host repository’s corresponding .git/worktrees/workspace/gitdir points back to:
That /tmp/... path exists only inside the container filesystem namespace, so the host cannot resolve it.
As a result, while the conversation container is still running and the worktree is healthy inside it, the host repository reports the same worktree as:
prunable gitdir file points to non-existent location
This was reproduced with OpenHands Agent Server 1.49.4 and Agent Canvas 1.22.0 on Docker Desktop for macOS.
Expected Behavior
A Docker-backed conversation created with worktree=true should use an isolated Git worktree whose metadata remains valid for the lifetime of the conversation.
The linked worktree should be valid both from inside the conversation runtime and from the host repository that owns the Git metadata.
In particular, the host repository should not report the active conversation worktree as:
prunable gitdir file points to non-existent location
while the conversation container is still running.
Actual Behavior
The conversation container creates the requested worktree successfully and uses the expected branch:
## openhands/<conversation-id>...origin/main
However, the linked-worktree metadata spans two filesystem namespaces.
While the conversation container is still running, check the host repository’s worktree state:
git -C <host-repo> worktree list --porcelain
Observed:
worktree /tmp/conversation-worktrees/<conversation-id>/workspace
HEAD <sha>
branch refs/heads/openhands/<conversation-id>
prunable gitdir file points to non-existent location
At the same time, verify the worktree from inside the container:
git -C /tmp/conversation-worktrees/<conversation-id>/workspace \
status --short --branch
Observed:
## openhands/<conversation-id>...origin/main
This shows the same linked worktree is healthy inside the container but already invalid/prunable from the host.
Acceptance Criteria
Creating a Docker-backed conversation with worktree=true produces a Git worktree that remains valid for the lifetime of the conversation.
While the conversation container is running, git worktree list --porcelain on the host repository does not report the conversation worktree as prunable.
The worktree remains on the expected openhands/<conversation-id> branch.
The worktree is valid both from inside the conversation runtime and from the host repository that owns the Git metadata.
Automated coverage includes the combined case of:
conversation_runtime=docker
an existing Git repository workspace
worktree=true
The regression test verifies the worktree state from both the inner container/runtime and the host repository.
Installation Method
Agent Canvas 1.22.0 using its OpenHands Agent Server integration, with Docker conversation image ghcr.io/openhands/agent-server:1.49.4-python
If you selected "Other", please specify
No response
SDK Version
1.49.4
Version Confirmation
I have confirmed this bug exists on the LATEST version of OpenHands SDK
Python Version
No response
Model Name (if applicable)
No response
Operating System
MacOS
Logs and Error Messages
Host-side Git state while the conversation container is still running:
worktree /Users/hhernand/Development/repos/agent-orchestrator-sandbox
HEAD dc3dee71e13e2f5a8180fce30bf1362b6c6d7b10
branch refs/heads/main
worktree /tmp/conversation-worktrees/5725b8f5-28f5-4cee-9d68-a95832c18258/workspace
HEAD dc3dee71e13e2f5a8180fce30bf1362b6c6d7b10
branch refs/heads/openhands/5725b8f5-28f5-4cee-9d68-a95832c18258
prunable gitdir file points to non-existent location
At the same time, inside the active conversation container:
The reverse pointer therefore targets a path that exists only inside the container filesystem namespace.
Minimal Code Sample
No response
Screenshots and Additional Context
This appears to be an integration regression between two features that were introduced separately.
The conversation worktree feature was added in PR #3180 (feat(agent-server): add conversation worktree option) and merged on 9 May 2026. Its documented validation covers creation of /tmp/conversation-worktrees/<conversation_id> worktrees and openhands/<conversation_id> branches, but the end-to-end test keeps both the repository and the worktree in the same filesystem namespace.
The per-conversation Docker runtime was added later in PR #3403 (feat(agent-server): add docker runtime mode for per-conversation containers), merged on 16 September 2026 and released in v1.49.0. That PR includes substantial Docker-runtime validation, including 205 focused tests and a live Docker-backed conversation.
However, I could not find evidence in the documented validation or relevant Docker-runtime tests that the combined case was exercised:
The failure reproduced here occurs specifically in that combination:
the Git common directory lives in the host repository bind-mounted at /workspace;
the linked worktree itself is created under the container-private /tmp/conversation-worktrees/...;
Git records absolute paths between those two locations;
the reverse pointer stored in the host repository therefore references a path that only exists inside the container.
A regression test that creates a Docker-backed conversation with worktree=true and validates the resulting worktree from both the inner runtime and the host repository should reproduce the issue directly.
For completeness: this was reproduced on 1.49.4. The current release is 1.49.5, and the relevant worktree creation logic is unchanged between 1.49.4 and 1.49.5, while the Docker conversation start path still rewrites the inner workspace to /workspace. I have not yet performed the live reproduction on 1.49.5, so I have not marked the “confirmed on latest version” checkbox.
OpenHands AI triage
The following comments and acceptance criteria were added by the OpenHands AI agent.
Triage
Valid bug, confirmed against main. The Docker conversation runtime rewrites the inner workspace to /workspace and bind-mounts the host repository there (docker_runtime/routers.py sets body["workspace"] = {"working_dir": "/workspace"}; registry.py mounts workspace_dir -> /workspace). The inner agent-server then creates the linked worktree under its configured conversation_worktree_root, defaulting to /tmp/conversation-worktrees (config.py, conversation_service.py). Git records absolute paths: the worktree .git file points at /workspace/.git/worktrees/<name>, while the host repo's .git/worktrees/<name>/gitdir records the container-only /tmp/... path. The host therefore reports the still-running worktree as prunable.
Scope: make the conversation-worktree path contract resolvable from both filesystem namespaces for Docker-backed worktree creation. The worktree the inner agent-server creates must be reachable from the host repository that owns the Git metadata, so the reverse pointer the host stores resolves to a real path. Non-goals:
Do not change worktree behavior for conversation_runtime=local, the openhands/<conversation-id> branch naming, the start-point resolution policy, or worktree cleanup semantics.
Do not add client- or host-specific special-case branches in SDK core; keep any namespace/mount assumption contained to the Docker runtime, config, or workspace layer per CONTRIBUTING.md.
Do not attempt to keep the path valid after the owning mount/container is gone.
Acceptance Criteria
Creating a conversation with conversation_runtime=docker, an existing Git repository as the workspace, and worktree=true creates a linked worktree on the expected openhands/<conversation-id> branch while the container is running.
While the conversation container is still running, git -C <host-repo> worktree list --porcelain lists that worktree without prunable gitdir file points to non-existent location, and the host repository does not report the active worktree as prunable.
The absolute path stored in the host repository's .git/worktrees/<name>/gitdir resolves to an existing path on the host for the lifetime of the conversation, and the worktree .git file resolves inside the container.
The same worktree is reported valid from inside the conversation runtime (git -C <worktree> status --short --branch inside the container) and from the host repository, at the same time.
Non-Docker behavior is unchanged: conversation_runtime=local with worktree=true still uses the existing conversation_worktree_root default, and existing worktree tests still pass.
A non-Git workspace with worktree=true under conversation_runtime=docker still starts successfully without creating a worktree (existing fallback preserved).
After the conversation ends, the worktree and its openhands/<conversation-id> branch are cleaned up, or the host repository no longer reports a stale worktrees/<name> registration, so repeated conversations in the same repository do not accumulate prunable entries.
Automated coverage exercises the combined case conversation_runtime=docker + an existing Git repository workspace + worktree=true, and asserts the host-side git worktree list --porcelain view as well as the inner-runtime view. A test that only inspects the inner runtime, or that never checks the host repository, does not satisfy this.
Any affected production artifact (packaged agent-server or Docker image) is exercised, not only a mocked unit test, consistent with repository guidance for mount/packaging changes.
Is there an existing issue for the same bug?
Bug Description
When Agent Server is configured with
OH_CONVERSATION_RUNTIME=docker, creating a conversation withworktree=trueproduces a linked Git worktree that is valid inside the conversation container but immediately appears asprunablefrom the host.In the reproduced case, the host repository is bind-mounted into the conversation container as
/workspace. The inner Agent Server then creates the requested linked worktree under/tmp/conversation-worktrees/<conversation-id>/workspace.The worktree’s
.gitfile points to:while the host repository’s corresponding
.git/worktrees/workspace/gitdirpoints back to:That
/tmp/...path exists only inside the container filesystem namespace, so the host cannot resolve it.As a result, while the conversation container is still running and the worktree is healthy inside it, the host repository reports the same worktree as:
This was reproduced with OpenHands Agent Server 1.49.4 and Agent Canvas 1.22.0 on Docker Desktop for macOS.
Expected Behavior
A Docker-backed conversation created with
worktree=trueshould use an isolated Git worktree whose metadata remains valid for the lifetime of the conversation.The linked worktree should be valid both from inside the conversation runtime and from the host repository that owns the Git metadata.
In particular, the host repository should not report the active conversation worktree as:
while the conversation container is still running.
Actual Behavior
The conversation container creates the requested worktree successfully and uses the expected branch:
However, the linked-worktree metadata spans two filesystem namespaces.
Inside the container, the worktree lives at:
and its
.gitfile points to:The corresponding Git metadata in the host repository points back to:
Because that
/tmp/...path exists only inside the container, the host repository immediately considers the worktree invalid:This occurs while the conversation container is still running, so the condition is not caused by container teardown or cleanup.
Steps to Reproduce
Select an existing Git repository as the workspace.
Create a new conversation using New worktree.
Send any minimal message so the conversation is actually created.
Identify the conversation container and verify that the original repository is mounted at
/workspace.Inside the container, inspect the generated worktree:
A worktree is created under:
.gitfile:Observed:
Observed:
Observed:
Observed:
This shows the same linked worktree is healthy inside the container but already invalid/prunable from the host.
Acceptance Criteria
Creating a Docker-backed conversation with
worktree=trueproduces a Git worktree that remains valid for the lifetime of the conversation.While the conversation container is running,
git worktree list --porcelainon the host repository does not report the conversation worktree asprunable.The worktree remains on the expected
openhands/<conversation-id>branch.The worktree is valid both from inside the conversation runtime and from the host repository that owns the Git metadata.
Automated coverage includes the combined case of:
conversation_runtime=dockerworktree=trueThe regression test verifies the worktree state from both the inner container/runtime and the host repository.
Installation Method
Agent Canvas 1.22.0 using its OpenHands Agent Server integration, with Docker conversation image ghcr.io/openhands/agent-server:1.49.4-python
If you selected "Other", please specify
No response
SDK Version
1.49.4
Version Confirmation
Python Version
No response
Model Name (if applicable)
No response
Operating System
MacOS
Logs and Error Messages
Host-side Git state while the conversation container is still running:
At the same time, inside the active conversation container:
The worktree
.gitfile inside the container contains:while the host repository metadata contains:
The reverse pointer therefore targets a path that exists only inside the container filesystem namespace.
Minimal Code Sample
No response
Screenshots and Additional Context
This appears to be an integration regression between two features that were introduced separately.
The conversation worktree feature was added in PR #3180 (
feat(agent-server): add conversation worktree option) and merged on 9 May 2026. Its documented validation covers creation of/tmp/conversation-worktrees/<conversation_id>worktrees andopenhands/<conversation_id>branches, but the end-to-end test keeps both the repository and the worktree in the same filesystem namespace.The per-conversation Docker runtime was added later in PR #3403 (
feat(agent-server): add docker runtime mode for per-conversation containers), merged on 16 September 2026 and released in v1.49.0. That PR includes substantial Docker-runtime validation, including 205 focused tests and a live Docker-backed conversation.However, I could not find evidence in the documented validation or relevant Docker-runtime tests that the combined case was exercised:
The failure reproduced here occurs specifically in that combination:
/workspace;/tmp/conversation-worktrees/...;A regression test that creates a Docker-backed conversation with
worktree=trueand validates the resulting worktree from both the inner runtime and the host repository should reproduce the issue directly.For completeness: this was reproduced on 1.49.4. The current release is 1.49.5, and the relevant worktree creation logic is unchanged between 1.49.4 and 1.49.5, while the Docker conversation start path still rewrites the inner workspace to
/workspace. I have not yet performed the live reproduction on 1.49.5, so I have not marked the “confirmed on latest version” checkbox.OpenHands AI triage
The following comments and acceptance criteria were added by the OpenHands AI agent.
Triage
Valid bug, confirmed against
main. The Docker conversation runtime rewrites the inner workspace to/workspaceand bind-mounts the host repository there (docker_runtime/routers.pysetsbody["workspace"] = {"working_dir": "/workspace"};registry.pymountsworkspace_dir -> /workspace). The inner agent-server then creates the linked worktree under its configuredconversation_worktree_root, defaulting to/tmp/conversation-worktrees(config.py,conversation_service.py). Git records absolute paths: the worktree.gitfile points at/workspace/.git/worktrees/<name>, while the host repo's.git/worktrees/<name>/gitdirrecords the container-only/tmp/...path. The host therefore reports the still-running worktree asprunable.Scope: make the conversation-worktree path contract resolvable from both filesystem namespaces for Docker-backed worktree creation. The worktree the inner agent-server creates must be reachable from the host repository that owns the Git metadata, so the reverse pointer the host stores resolves to a real path. Non-goals:
conversation_runtime=local, theopenhands/<conversation-id>branch naming, the start-point resolution policy, or worktree cleanup semantics.CONTRIBUTING.md.Acceptance Criteria
conversation_runtime=docker, an existing Git repository as the workspace, andworktree=truecreates a linked worktree on the expectedopenhands/<conversation-id>branch while the container is running.git -C <host-repo> worktree list --porcelainlists that worktree withoutprunable gitdir file points to non-existent location, and the host repository does not report the active worktree as prunable..git/worktrees/<name>/gitdirresolves to an existing path on the host for the lifetime of the conversation, and the worktree.gitfile resolves inside the container.git -C <worktree> status --short --branchinside the container) and from the host repository, at the same time.conversation_runtime=localwithworktree=truestill uses the existingconversation_worktree_rootdefault, and existing worktree tests still pass.worktree=trueunderconversation_runtime=dockerstill starts successfully without creating a worktree (existing fallback preserved).openhands/<conversation-id>branch are cleaned up, or the host repository no longer reports a staleworktrees/<name>registration, so repeated conversations in the same repository do not accumulate prunable entries.conversation_runtime=docker+ an existing Git repository workspace +worktree=true, and asserts the host-sidegit worktree list --porcelainview as well as the inner-runtime view. A test that only inspects the inner runtime, or that never checks the host repository, does not satisfy this.