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
Cross-project spawns: a thread can start workers in another project's repo #109
A thread is hard-bound to one project: every spawn uses thread.worktreePath || project.path as cwd (electron/runner.js:1320, 1515, 2238, 2603, 2972), and the sidebar/threads/activity all key off projectId.
Real work does not stay inside that box. Examples:
Working in the app repo and needing a matching change in the docs/site repo.
A memory-server or core change that needs a consumer repo updated in the same breath.
"Go look at how project X does this" — read-only, but currently impossible without switching projects and losing thread context.
Today the workarounds are all bad: start a second thread in the other project and hand-copy context, or let the agent cd out of its project and end up with a thread whose commits, diffs, PR list and worktree state all belong to a project the UI says it is not in.
Rough options (not decided):
Read-only reach — allow other project paths as extra readable roots, keep writes in the owning project. Smallest, covers the "go look at X" case.
Secondary projects on a thread — a thread declares extra project ids; runner passes their paths as additional dirs, and diff/commit/PR views group by project instead of assuming one.
Hand-off — a thread spawns a linked thread in the other project with the relevant context carried over, and the two show as related.
Open questions: what the diff/commit/worktree UI does when a run touches two repos; whether worktrees are created per project; how the sidebar shows a thread that spans projects.
changed the title [-]Cross-project work: a thread in one project sometimes needs to act in another[/-][+]Cross-project spawns: a thread can start workers in another project's repo[/+]on Aug 16, 2026
Reframed after investigating: the issue asked for cross-project reach, but the workspace already had accidental cross-project reach with no guard rail. Willem's ruling: a run should never write to a second repo. So this closes the hole rather than building the feature.
What actually happened
Shipnest-v2 thread, 2026-08-16 12:48–13:10. An agent working in /Users/willem/code/Shipnest-v2 called thread_fork({threadId: "260be2c1"}) — a Solenta thread. Two grok workers spawned on git worktrees of the wrong repo. Told off, it repeated the mistake four minutes later with 9fafc689, guessed from the title "changelog and website". Cleanup cost two deleted branches and a removed worktree.
Its own diagnosis, from the transcript:
"I have no way to see which project a thread belongs to — threads_list returns id, title, provider, status, nothing about the workspace — so every fork I make is a guess, and guessing is exactly what caused this."
Root cause
thread_fork(threadId) takes its source thread as an argument, and electron/orchServer.js has no caller identity at all — one workspace-wide bearer token over stateless HTTP. So the server cannot know who is calling, and the caller must name a thread. Naming one was necessarily a guess: the agent is never told its own thread id, and threads_list never said which project any thread belonged to.
thread_send had the same hole — it started a run on any threadId with no check.
Fixed in a12bd26
threads_list reports projectId and projectName per thread.
Every dispatched prompt carries a [Thread] note stating the run's own thread id, project and cwd. Gated on coder-threads being registered, mirroring planboardNoteFor's GitHub-origin gate.
thread_fork and thread_send require a stated projectId and reject a thread outside it, naming both projects. A same-project fork of a sibling thread still works — only the boundary is blocked.
Because each thread stays bound to one project, the diff/commit/PR/worktree views are untouched. That answers all three of this issue's original open questions with "nothing changes".
Known ceiling
The assert is a self-declared claim, not proof — it catches a confused caller, not a malicious one. Real caller identity needs per-run bearer tokens mapped to thread ids; grok and kimi can't receive those (they register MCP globally via grok mcp add … -s user and ~/.kimi-code/mcp.json), and the incident's workers were grok. Marked with a ponytail: comment naming the upgrade path.
Design doc: docs/superpowers/specs/2026-08-16-cross-project-spawn-guard-design.md. Tests: 1593 across core/renderer/electron/memory, 0 failures.
Deferred
Read-only reach into another project ("go look at how X does this") is left unbuilt until the leak fix has been lived with. Cross-project writes are off the table.
The guard shipped in #111. This is the piece that comment deferred — "go look at how project X does this", read-only.
The finding that shaped it: there is no sandbox to open. Solenta passes no sandbox or approval flags to any provider, and grok, kimi and opencode all run tools unprompted in headless mode (providers.js:275, :393, :475). codex is held only by its own default sandbox. Only claude has a permission channel. So four of five providers can already read and write any repo on this disk — this feature grants no new capability. What it adds is the agent being told which checkouts exist and that they are read-only, plus not being nagged per file on claude.
--add-dir is not used anywhere: it makes a directory a full workspace dir, which grants writes.
Per-thread opt-in readProjectIds, set from a "Read access" submenu in the thread overflow menu. Forks inherit it.
readRootsFor resolves ids → on-disk roots, dropping deleted and remote-host projects at read time.
A [Read access] sentence in the dispatched prompt naming each root, ungated (the existing [Thread] sentence keeps its coder-threads gate).
On claude, autoAllowRead answers allow to Read/Grep/Glob/NotebookRead under a root. Edit, Write, Bash and anything unrecognised still prompt, so a write into another repo stays a decision the user makes.
Nothing in diff/commit/PR/worktree/sidebar changes — writes still only land in the owning project.
Known ceiling: on grok, kimi and opencode the read-only rule is a sentence in the prompt, nothing more. Marked with a ponytail: comment, same as assertSameProject.
Plan:docs/superpowers/plans/2026-08-16-cross-project-read-reach.md — 6 tasks, TDD, on branch coder/cross-project-spawns-a-thread-can-start--c056ca.
Status check: resolved by product decision rather than by building the feature. After an incident where an agent forked a foreign thread by guessing its id, the ruling was 'a run must never write to a second repo'. What shipped (PR #111): assertSameProject in orchServer.js blocks cross-project fork/send, threads_list rows carry projectId/projectName, runs are told their own thread/project id. The read-only-reach half has a committed design + implementation plan (docs/superpowers/specs/2026-08-16-cross-project-read-reach-design.md, docs/superpowers/plans/2026-08-16-cross-project-read-reach.md) but was explicitly deferred and has no code. Closing this as decided; the read-reach plan can be revived as its own issue when wanted.
Activity
[-]Cross-project work: a thread in one project sometimes needs to act in another[/-][+]Cross-project spawns: a thread can start workers in another project's repo[/+]currentbits commentedon Aug 16, 2026
Reframed after investigating: the issue asked for cross-project reach, but the workspace already had accidental cross-project reach with no guard rail. Willem's ruling: a run should never write to a second repo. So this closes the hole rather than building the feature.
What actually happened
Shipnest-v2 thread, 2026-08-16 12:48–13:10. An agent working in
/Users/willem/code/Shipnest-v2calledthread_fork({threadId: "260be2c1"})— a Solenta thread. Two grok workers spawned on git worktrees of the wrong repo. Told off, it repeated the mistake four minutes later with9fafc689, guessed from the title "changelog and website". Cleanup cost two deleted branches and a removed worktree.Its own diagnosis, from the transcript:
Root cause
thread_fork(threadId)takes its source thread as an argument, andelectron/orchServer.jshas no caller identity at all — one workspace-wide bearer token over stateless HTTP. So the server cannot know who is calling, and the caller must name a thread. Naming one was necessarily a guess: the agent is never told its own thread id, andthreads_listnever said which project any thread belonged to.thread_sendhad the same hole — it started a run on any threadId with no check.Fixed in a12bd26
threads_listreportsprojectIdandprojectNameper thread.[Thread]note stating the run's own thread id, project and cwd. Gated oncoder-threadsbeing registered, mirroringplanboardNoteFor's GitHub-origin gate.thread_forkandthread_sendrequire a statedprojectIdand reject a thread outside it, naming both projects. A same-project fork of a sibling thread still works — only the boundary is blocked.Because each thread stays bound to one project, the diff/commit/PR/worktree views are untouched. That answers all three of this issue's original open questions with "nothing changes".
Known ceiling
The assert is a self-declared claim, not proof — it catches a confused caller, not a malicious one. Real caller identity needs per-run bearer tokens mapped to thread ids; grok and kimi can't receive those (they register MCP globally via
grok mcp add … -s userand~/.kimi-code/mcp.json), and the incident's workers were grok. Marked with aponytail:comment naming the upgrade path.Design doc:
docs/superpowers/specs/2026-08-16-cross-project-spawn-guard-design.md. Tests: 1593 across core/renderer/electron/memory, 0 failures.Deferred
Read-only reach into another project ("go look at how X does this") is left unbuilt until the leak fix has been lived with. Cross-project writes are off the table.
currentbits commentedon Aug 16, 2026
PR: #111
Merge pull request #111 from currentbits/coder/cross-project-work-a-t…
currentbits commentedon Aug 16, 2026
Second half: read-only reach, designed
The guard shipped in #111. This is the piece that comment deferred — "go look at how project X does this", read-only.
The finding that shaped it: there is no sandbox to open. Solenta passes no sandbox or approval flags to any provider, and grok, kimi and opencode all run tools unprompted in headless mode (
providers.js:275,:393,:475). codex is held only by its own default sandbox. Only claude has a permission channel. So four of five providers can already read and write any repo on this disk — this feature grants no new capability. What it adds is the agent being told which checkouts exist and that they are read-only, plus not being nagged per file on claude.--add-diris not used anywhere: it makes a directory a full workspace dir, which grants writes.Shape (design:
docs/superpowers/specs/2026-08-16-cross-project-read-reach-design.md):readProjectIds, set from a "Read access" submenu in the thread overflow menu. Forks inherit it.readRootsForresolves ids → on-disk roots, dropping deleted and remote-host projects at read time.[Read access]sentence in the dispatched prompt naming each root, ungated (the existing[Thread]sentence keeps itscoder-threadsgate).autoAllowReadanswersallowtoRead/Grep/Glob/NotebookReadunder a root.Edit,Write,Bashand anything unrecognised still prompt, so a write into another repo stays a decision the user makes.Known ceiling: on grok, kimi and opencode the read-only rule is a sentence in the prompt, nothing more. Marked with a
ponytail:comment, same asassertSameProject.Plan:
docs/superpowers/plans/2026-08-16-cross-project-read-reach.md— 6 tasks, TDD, on branchcoder/cross-project-spawns-a-thread-can-start--c056ca.currentbits commentedon Aug 16, 2026
Design + plan PR: #119 (docs only; implementation not started).
Merge pull request #119 from currentbits/coder/cross-project-spawns-a…
currentbits commentedon Aug 18, 2026
Status check: resolved by product decision rather than by building the feature. After an incident where an agent forked a foreign thread by guessing its id, the ruling was 'a run must never write to a second repo'. What shipped (PR #111):
assertSameProjectin orchServer.js blocks cross-project fork/send, threads_list rows carry projectId/projectName, runs are told their own thread/project id. The read-only-reach half has a committed design + implementation plan (docs/superpowers/specs/2026-08-16-cross-project-read-reach-design.md, docs/superpowers/plans/2026-08-16-cross-project-read-reach.md) but was explicitly deferred and has no code. Closing this as decided; the read-reach plan can be revived as its own issue when wanted.