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
{{ message }}
Repository navigation
core: agent silently stays in old directory when session_move fails ("Unable to move session to <dir>") #56038
After creating a worktree, the agent can end up working in the old directory without anyone noticing: session_move failed, the agent couldn't tell why, and it carried on by cd-ing into the worktree in every shell call. The session stayed in the old directory, so the worktree's AGENTS.md was never loaded and relative paths in read/edit/glob resolved against the wrong root.
How it happened (2.0.30, Sonnet 5.5): in one turn the model ran git fetch && git worktree add ../repo-x in a shell call and called tools.opencode.session_move to ../repo-x as a parallel tool call. The move ran ~100 ms after the shell started, before the fetch had finished, so the directory didn't exist yet. Rejecting the move was correct. But the tool result was only:
Unable to move session to /…/repo-x
so the model had no reason to retry. Calling session_move again once the directory existed worked.
Two things would make this recoverable:
Say why the move failed.packages/core/src/tool/plugin/opencode.ts (same on dev) maps every error to one message:
Effect.mapError((error)=>newToolFailure({message: `Unable to move session to ${input.directory}`, error }),),
SessionMove already fails with typed errors (DestinationNotFoundError, DestinationNotDirectoryError, DestinationUnavailableError, NotFoundError). The session.move HTTP handler maps them to specific messages, and worktree_remove in the same file maps its own errors, so the same pattern fits here. With Directory does not exist: … the model would most likely have retried.
Say when to call it. The injected hint ("When you create a worktree outside the current working directory … consider using execute to call tools.opencode.session_move") and the tool description ("do not run destination-dependent tools in the same execute call") don't say the directory must exist first. Adding "call it after the command that creates the directory has finished, not in parallel with it" would likely have prevented this.
Plugins
No response
OpenCode version
2.0.30
Steps to reproduce
In Code Mode: await tools.opencode.session_move({ directory: "/tmp/does-not-exist" })
→ Unable to move session to /tmp/does-not-exist (caught error: name: "Error", no cause)
Same move over HTTP: opencode api post /api/session/<sessionID>/move --data '{"directory":"/tmp/does-not-exist"}'
→ 400 {"_tag":"InvalidRequestError","message":"Directory does not exist: /tmp/does-not-exist"}
opencode.log has no entry for the failed tool call.
Thanks for the detailed report. I reproduced this on 2.0.30 and it still happens on the latest dev (00e7472).
Steps: with a simulated model, the model calls execute with code that runs await tools.opencode.session_move({ directory: "/tmp/does-not-exist-56038" }) inside a try/catch and returns the caught error. The tool result the model gets back is:
{"name":"Error","message":"Unable to move session to /tmp/does-not-exist-56038","cause":null}
Nothing in it says the directory doesn't exist. The session.move HTTP handler maps the same errors to specific messages (Directory does not exist: …, Not a directory: …, and so on), but the tool in packages/core/src/tool/plugin/opencode.ts still turns every error into one message. The hint and tool description also still don't say the directory has to exist before the call.
The script below reproduces it, and the recording shows the failed opencode.session_move call in the TUI.
recording.mp4Reproduction script (drive.ts)
// Repro for anomalyco/opencode#56038: session_move to a missing directory// returns only "Unable to move session to <dir>" with no reason.import{Effect,Stream}from"effect"import{Llm,OpenCodeDriver}from"opencode-drive"constcode=`try { await tools.opencode.session_move({ directory: "/tmp/does-not-exist-56038" }) return "moved"} catch (e) { return JSON.stringify({ name: e?.name, message: e?.message, cause: e?.cause ? String(e.cause) : null })}`exportdefaultOpenCodeDriver.use({
...(process.env.OC_DEV ? {opencode: {dev: process.env.OC_DEV}} : {}),project: {git: true,files: {"README.md": "hi\n"}},tui: {recording: true},},({ ui, llm })=>Effect.gen(function*(){yield*llm.serve((request,index)=>{if(index===0)returnStream.make(Llm.toolCall({index: 0,id: "call_move",name: "execute",input: { code }}),Llm.finish("tool-calls"),)// Print the tool result the model receives for the execute call.constbody=JSON.stringify(request)constat=body.indexOf("Unable to move")console.log("TOOL RESULT SEEN BY MODEL:",at>=0 ? body.slice(Math.max(0,at-300),at+300) : body.slice(-1500))returnStream.make(Llm.text("Done checking the move result."))})yield*ui.submit("Move this session to /tmp/does-not-exist-56038")yield*ui.waitFor("Done checking",{timeout: 60000})yield*Effect.sleep(1500)yield*ui.screenshot("session-move-error")}),)
Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.
Activity
opencode-agent commentedon Oct 16, 2026
Thanks for the detailed report. I reproduced this on 2.0.30 and it still happens on the latest
dev(00e7472).Steps: with a simulated model, the model calls
executewith code that runsawait tools.opencode.session_move({ directory: "/tmp/does-not-exist-56038" })inside a try/catch and returns the caught error. The tool result the model gets back is:Nothing in it says the directory doesn't exist. The
session.moveHTTP handler maps the same errors to specific messages (Directory does not exist: …,Not a directory: …, and so on), but the tool inpackages/core/src/tool/plugin/opencode.tsstill turns every error into one message. The hint and tool description also still don't say the directory has to exist before the call.The script below reproduces it, and the recording shows the failed
opencode.session_movecall in the TUI.recording.mp4
Reproduction script (drive.ts)
Run it with opencode-drive:
opencode-drive run ./drive.ts. It drivesopencodefrom yourPATH; setOC_DEVto the path of an OpenCode checkout to run that from source instead.