One OpenCode per project: worktree sessions run in-app, reuse the project instance #265

Closed
opened 2026-07-09 10:17:15 +02:00 by dries · 0 comments
Owner

One OpenCode Per Project — Worktree Sessions Run In-App

Problem Statement

Today, every worktree session (/wt, MCP new_session with a worktree,
loops spawn_worktree) shells out to git worktree add and then launches
a brand-new opencode --port 0 inside its own tmux window. A user
working across several worktrees of one project ends up with N opencode
processes and N tmux windows for a single repo. This is cruft: it eats
RAM, clutters tmux list-sessions, and multiplies the number of live
agent processes the user has to reason about.

The user wants one opencode per project. Additional worktrees (and
other directories) of that project should reuse the project's single
opencode instance instead of each spawning their own process and tmux
window.

Solution

A worktree session no longer spawns its own opencode. Instead:

  • ocman guarantees exactly one opencode instance per project, rooted
    at the project's main checkout. If none is running when a session is
    requested, ocman launches one (in tmux, rooted at the main checkout).
  • Worktree sessions are created in-app against that single instance,
    using OpenCode's per-session directory parameter to point the session
    at the worktree path. ocman drives the session over OpenCode's HTTP API
    and surfaces it in its own UI. There is no per-worktree tmux window
    and no per-worktree opencode process.
  • Because a worktree lives outside the main checkout, worktree-session
    tool calls trigger OpenCode's external_directory permission. ocman
    pre-authorizes that permission for paths under the project's own
    .worktrees/ root by seeding OPENCODE_PERMISSION when it launches the
    project instance.

The old "TUI per worktree" flow is removed — there is no opt-in to
keep it. Net effect per project: 1 opencode + 1 tmux session instead
of N.

