A /move-session slash command for opencode that
reassigns the current chat session to a different project directory — and
cleans up after itself so the move leaves no trace in your conversation.
/move-session ~/Projects/myrepo
/move-session to my projects
/move-session here
opencode binds each session to a project (a .git ancestor + a row in
opencode.db). When you start a chat in /tmp/scratch and want it to
live in ~/Projects/myrepo, there's no built-in way to move it — the
session list in ~/Projects/myrepo won't show it because the
project_id/directory/path columns still point at /tmp/scratch.
This command:
- Resolves the target (path,
~/..., relative, or natural language like "my projects") via the LLM. - Atomically rewrites
project_id,directory, andpathfor the session and every nested subagent session via a recursive CTE. - Reuses an existing project row if one already covers the target's git
toplevel; otherwise leaves the session on
globalso opencode's ownmigrateGloballogic adopts it the next time a TUI opens that path. - Schedules a deferred chat cleanup so the next time opencode launches, the slash-command turn (4 messages: user → subtask call → synthetic continuation → build agent paraphrase) and the background subagent session are removed permanently.
You see the "Moved to ..." reply in the current session. Restart, open the session at the new place, and the move command itself is gone.
| File | Role |
|---|---|
plugins/session-env.js |
injects OPENCODE_SESSION_ID (and OPENCODE_PROJECT_ID, OPENCODE_WORKTREE, OPENCODE_DIRECTORY, OPENCODE_CALL_ID) into every shell command opencode spawns, via the shell.env plugin hook. This is the only deterministic way to identify the calling session from inside bash. |
commands/move-session.md |
the /move-session slash command. Runs as agent: general (subtask) so its bash and the subagent's reply don't pollute the parent session. Looks up the user's session via parent_id of $OPENCODE_SESSION_ID. |
plugins/move-session-auto-undo.js |
one-shot at opencode startup: reads ~/.local/share/opencode/move-session-pending.jsonl and removes each queued move turn (4 messages) plus its subagent session via direct bun:sqlite DELETE (FKs cascade). Skips reverting if you typed real messages after the move. |
curl -fsSL https://raw.githubusercontent.com/moha-abdi/opencode-move-session/main/install.sh | bashmkdir -p ~/.config/opencode/plugins ~/.config/opencode/commands
cp plugins/*.js ~/.config/opencode/plugins/
cp commands/*.md ~/.config/opencode/commands/Then restart opencode so the plugins load.
/move-session <target>
<target> can be:
- An absolute path:
/Users/me/code/repo - A
~/...path:~/Projects/foo - A relative path:
../sibling - A bare project name that matches a known opencode project's basename
- A natural-language phrase:
to my projects,current dir,home,the foo repo
The agent resolves the phrase, performs the DB move, and replies on one line. On the next opencode launch the move turn quietly disappears from the chat.
The trick is that opencode passes the calling session's id into the
shell.env plugin hook (tool/shell.ts:411). Hooks run synchronously
per tool call, so by the time the bash script runs, $OPENCODE_SESSION_ID
is exactly the session whose tool call this is — never a guess based on
"most recently updated". The slash command's own backtick template
expansion (prompt.ts:1692) does not go through that hook, so the
command instructs the LLM to do its work via the Bash tool, which does.
For determining the user's session (rather than the subagent's), the
bash queries parent_id of $OPENCODE_SESSION_ID. When run as a
subtask, that's the user's session; when not, it's null and we fall back
to $OPENCODE_SESSION_ID itself.
- The bash script writes directly to
~/.local/share/opencode/opencode.dbviasqlite3. opencode's WAL mode permits concurrent writers; the move usesBEGIN IMMEDIATEto serialize against itself. If opencode changes the schema (drizzle migrations), this command will need adjusting. - The plugin uses
bun:sqlite(opencode is a Bun runtime). It runs once at startup; if you have multiple TUIs opening at the same time, they race-claim the queue file by truncating it — duplicate processing is idempotent (DELETE on a missing row is a no-op). - Existing TUI sessions cache their project assignment in memory. After a move you have to restart opencode (or switch sessions) for the live TUI to pick up the change.
- The cleanup distinguishes synthetic vs. real user messages by checking
the
syntheticflag on each part. Anything you type after the move has no synthetic flag, so it's preserved.
To stop auto-cleaning the chat: rm ~/.config/opencode/plugins/move-session-auto-undo.js.
The slash command itself will keep working; you'd just press <leader>u
manually if you want the turn gone.
To remove entirely: delete the three installed files.
MIT — see LICENSE.