fix(cli): keep automated worktree creation in background (#11445)

This commit is contained in:
Neil 2026-07-29 17:45:57 -07:00 committed by GitHub
parent 5e00a30e4e
commit 78b8a37aed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 21 additions and 19 deletions

View File

@ -143,12 +143,12 @@ ORCA worktree create --name task --setup skip --json
ORCA worktree create --name task --run-hooks --json
```
- `--agent <id>` launches that agent **in the first terminal** (Orca docs: *"`--agent` launches the selected agent in the first terminal"*); `--prompt <text>` sends initial work to it. Known ids include `claude`, `codex`, `omp`, `pi`, `grok`, and other installed TUI agents.
- `--agent <id>` launches that agent **in the first terminal** (Orca docs: _"`--agent` launches the selected agent in the first terminal"_); `--prompt <text>` sends initial work to it. Known ids include `claude`, `codex`, `omp`, `pi`, `grok`, and other installed TUI agents.
- **Prefer agent-first create for agent workers.** `orca worktree create --agent <id> --prompt "..."` puts the agent in the worktree's first terminal without adding a separate fallback shell for that worker. Repo setup or default-terminal settings may still add tabs or splits. Without configured default tabs, the bare-create fallback shell plus a later `terminal create --command <agent>` is an anti-pattern for ordinary agent worktrees — use `--agent` instead of “create worktree, then open agent.” Configured default tabs are intentional surfaces; never treat one as disposable without verifying that it is an unused shell.
- After create, use exactly one agent handle: `startupTerminal.handle` from the create response when present, or the matching result from `orca terminal list --worktree id:<repoId>::<newWorktreePath> --json` (or `name:<displayName>`) when the response omits it. If a handle later returns `terminal_handle_stale`, re-list it; never dual-send to old and replacement handles.
- `--setup run|skip|inherit` controls repo setup hooks. Default is `inherit`, which follows the repo's setup policy.
- `--run-hooks` is a legacy alias for `--setup run`; it also reveals/activates the new worktree.
- `--agent`, `--activate`, and `--run-hooks` reveal the new worktree. Plain create stays in the background.
- `--activate` and `--run-hooks` reveal the new worktree. `--agent` alone stays in the background.
- Let Orca choose setup terminal placement from repo settings, including tab vs split behavior. Do not manually create extra setup terminals when `--agent` already owns the first tab.
- If an older installed CLI rejects `--agent`, `--prompt`, or `--setup`, create the worktree normally, then run `orca terminal create --worktree <selector> --command "<requested-agent>"` and `orca terminal send` if a prompt is needed. This can leave a fallback shell when no default tabs are configured; close it only after confirming it is unused.
- `worktree create` creates a new checkout. For a fresh agent in the **current** checkout (no new worktree), use `orca terminal create --worktree active --command "codex" --json` — that path does not create a second worktree shell.

File diff suppressed because one or more lines are too long

View File

@ -237,8 +237,7 @@ export const WORKTREE_HANDLERS: Record<string, CommandHandler> = {
...linearIssueLink,
comment: getOptionalStringFlag(flags, 'comment'),
runHooks: flags.get('run-hooks') === true,
activate:
flags.get('activate') === true || flags.get('run-hooks') === true || Boolean(startupAgent),
activate: flags.get('activate') === true || flags.get('run-hooks') === true,
...(setupDecision ? { setupDecision } : {}),
parentWorktree: explicitParentWorktree,
...(explicitParentWorkspace ? { parentWorkspace: explicitParentWorkspace } : {}),

View File

@ -2826,7 +2826,7 @@ describe('orca cli worktree awareness', () => {
})
})
it('passes agent prompt and setup policy through worktree.create', async () => {
it('starts an agent worktree in the background unless activation is explicit', async () => {
queueFixtures(
callMock,
worktreeListFixture([buildWorktree('/tmp/repo', 'main', 'abc', 'repo-1')]),
@ -2864,7 +2864,7 @@ describe('orca cli worktree awareness', () => {
linkedIssue: undefined,
comment: undefined,
runHooks: false,
activate: true,
activate: false,
setupDecision: 'run',
parentWorktree: undefined,
cwdParentWorktree: 'id:repo-1::/tmp/repo',
@ -2876,7 +2876,7 @@ describe('orca cli worktree awareness', () => {
})
})
it('infers the repo from the current worktree on worktree.create', async () => {
it('infers the repo and honors explicit activation on worktree.create', async () => {
queueFixtures(
callMock,
worktreeListFixture([buildWorktree('/tmp/repo', 'main', 'abc', 'repo-1')]),
@ -2898,6 +2898,7 @@ describe('orca cli worktree awareness', () => {
'codex',
'--prompt',
'hi',
'--activate',
'--json'
],
'/tmp/repo/src'

View File

@ -211,20 +211,19 @@ describe('orchestration federation', () => {
remote_worktree_id: 'repo::windows-worktree',
remote_terminal_handle: 'term_windows_worker'
})
expect(workerDb.getRemoteDispatchAttachment(dispatch.id)).toMatchObject({
const attachment = workerDb.getRemoteDispatchAttachment(dispatch.id)
expect(attachment).toMatchObject({
task_id: task.id,
protocol_version: 2,
state: 'ready',
worktree_id: 'repo::windows-worktree',
terminal_handle: 'term_windows_worker'
})
expect(JSON.parse(workerDb.getRemoteDispatchAttachment(dispatch.id)?.effects ?? '[]')).toEqual(
expect.arrayContaining([
expect.objectContaining({ kind: 'dispatch_input', state: 'accepted' })
])
)
const fx = JSON.parse(attachment?.effects ?? '[]') as { kind?: string; state?: string }[]
expect(fx.some((x) => x.kind === 'dispatch_input' && x.state === 'accepted')).toBe(true)
expect(workerDb.listTasks()).toHaveLength(0)
expect(vi.mocked(workerRuntime.createManagedWorktree).mock.calls[0]?.[0].activate).toBe(false)
const create = vi.mocked(workerRuntime.createManagedWorktree).mock.calls[0]?.[0]
expect([create.activate, create.runHooks]).toEqual([false, false])
expect(workerRuntime.sendTerminalAgentPrompt).toHaveBeenCalledWith(
'term_windows_worker',
expect.stringContaining(`Your task ID is: ${task.id}`)

View File

@ -119,7 +119,8 @@ export const ORCHESTRATION_FEDERATION_ATTACH_METHODS: RpcMethod[] = [
baseBranch: params.baseBranch,
displayName: params.displayName,
comment: params.comment,
runHooks: setupDecision === 'run',
// setupDecision runs setup without the legacy runHooks activation side effect.
runHooks: false,
setupDecision,
awaitTerminalProvisioning: true,
observeSetupCompletion: true,

View File

@ -125,7 +125,8 @@ export async function createWorkerWorktree(args: {
baseBranch: params.baseBranch,
displayName: params.displayName,
comment: params.comment,
runHooks: setupDecision === 'run',
// setupDecision runs setup without the legacy runHooks activation side effect.
runHooks: false,
setupDecision,
awaitTerminalProvisioning: true,
observeSetupCompletion: true,

View File

@ -256,7 +256,7 @@ describe('orchestration new-worktree workers', () => {
const { result } = await startWorker({ setup })
expect(runtime.createManagedWorktree).toHaveBeenCalledWith(
expect.objectContaining({ setupDecision: setup, runHooks: setup === 'run' })
expect.objectContaining({ setupDecision: setup, runHooks: false, activate: false })
)
expect(result).toMatchObject({
state: 'ready',

View File

@ -2458,9 +2458,10 @@ describe('orchestration RPC methods', () => {
expect.objectContaining({
repoSelector: 'repo',
name: 'child-worker',
runHooks: true,
runHooks: false,
setupDecision: 'run',
startupAgent: 'codex',
activate: false,
lineage: expect.objectContaining({ parentWorktree: 'repo::parent', noParent: false })
})
)