Background Facts (verified)

  • OpenCode 1.17.10 (the maintainer's build) accepts a per-session
    directory on POST /session via the ?directory= query param or
    the x-opencode-directory header. Verified live: a single opencode
    launched in the ocman checkout created sessions rooted at an unrelated
    external directory (/private/tmp), and the returned session object
    reported the correct directory, path, and projectID.
  • Session objects carry their own directory (GET /session and
    GET /session/{id}), so a session's worktree can be read back rather
    than inferred from the process cwd.
  • external_directory is a first-class OpenCode permission key
    ("Any tool that reads or writes files outside the project worktree").
  • OPENCODE_PERMISSION (inline JSON, loaded at launch, precedence
    over project/global config) is how ocman already plans to seed child
    permissions (tracker #101). It can carry an external_directory allow
    rule for the .worktrees/ root.
  • ocman's opencode.CreateSession already drives sessions over HTTP
    (prompt_async, shell, command, abort, permissions) but currently
    uses req.Directory only for port discovery — it POSTs /session
    with a literal {} body, so today a session's directory always equals
    the opencode process's launch cwd.

User Stories

  1. As a developer, I want a worktree session to reuse my project's
    existing opencode, so that I don't accumulate one opencode process per
    worktree.
  2. As a developer, I want a worktree session to reuse my project's
    existing tmux session, so that tmux list-sessions stays clean.
  3. As a developer, I want a worktree session to appear and be driven
    inside ocman's UI, so that I don't need to switch to a terminal TUI to
    work in a worktree.
  4. As a developer, when I create a worktree session and no opencode is
    running for that project, I want ocman to launch exactly one opencode
    (rooted at the main checkout) automatically, so that the flow always
    succeeds without a manual "start opencode first" step.
  5. As a developer, when I create a second worktree session for the same
    project, I want it to reuse the same opencode that the first one
    launched, so that only one process ever exists per project.
  6. As a developer, I want worktree-session tool calls (read/edit/bash) to
    run without being blocked by external_directory permission prompts
    for paths ocman itself created under .worktrees/, so that the session
    is usable immediately.
  7. As a developer, I do not want worktree-session tool calls outside
    the .worktrees/ root to be silently auto-allowed, so that the
    pre-authorization stays scoped to what ocman vouches for.
  8. As a developer using MCP new_session with a worktree, I want it to
    reuse the project's single opencode instead of spawning its own, so
    that agent-split worktrees follow the same one-per-project rule.
  9. As a developer using an agent loop's spawn_worktree action, I want
    each spawned worktree session to reuse the project's single opencode,
    so that a long-running loop doesn't spawn a fleet of opencode
    processes.
  10. As a developer using MCP new_session in the same directory, I want
    ocman to launch the project opencode if none is running, so that
    same-directory splits self-heal instead of failing with
    "no running OpenCode instance".
  11. As a developer, I want the worktree-creation result to tell me the
    created session ID (the thing I navigate to in-app), rather than a
    tmux target, so that the UI can open the session directly.
  12. As a developer, I want the Worktrees view to correctly associate each
    worktree with its sessions by the session's own directory, so that
    the association is right even though all those sessions share one
    opencode process whose cwd is the main checkout.
  13. As a developer, I want any opencode/tmux windows created by the old
    per-worktree flow to keep working until I close them, so that the
    change doesn't forcibly tear down my running work.
  14. As a developer, I want a clear error if the project's main checkout
    isn't a git repository or opencode can't be launched, so that
    failures are legible rather than silent.
  15. As a developer on a multi-remote setup, I want "ensure one opencode
    per project" to run on the host that owns the project directory
    (local or a remote), so that the process is launched where the files
    are.
  16. As a developer, I want the external_directory pre-authorization to
    survive across worktree sessions of the same project, so that the
    second and later worktrees don't re-prompt — it is seeded once, at the
    project instance's launch.

Implementation Decisions

D-1: New central host capability EnsureProjectOpencode

  • A new method on hostsvc.Host:
    EnsureProjectOpencode(ctx, req) (result, error) where the request
    carries the project directory (any path inside the repo) and the result
    carries the discovered/launched opencode port (and the resolved repo
    root + tmux session name for observability).
  • Semantics: resolve the repo root (main checkout) from the given
    directory; discover a running opencode for that repo root; if none,
    launch exactly one opencode --port 0 in tmux rooted at the main
    checkout, seeded with OPENCODE_PERMISSION (see D-4), and wait for its
    port to become discoverable; return the port. Idempotent: a second call
    for the same project returns the already-running instance without
    launching a second one.
  • This is the only code path that launches opencode. It runs on the
    host that owns the directory (local Host, or a remote's in-process
    Host via gRPC), following the existing Host/Router
    (ForDir/ForRemote) seam. The remote gRPC surface gains one RPC that
    marshals/unmarshals through the existing JsonReq/JsonResp envelope
    (no protobuf message regeneration needed).

D-2: EnsureProjectOpencode is the single seam all three callers share

  • /wtlocal.Host.CreateWorktreeSession stops calling
    LaunchWorktreeTmux. It creates the git worktree, calls
    EnsureProjectOpencode(repoRoot), then creates an in-app session
    pointed at the worktree path against the returned port.
  • MCP new_session (worktree) + loops spawn_worktree
    mcp.SessionLauncher.LaunchWithWorktree replaces its current
    launchTmux(repoRoot, worktreePath) + waitForPort(worktreePath) with
    EnsureProjectOpencode(repoRoot), then calls Launch with
    Directory = worktreePath. EnsureProjectOpencode is injected into
    SessionLauncher as a dependency (replacing the launchTmux /
    discoverPort / waitForPort trio for the worktree path).
  • MCP new_session (same directory)SessionLauncher.Launch (or
    its callers) calls EnsureProjectOpencode(dir) before CreateSession,
    so same-directory splits self-heal instead of returning
    ErrPlatformUnreachable.

D-3: opencode.CreateSession must send the session directory

  • opencode.Adapter.CreateSession currently uses req.Directory only for
    port discovery and POSTs {}. It must additionally pass the directory
    to OpenCode so the project instance can create a session rooted at a
    worktree: send x-opencode-directory (URL-encoded) or ?directory=
    on POST /session.
  • Port discovery changes meaning: for worktree sessions, the port is the
    project instance's port (discovered by repo root / provided by
    EnsureProjectOpencode), not a port discovered by the worktree path.
    The directory sent to OpenCode is the worktree; the port is the project
    instance's.
  • platforms.CreateSessionRequest may gain an explicit port/target field
    (or the adapter is told the port by the caller) so the caller that
    already ran EnsureProjectOpencode doesn't re-run discovery. Keep the
    interface change minimal; prefer threading the known port over a second
    lsof scan.

D-4: Pre-authorize external_directory via OPENCODE_PERMISSION at launch

  • When EnsureProjectOpencode launches the project instance, it sets
    OPENCODE_PERMISSION (inline JSON) with an external_directory allow
    rule scoped to the project's .worktrees/<repo>/** root.
  • Scope: only paths under ocman's own .worktrees/ root for that
    project are allowed. Directories outside that root are not blanket
    allowed (US-7).
  • Seeding happens once, at launch (US-16). If the project instance was
    already running (launched by the user, or a prior ocman launch),
    ocman does not restart it to inject the rule; in that case worktree
    tool calls fall through to OpenCode's normal permission flow (which
    ocman's autoapprove pipeline can still handle at runtime). This is an
    accepted limitation, noted in Further Notes.
  • This decision composes with tracker #101 (inherit parent's always-allow
    permissions): both write OPENCODE_PERMISSION at launch; the
    external_directory rule is merged with any inherited permission set
    rather than overwriting it.

