@bojackduy/opencode-fork-lane
v0.3.2
Published
Fork-lane for OpenCode — lane-inspired copy-on-write git worktrees + session fork. /fork-lane (/lane) creates a new branch + worktree (reflink for ignored files) and forks the session into it. Agent-callable via fork_lane tool (alias lane).
Maintainers
Readme
opencode-fork-lane — Copy-on-Write Git Worktrees + Session Fork for OpenCode
Fork the session. Keep the cache warm. opencode-fork-lane is an OpenCode plugin that forks your session into a lane-style copy-on-write git worktree: a new branch, a new folder, and every git-ignored path (node_modules, target/, .env) cloned by reference with reflink — so the new tree starts with warm caches instead of a reinstall and a cold build.
- TUI:
/fork-lane(alias/lane, orctrl+f) asks what to fork (full session or a specific prompt, like native fork), asks for a lane name, creates the worktree, forks the session with history, moves the fork into the new worktree, and navigates to it. - Agent: the
fork_lanetool (aliaslane) lets the model isolate risky, experimental, or parallel work by itself — no human shell commands needed. Say "lane", "fork lane", "fork-lane", or "worktree" and the agent should callfork_lane(or itslanealias).
One lane name becomes the git branch, the worktree folder (.lane/trees/<name>), and the forked session title.
🌐 Landing page: bojackduy.github.io/opencode-fork-lane · 📦 npm · 🐞 Issues · 📝 Changelog
Install
Install from npm (published as @bojackduy/opencode-fork-lane). The same package provides both the server plugin and the TUI plugin — opencode resolves the right entrypoint (./server / ./tui) from the config file it appears in:
// opencode.jsonc
{
"plugin": ["@bojackduy/opencode-fork-lane"]
}// tui.json
{
"plugin": ["@bojackduy/opencode-fork-lane"]
}To use a local clone instead (development):
// opencode.jsonc { "plugin": ["/path/to/opencode-fork-lane/src/server.ts"] } // tui.json { "plugin": ["/path/to/opencode-fork-lane/src/tui.tsx"] }
Then quit and restart opencode (config is loaded once at startup).
Requires: git, bun >= 1.1, opencode >= 1. Optional but recommended: lane binary (curl -fsSL https://lane.lukeed.com | sh) — when present the plugin delegates to lane new for full fidelity; otherwise it does git + reflink itself.
Use
TUI — /fork-lane (alias /lane)
- Open a session in a git repo.
- Run
/fork-lane(or/lane, orctrl+f). - Pick what to fork — Full session or a specific prompt (same choice native fork gives you).
- Enter a lane name, e.g.
fix-loginorfeat/login("/" creates a namespaced branch/folder; session shows "feat — login"). - You land in a forked session titled
fix-login, rooted at<gitRoot>/.lane/trees/fix-loginon branchfix-login, with history up to your fork point.
Agent — fork_lane (alias lane)
fork_lane(name="fix-login", task="Make verify constant-time, keep signature")
# same thing:
lane(name="fix-login", task="Make verify constant-time, keep signature")name(required): branch + folder + session title (slugified, min 2 chars).task(optional): handoff line posted into the fork so the continuation has context.base(optional): git ref the lane branches from (defaults to current HEAD).messageID(optional): fork at a specific message instead of full history.moveChanges(default true): best-effortmove-sessioncarrying uncommitted changes into the lane.
Returns JSON: { ok, name, branch, directory, via, forkedSession, moved, moveDetail, next }.
moved: true— the fork now lives in the new worktree. Continue there.moved: false— the fork holds history but is still rooted at the old directory (older server / move failed). Do new edits with absolute paths underdirectory; a human can TUI → Move session →directory, or re-run/fork-lane(TUI moves correctly).
How it works (lane-like)
| | git worktree add | lane new / fork-lane |
|---|---|---|
| tracked files | checked out | checked out |
| node_modules, target/, .env (any depth) | absent | exists, by reference |
| uncommitted work | absent | with moveChanges |
| OpenCode session | you're on your own | forked with history, moved in |
| agent self-isolation | no | fork_lane tool |
| cost | reinstall + cold build | ~0 B, warm cache |
- Worktree location follows lane:
<gitRoot>/.lane/trees/<name>, branch<name>— solane ls,lane note, andlane mergeall work inside fork-lane trees. - Reflink: APFS
cp -cR, Linuxcp --reflink=always -a, else plain copy fallback.lane newis preferred when the binary exists. - Session:
session.fork(history, optionally from a message) →session.update(title = name) →experimental.controlPlane.moveSession(directory = new worktree). The server tool falls back to rawPOST /experimental/control-plane/move-sessionand degrades gracefully.
FAQ
Where do lanes live?
Under <gitRoot>/.lane/trees/<name> on branch <name> — the same layout lane uses. Names may contain "/" (e.g. feat/login) — branch and folder keep the slash, session title shows " — ".
Does it work without the lane binary? Yes. The plugin reimplements the copy-on-write step: worktree plus reflink cloning of every git-ignored path.
What if moving the session fails?
The fork keeps full history in the old directory and the worktree is still ready. The tool response now carries moved, moveDetail, plus a classified moveKind and moveRemedy:
| moveKind | meaning | remedy |
|---|---|---|
| unreachable | the server tool couldn't reach the opencode server (all 0.2.3 moves failed this way — raw fetch to an unconnectable serverUrl) | 0.2.4 retries loopback variants (0.0.0.0/localhost → 127.0.0.1); if it still fails, move via TUI |
| project-mismatch | destination is outside the session's project | open opencode in the lane dir, or TUI → Move session → lane directory |
| apply-conflict | uncommitted changes didn't apply cleanly | retry with moveChanges: false, move changes manually |
| not-found / not-git / http | stale session, non-git dir, or server rejection | see moveDetail, or move via TUI |
TUI /fork-lane uses the in-process client and moves correctly; on failure it now shows an error toast with the cause and remedy instead of a truncated warning.
Is lane's memory (lane note / lane why) supported?
Out of scope for v0.1 — but the layout is compatible, so you can lane note inside the lane normally.
Why not just lane new + opencode?
You can — and should, when you want a bare terminal lane. fork-lane is for when the conversation should move with the checkout: same history, new branch, warm cache, one command, plus an agent-callable equivalent so the model can isolate itself without asking you to run shell commands.
Dev
bun install
bun run typecheck
bun test
bun run buildReleases are tag-gated: push v*.*.* and the publish workflow typechecks, tests, builds, publishes to npm with provenance, and cuts a GitHub release from the changelog.