D-5: WorktreeSessionResult returns a session ID, not a tmux target

  • hostsvc.WorktreeSessionResult changes: the primary output becomes the
    created session ID (plus worktree path, branch, reused flags). The
    tmux-target / opencode-launched fields that drove tmux switch-client
    are removed for this path.
  • The /api/worktree/create-and-launch response shape changes
    accordingly; the frontend navigates to the session in-app instead of
    switching tmux.

D-6: Retire the per-worktree tmux machinery

  • Delete tmux.LaunchWorktreeWindow, LaunchWorktreeWindowWith, the
    WorktreeSession ("ocman-worktree") constant, and the per-worktree
    window naming / AD-3 / AD-4 idempotent-relaunch logic from the
    worktree-sessions spec. The one remaining tmux launch is the single
    project instance (via EnsureProjectOpencode, reusing
    LaunchOpencode-style session-per-main-checkout).
  • Old windows/processes from the previous flow are left alone (US-13); no
    teardown logic is added.

D-7: Frontend joins worktree ↔ session by the session's own directory

  • The Worktrees view continues to associate sessions to worktrees by
    directory equality, but the source of truth is each session's own
    directory
    (now distinct from the opencode process cwd), which ocman
    surfaces per session. No change to the join shape, only to ensuring
    the per-session directory is available and used.

D-8: Capability flag semantics unchanged, preconditions adjusted

  • The worktreeSessions capability stays server-wide. Its precondition
    set is unchanged in spirit (git + tmux + opencode on PATH + an OpenCode
    adapter registered) since the single project instance still runs in
    tmux.

Testing Decisions

Good tests here assert external behavior at the seams, not internal
call order:

  • EnsureProjectOpencode (host level) — the primary new seam. Test
    against a fake runner/discoverer:

    • When an instance for the repo root is already discoverable → returns
      that port and launches nothing (idempotency / one-per-project).
    • When none is discoverable → launches exactly once, seeds
      OPENCODE_PERMISSION with the scoped external_directory rule, waits
      for the port, returns it.
    • Two concurrent/sequential calls for the same project → at most one
      launch.
    • Non-repo directory → clear error (git.ErrNotARepo).
      Prior art: internal/tmux/launch_test.go (fake Runner),
      internal/mcp/launcher tests (injected launchTmux/discoverPort).
  • opencode.CreateSession sends directory (adapter level) — against a
    fake OpenCode HTTP server, assert that POST /session carries the
    worktree directory (query param or header) and that the returned
    session's directory is honored. Prior art:
    internal/platforms/opencode/operations_test.go
    (TestCreateSessionPinsReturnedSessionPort,
    TestCreateSession_TitleIsSetAfterCreation).

  • local.Host.CreateWorktreeSession (host orchestration) — with a
    temp git repo and fakes for EnsureProjectOpencode + session create,
    assert: worktree created, project instance ensured, session created
    with Directory == worktreePath, result carries the session ID.

  • SessionLauncher.LaunchWithWorktree / Launch — assert both now
    route through the injected EnsureProjectOpencode and no longer call
    the retired per-worktree tmux launcher. Same-directory Launch
    self-heals (ensures instance) instead of erroring.

  • HTTP handlerhandlers_worktree_test.go: response now carries a
    session ID; status-code mapping for the error paths preserved.

  • FrontendWorktreesView associates sessions by per-session
    directory; api.worktree.createAndLaunch round-trip returns a session
    ID and navigation targets the session (not tmux switch). Prior art:
    existing worktree view + api.test.ts.

Coverage ratchet applies: new functions (EnsureProjectOpencode, the
OPENCODE_PERMISSION builder, the directory-sending branch of
CreateSession) ship with tests in the same PR.

Out of Scope

  • Live permission propagation after launch — the
    external_directory rule is seeded once at instance launch (D-4);
    re-seeding a pre-existing instance is out of scope.
  • Tearing down old per-worktree opencode/tmux windows from the
    previous flow (US-13) — they are left running.
  • Restarting an already-running project instance to inject the
    external_directory rule — out of scope (runtime autoapprove covers
    the gap).
  • Non-OpenCode platforms — OpenCode-only, consistent with the
    existing worktree feature.
  • A user-configurable base path for .worktrees/ — unchanged from the
    current deterministic layout.
  • The full delivery of tracker #101 (inherit parent always-allow) —
    this spec only requires that D-4's OPENCODE_PERMISSION seeding merge
    with
    , not conflict with, #101's mechanism.

Further Notes

  • Interaction with #101: both features write OPENCODE_PERMISSION at
    launch. Implementation must merge the external_directory rule with any
    inherited permission set rather than overwrite. Sequencing: whichever
    lands second must account for the first.
  • Pre-existing instance limitation: if the project opencode was
    already running (user-launched, or launched before this feature), the
    external_directory rule was not seeded into it. Worktree tool calls in
    that case fall through to OpenCode's normal permission flow; ocman's
    runtime autoapprove pipeline can still allow them. This is acceptable
    for v1 and should be documented in docs/ alongside the feature.
  • Multi-remote: EnsureProjectOpencode runs on the owning host (R-C),
    same as CreateWorktreeSession today. The hub never launches opencode
    for a remote's project.
  • Docs to update: AGENTS.md (worktree paragraph — no longer "one
    opencode per worktree"), docs/architecture.md (session/worktree data
    flow), and any worktree-sessions user docs.
# One OpenCode Per Project — Worktree Sessions Run In-App ## Problem Statement Today, every worktree session (`/wt`, MCP `new_session` with a worktree, loops `spawn_worktree`) shells out to `git worktree add` and then launches a **brand-new `opencode --port 0`** inside its own tmux window. A user working across several worktrees of one project ends up with N opencode processes and N tmux windows for a single repo. This is cruft: it eats RAM, clutters `tmux list-sessions`, and multiplies the number of live agent processes the user has to reason about. The user wants **one opencode per project**. Additional worktrees (and other directories) of that project should reuse the project's single opencode instance instead of each spawning their own process and tmux window. ## Solution A worktree session no longer spawns its own opencode. Instead: - ocman guarantees **exactly one opencode instance per project**, rooted at the project's main checkout. If none is running when a session is requested, ocman launches one (in tmux, rooted at the main checkout). - Worktree sessions are created **in-app** against that single instance, using OpenCode's per-session `directory` parameter to point the session at the worktree path. ocman drives the session over OpenCode's HTTP API and surfaces it in its own UI. There is **no per-worktree tmux window** and **no per-worktree opencode process**. - Because a worktree lives *outside* the main checkout, worktree-session tool calls trigger OpenCode's `external_directory` permission. ocman pre-authorizes that permission for paths under the project's own `.worktrees/` root by seeding `OPENCODE_PERMISSION` when it launches the project instance. The old "TUI per worktree" flow is **removed** — there is no opt-in to keep it. Net effect per project: **1 opencode + 1 tmux session** instead of N. ## Background Facts (verified) - **OpenCode 1.17.10** (the maintainer's build) accepts a per-session `directory` on `POST /session` via the `?directory=` query param **or** the `x-opencode-directory` header. Verified live: a single opencode launched in the ocman checkout created sessions rooted at an unrelated external directory (`/private/tmp`), and the returned session object reported the correct `directory`, `path`, and `projectID`. - **Session objects carry their own `directory`** (`GET /session` and `GET /session/{id}`), so a session's worktree can be read back rather than inferred from the process cwd. - **`external_directory`** is a first-class OpenCode permission key ("Any tool that reads or writes files outside the project worktree"). - **`OPENCODE_PERMISSION`** (inline JSON, loaded at launch, precedence over project/global config) is how ocman already plans to seed child permissions (tracker #101). It can carry an `external_directory` allow rule for the `.worktrees/` root. - ocman's `opencode.CreateSession` **already** drives sessions over HTTP (`prompt_async`, `shell`, `command`, `abort`, permissions) but currently uses `req.Directory` **only for port discovery** — it POSTs `/session` with a literal `{}` body, so today a session's directory always equals the opencode process's launch cwd. ## User Stories 1. As a developer, I want a worktree session to reuse my project's existing opencode, so that I don't accumulate one opencode process per worktree. 2. As a developer, I want a worktree session to reuse my project's existing tmux session, so that `tmux list-sessions` stays clean. 3. As a developer, I want a worktree session to appear and be driven inside ocman's UI, so that I don't need to switch to a terminal TUI to work in a worktree. 4. As a developer, when I create a worktree session and no opencode is running for that project, I want ocman to launch exactly one opencode (rooted at the main checkout) automatically, so that the flow always succeeds without a manual "start opencode first" step. 5. As a developer, when I create a second worktree session for the same project, I want it to reuse the same opencode that the first one launched, so that only one process ever exists per project. 6. As a developer, I want worktree-session tool calls (read/edit/bash) to run without being blocked by `external_directory` permission prompts for paths ocman itself created under `.worktrees/`, so that the session is usable immediately. 7. As a developer, I do **not** want worktree-session tool calls outside the `.worktrees/` root to be silently auto-allowed, so that the pre-authorization stays scoped to what ocman vouches for. 8. As a developer using MCP `new_session` with a worktree, I want it to reuse the project's single opencode instead of spawning its own, so that agent-split worktrees follow the same one-per-project rule. 9. As a developer using an agent loop's `spawn_worktree` action, I want each spawned worktree session to reuse the project's single opencode, so that a long-running loop doesn't spawn a fleet of opencode processes. 10. As a developer using MCP `new_session` in the *same* directory, I want ocman to launch the project opencode if none is running, so that same-directory splits self-heal instead of failing with "no running OpenCode instance". 11. As a developer, I want the worktree-creation result to tell me the created **session ID** (the thing I navigate to in-app), rather than a tmux target, so that the UI can open the session directly. 12. As a developer, I want the Worktrees view to correctly associate each worktree with its sessions by the session's own `directory`, so that the association is right even though all those sessions share one opencode process whose cwd is the main checkout. 13. As a developer, I want any opencode/tmux windows created by the *old* per-worktree flow to keep working until I close them, so that the change doesn't forcibly tear down my running work. 14. As a developer, I want a clear error if the project's main checkout isn't a git repository or opencode can't be launched, so that failures are legible rather than silent. 15. As a developer on a multi-remote setup, I want "ensure one opencode per project" to run on the host that *owns* the project directory (local or a remote), so that the process is launched where the files are. 16. As a developer, I want the `external_directory` pre-authorization to survive across worktree sessions of the same project, so that the second and later worktrees don't re-prompt — it is seeded once, at the project instance's launch. ## Implementation Decisions ### D-1: New central host capability `EnsureProjectOpencode` - A new method on `hostsvc.Host`: `EnsureProjectOpencode(ctx, req) (result, error)` where the request carries the project directory (any path inside the repo) and the result carries the discovered/launched opencode port (and the resolved repo root + tmux session name for observability). - **Semantics**: resolve the repo root (main checkout) from the given directory; discover a running opencode for that repo root; if none, launch **exactly one** `opencode --port 0` in tmux rooted at the main checkout, seeded with `OPENCODE_PERMISSION` (see D-4), and wait for its port to become discoverable; return the port. Idempotent: a second call for the same project returns the already-running instance without launching a second one. - This is the **only** code path that launches opencode. It runs on the host that owns the directory (local `Host`, or a remote's in-process `Host` via gRPC), following the existing `Host`/`Router` (`ForDir`/`ForRemote`) seam. The remote gRPC surface gains one RPC that marshals/unmarshals through the existing `JsonReq`/`JsonResp` envelope (no protobuf message regeneration needed). ### D-2: `EnsureProjectOpencode` is the single seam all three callers share - **`/wt`** — `local.Host.CreateWorktreeSession` stops calling `LaunchWorktreeTmux`. It creates the git worktree, calls `EnsureProjectOpencode(repoRoot)`, then creates an in-app session pointed at the worktree path against the returned port. - **MCP `new_session` (worktree) + loops `spawn_worktree`** — `mcp.SessionLauncher.LaunchWithWorktree` replaces its current `launchTmux(repoRoot, worktreePath)` + `waitForPort(worktreePath)` with `EnsureProjectOpencode(repoRoot)`, then calls `Launch` with `Directory = worktreePath`. `EnsureProjectOpencode` is injected into `SessionLauncher` as a dependency (replacing the `launchTmux` / `discoverPort` / `waitForPort` trio for the worktree path). - **MCP `new_session` (same directory)** — `SessionLauncher.Launch` (or its callers) calls `EnsureProjectOpencode(dir)` before `CreateSession`, so same-directory splits self-heal instead of returning `ErrPlatformUnreachable`. ### D-3: `opencode.CreateSession` must send the session directory - `opencode.Adapter.CreateSession` currently uses `req.Directory` only for port discovery and POSTs `{}`. It must additionally pass the directory to OpenCode so the project instance can create a session rooted at a worktree: send `x-opencode-directory` (URL-encoded) **or** `?directory=` on `POST /session`. - Port discovery changes meaning: for worktree sessions, the port is the **project instance's** port (discovered by repo root / provided by `EnsureProjectOpencode`), not a port discovered by the worktree path. The directory sent to OpenCode is the worktree; the port is the project instance's. - `platforms.CreateSessionRequest` may gain an explicit port/target field (or the adapter is told the port by the caller) so the caller that already ran `EnsureProjectOpencode` doesn't re-run discovery. Keep the interface change minimal; prefer threading the known port over a second lsof scan. ### D-4: Pre-authorize `external_directory` via `OPENCODE_PERMISSION` at launch - When `EnsureProjectOpencode` launches the project instance, it sets `OPENCODE_PERMISSION` (inline JSON) with an `external_directory` allow rule scoped to the project's `.worktrees/<repo>/**` root. - **Scope**: only paths under ocman's own `.worktrees/` root for that project are allowed. Directories outside that root are **not** blanket allowed (US-7). - Seeding happens **once, at launch** (US-16). If the project instance was already running (launched by the user, or a prior ocman launch), ocman does **not** restart it to inject the rule; in that case worktree tool calls fall through to OpenCode's normal permission flow (which ocman's autoapprove pipeline can still handle at runtime). This is an accepted limitation, noted in Further Notes. - This decision composes with tracker #101 (inherit parent's always-allow permissions): both write `OPENCODE_PERMISSION` at launch; the `external_directory` rule is merged with any inherited permission set rather than overwriting it. ### D-5: `WorktreeSessionResult` returns a session ID, not a tmux target - `hostsvc.WorktreeSessionResult` changes: the primary output becomes the created **session ID** (plus worktree path, branch, reused flags). The tmux-target / opencode-launched fields that drove `tmux switch-client` are removed for this path. - The `/api/worktree/create-and-launch` response shape changes accordingly; the frontend navigates to the session in-app instead of switching tmux. ### D-6: Retire the per-worktree tmux machinery - Delete `tmux.LaunchWorktreeWindow`, `LaunchWorktreeWindowWith`, the `WorktreeSession` ("ocman-worktree") constant, and the per-worktree window naming / AD-3 / AD-4 idempotent-relaunch logic from the worktree-sessions spec. The one remaining tmux launch is the single project instance (via `EnsureProjectOpencode`, reusing `LaunchOpencode`-style session-per-main-checkout). - Old windows/processes from the previous flow are left alone (US-13); no teardown logic is added. ### D-7: Frontend joins worktree ↔ session by the session's own directory - The Worktrees view continues to associate sessions to worktrees by directory equality, but the source of truth is each **session's own `directory`** (now distinct from the opencode process cwd), which ocman surfaces per session. No change to the join *shape*, only to ensuring the per-session directory is available and used. ### D-8: Capability flag semantics unchanged, preconditions adjusted - The `worktreeSessions` capability stays server-wide. Its precondition set is unchanged in spirit (git + tmux + opencode on PATH + an OpenCode adapter registered) since the single project instance still runs in tmux. ## Testing Decisions Good tests here assert **external behavior at the seams**, not internal call order: - **`EnsureProjectOpencode` (host level)** — the primary new seam. Test against a fake runner/discoverer: - When an instance for the repo root is already discoverable → returns that port and launches nothing (idempotency / one-per-project). - When none is discoverable → launches exactly once, seeds `OPENCODE_PERMISSION` with the scoped `external_directory` rule, waits for the port, returns it. - Two concurrent/sequential calls for the same project → at most one launch. - Non-repo directory → clear error (`git.ErrNotARepo`). Prior art: `internal/tmux/launch_test.go` (fake `Runner`), `internal/mcp/launcher` tests (injected `launchTmux`/`discoverPort`). - **`opencode.CreateSession` sends directory (adapter level)** — against a fake OpenCode HTTP server, assert that `POST /session` carries the worktree directory (query param or header) and that the returned session's directory is honored. Prior art: `internal/platforms/opencode/operations_test.go` (`TestCreateSessionPinsReturnedSessionPort`, `TestCreateSession_TitleIsSetAfterCreation`). - **`local.Host.CreateWorktreeSession` (host orchestration)** — with a temp git repo and fakes for `EnsureProjectOpencode` + session create, assert: worktree created, project instance ensured, session created with `Directory == worktreePath`, result carries the session ID. - **`SessionLauncher.LaunchWithWorktree` / `Launch`** — assert both now route through the injected `EnsureProjectOpencode` and no longer call the retired per-worktree tmux launcher. Same-directory `Launch` self-heals (ensures instance) instead of erroring. - **HTTP handler** — `handlers_worktree_test.go`: response now carries a session ID; status-code mapping for the error paths preserved. - **Frontend** — `WorktreesView` associates sessions by per-session directory; `api.worktree.createAndLaunch` round-trip returns a session ID and navigation targets the session (not tmux switch). Prior art: existing worktree view + `api.test.ts`. Coverage ratchet applies: new functions (`EnsureProjectOpencode`, the `OPENCODE_PERMISSION` builder, the directory-sending branch of `CreateSession`) ship with tests in the same PR. ## Out of Scope - **Live permission propagation** after launch — the `external_directory` rule is seeded once at instance launch (D-4); re-seeding a pre-existing instance is out of scope. - **Tearing down** old per-worktree opencode/tmux windows from the previous flow (US-13) — they are left running. - **Restarting** an already-running project instance to inject the `external_directory` rule — out of scope (runtime autoapprove covers the gap). - **Non-OpenCode platforms** — OpenCode-only, consistent with the existing worktree feature. - **A user-configurable base path** for `.worktrees/` — unchanged from the current deterministic layout. - The full delivery of tracker **#101** (inherit parent always-allow) — this spec only requires that D-4's `OPENCODE_PERMISSION` seeding *merge with*, not conflict with, #101's mechanism. ## Further Notes - **Interaction with #101**: both features write `OPENCODE_PERMISSION` at launch. Implementation must merge the `external_directory` rule with any inherited permission set rather than overwrite. Sequencing: whichever lands second must account for the first. - **Pre-existing instance limitation**: if the project opencode was already running (user-launched, or launched before this feature), the `external_directory` rule was not seeded into it. Worktree tool calls in that case fall through to OpenCode's normal permission flow; ocman's runtime autoapprove pipeline can still allow them. This is acceptable for v1 and should be documented in `docs/` alongside the feature. - **Multi-remote**: `EnsureProjectOpencode` runs on the owning host (R-C), same as `CreateWorktreeSession` today. The hub never launches opencode for a remote's project. - **Docs to update**: `AGENTS.md` (worktree paragraph — no longer "one opencode per worktree"), `docs/architecture.md` (session/worktree data flow), and any worktree-sessions user docs.
dries closed this issue 2026-07-09 14:29:36 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
dries/ocman#265
No description provided.