> Summary: Fix OMO `task` so delegated subagents can use repo exploration tools in their child sessions without permission prompts or denials. Match OpenCode native Task semantics by deriving child-session permissions and prompt tools from the delegated agent instead of only denying `question`.
> Deliverables:
> - Shared subagent permission/tool builder with unit coverage
> - Background and unstable delegate-task permission fix
> - Regression coverage for `read`/`bash`/search access and write/tool delegation denial
> - Module QA plus real tmux/non-interactive QA evidence
> Effort: Medium
> Risk: Medium — permission rules affect delegated child-session execution and can accidentally over-allow tools if not scoped.
## Scope
### Must have
- Subagents launched through OMO `task` can call repo exploration tools such as `read`, `bash`, `grep`, and `glob` in child sessions.
- Sync child sessions are created with `parentID`, OpenCode-compatible title, model fields, directory route, and a derived permission ruleset.
- Background child sessions receive the same derived permission ruleset through `LaunchInput.sessionPermission`.
- Prompt bodies for sync, background launch, background resume, and fallback-agent retry use the same derived prompt tool map so `session.prompt` does not overwrite useful child-session permissions with deny-only rules.
-`question` remains denied for delegated child sessions.
- Read-only subagents still cannot write or recursively delegate: `write`, `edit`, `apply_patch`, `task`, and `call_omo_agent` remain denied where the delegated agent restrictions deny them.
- Existing task metadata contract remains intact: `sessionId` is published and visible task metadata still includes `session_id`.
- Regression tests prove both sync and background paths include explicit `allow` rules for exploration tools and explicit `deny` rules for restricted tools.
- Real tmux/manual QA proves an `explore` subagent launched by `task` can inspect files and run a harmless shell command without `"Permission required"` output.
### Must NOT have (guardrails, anti-slop, scope boundaries)
- Do not disable OpenCode permission checks globally.
- Do not add `permission: "*", action: "allow"` or any broad wildcard allow.
- Do not grant write/edit/apply_patch to `explore`, `librarian`, or `oracle`.
- Do not refactor background polling, concurrency, wake gating, model fallback, or tmux layout.
- Do not change agent prompts, category model selection, metadata formatting, or task output text except where tests require permission metadata.
- Do not remove `getAgentToolRestrictions`; centralize the new derived session permission behavior around it or a closely related shared helper.
- Do not change the native OpenCode source under `../opencode`.
## Verification strategy
> Zero human intervention — all verification is agent-executed.
- Test decision: TDD + Bun test
- QA policy: every task has agent-executed scenarios
- Evidence: `evidence/task-<N>-<slug>.<ext>`
## Execution strategy
### Parallel execution waves
> Target 5-8 tasks per wave. <3 per wave (except final) = under-splitting.
> Extract shared dependencies as Wave-1 tasks to maximize parallelism.
Wave 1 (no dependencies):
- Task 1: Add shared subagent permission/tool builder and focused unit tests
What to do: Create one focused helper, preferably `src/shared/subagent-session-permission.ts`, plus `src/shared/subagent-session-permission.test.ts`. The helper must produce both:
- Add explicit `allow` rules for repo exploration tools when not denied by the agent: `read`, `bash`, `grep`, `glob`, `lsp_symbols`, `lsp_goto_definition`, `lsp_find_references`, `lsp_diagnostics`, `ast_grep_search`.
- Apply agent/tool deny rules last so read-only restrictions win over defaults.
- Preserve team-tool denylist behavior when `includeTeamToolDenylist` is true.
- Do not add wildcard allow rules.
Must NOT do: Do not change any call sites yet. Do not change agent definitions. Do not add a generic `utils.ts` or `helpers.ts`.
References (executor has NO interview context — be exhaustive):
- Pattern: `src/shared/agent-tool-restrictions.ts:24` — current read-only denylist omits explicit `read`/`bash` allows, which leaves child sessions in ask/permission-required state.
- Pattern: `src/shared/agent-tool-restrictions.ts:66` — current `getAgentToolRestrictions()` is the source of prompt-body deny rules and team-tool deny rules.
- Pattern: `src/shared/question-denied-session-permission.ts:1` — current ruleset shape and `QUESTION_DENIED_SESSION_PERMISSION`.
- Pattern: `src/shared/permission-compat.ts:6` — OMO agent permission map value type.
- Pattern: `src/agents/explore.ts:27` — `explore` denies write/edit/apply_patch/task/call_omo_agent and explicitly allows some LSP/AST tools.
- External: `../opencode/packages/opencode/src/agent/subagent-permissions.ts:17` — native Task derives child-session permissions from parent and subagent rules.
- External: `../opencode/packages/opencode/src/permission/evaluate.ts:9` — missing rule defaults to `ask`, which is the source of permission prompts/denials.
- Test: `src/agents/tool-restrictions.test.ts` — existing assertions around agent permission maps.
Acceptance criteria (agent-executable only):
- [ ]`bun test src/shared/subagent-session-permission.test.ts --bail` passes.
- [ ] Test asserts `buildSubagentSessionPermission("explore")` contains `allow` for `read`, `bash`, `grep`, and `glob`.
- [ ] Test asserts `buildSubagentSessionPermission("explore")` contains `deny` for `write`, `edit`, `apply_patch`, `task`, `call_omo_agent`, and `question`.
- [ ] Test asserts no generated rule is `{ permission: "*", action: "allow", pattern: "*" }`.
- [ ] Test asserts prompt tools mirror the permission intent: exploration tools `true`, restricted tools `false`.
QA scenarios (MANDATORY — task incomplete without these):
The tests must assert that child session creation receives a permission ruleset with explicit exploration allows and restricted-tool denies. Keep existing title, `parentID`, and directory assertions.
Must NOT do: Do not make broad behavior changes in this task except the minimal helper import needed if Task 1 already exists. Do not delete the old `question` denial assertion; update it into the larger ruleset.
References (executor has NO interview context — be exhaustive):
- Pattern: `src/tools/delegate-task/sync-session-creator.test.ts:5` — current sync child-session test only expects `question` deny.
- Pattern: `src/tools/delegate-task/background-task.test.ts:209` — current delegate background launch test only expects `question` deny in `sessionPermission`.
- Pattern: `src/features/background-agent/manager-session-permission.test.ts:84` — manager-level test asserts explicit session permission rules are passed into `session.create`.
- External: `../opencode/packages/opencode/src/tool/task.ts:152` — native Task creates child session with `parentID`, title, and derived permission.
- External: `../opencode/packages/opencode/src/tool/task.ts:178` — native Task metadata includes child `sessionId`.
Acceptance criteria (agent-executable only):
- [ ] `bun test src/tools/delegate-task/sync-session-creator.test.ts src/tools/delegate-task/background-task.test.ts src/features/background-agent/manager-session-permission.test.ts --bail` initially fails before Tasks 3 and 4 if run after only test edits.
- [ ] Tests assert `parentID` and title stay unchanged.
- [ ] Tests assert permission arrays contain `allow` for `read` and `bash`.
- [ ] Tests assert permission arrays contain `deny` for `question`, `write`, `edit`, `apply_patch`, and `task`.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: contract tests capture the regression
Tool: bash
Steps: mkdir -p evidence && bun test src/tools/delegate-task/sync-session-creator.test.ts src/tools/delegate-task/background-task.test.ts src/features/background-agent/manager-session-permission.test.ts --bail > evidence/task-2-contract-tests.txt || true
Expected: Evidence shows the new expectations before implementation, or passes if Task 3/4 are already applied by parallel execution.
- [ ] 3. Wire sync delegate-task session creation and prompt body
What to do:
- Update `createSyncSession()` to accept an optional `sessionPermission` argument and use it instead of hardcoded `QUESTION_DENIED_SESSION_PERMISSION`.
- Update `executeSyncTask()` to pass `buildSubagentSessionPermission(agentToUse, ...)` on initial child session creation and retry child session creation.
- Update `sendSyncPrompt()` to use `buildSubagentPromptTools(agentToUse, { allowTask })` rather than assembling a deny-only map inline.
- Keep `setSessionTools()` and `applySessionPromptParams()` behavior.
- Keep `routePromptRetry()` and `routePromptSyncRetry()` behavior unchanged.
Must NOT do: Do not alter polling, fetch result, fallback selection, metadata formatting, or `promptAsync` gate behavior.
- Pattern: `src/tools/delegate-task/sync-task.ts:88` — initial sync child session is created here.
- Pattern: `src/tools/delegate-task/sync-task.ts:264` — fallback retry creates another sync child session and must receive the same derived permission rules.
- Pattern: `src/tools/delegate-task/sync-prompt-sender.ts:70` — prompt body currently builds tools from `getAgentToolRestrictions()` only.
- Pattern: `src/tools/delegate-task/sync-prompt-route.test.ts:16` — existing route tests must keep passing.
- External: `../opencode/packages/opencode/src/session/prompt.ts:1622` — prompt `tools` are converted into session permission rules, so prompt body must not erase exploration allows.
- Update `src/features/background-agent/spawner.ts` initial prompt body, resume prompt body, and fallback-agent prompt body to use `buildSubagentPromptTools()`.
References (executor has NO interview context — be exhaustive):
- Pattern: `src/features/background-agent/spawner.ts:158` — initial background prompt body currently builds tool map inline.
- Pattern: `src/features/background-agent/spawner.ts:29` — fallback prompt body currently builds a second inline tool map.
- Pattern: `src/features/background-agent/spawner.ts:299` — resume prompt body currently builds another inline tool map.
- Test: `src/features/background-agent/manager-session-permission.test.ts:9` — already captures prompt route and can be extended or paired with a new focused test.
- External: `../opencode/packages/opencode/src/session/prompt.ts:1622` — prompt body tools become session permission rules.
Acceptance criteria (agent-executable only):
- [ ] `bun test src/features/background-agent/manager-session-permission.test.ts src/features/background-agent/manager.test.ts src/features/background-agent/spawner.test.ts --bail` passes, or if `manager.test.ts` is too broad/slow, record the narrower replacement command in evidence.
- [ ] A launch prompt test asserts exploration tools are explicitly true and restricted tools false.
- [ ] A resume prompt test asserts the same tool map contract.
- [ ] Existing fallback-agent retry tests still pass.
QA scenarios (MANDATORY — task incomplete without these):
Scenario: no prompt gate or fallback routing regression
Tool: bash
Steps: bun test src/features/background-agent/manager.test.ts src/features/background-agent/spawner.test.ts --bail > evidence/task-5-background-manager.txt
Expected: Exit 0, or if pre-existing unrelated failures occur, evidence includes exact failing test names and a narrower passing command that covers spawner prompt behavior.
Evidence: evidence/task-5-background-manager.txt
```
Commit: YES | Message: `fix(background-agent): keep exploration tools enabled in subagent prompts` | Files: [`src/features/background-agent/spawner.ts`, `src/features/background-agent/manager-session-permission.test.ts`, related tests]
- [ ] 6. Run module QA and lock regression evidence
What to do:
- Run focused delegate-task/background-agent tests.
- Run typecheck.
- Run the full root test suite if focused tests and typecheck pass.
- Capture evidence files and summarize failures only if unrelated/pre-existing.
Must NOT do: Do not fix unrelated failures. Do not weaken tests to pass. Do not skip typecheck.
- [ ] 7. Run real tmux/manual QA for delegated exploration
What to do:
- Build the local plugin.
- Run a real OMO non-interactive session inside tmux from this repo that forces `task(subagent_type="explore", run_in_background=false or true)` to inspect a known file and run a harmless shell command.
- Confirm output contains the expected file fact and does not contain `"Permission required"`, `"missing permission"`, or `"permission denied"` for `read`/`bash`.
- Repeat with background mode and collect `background_output` after system completion.
- Capture tmux pane output and `/tmp/oh-my-opencode.log` excerpts.
Must NOT do: Do not run destructive shell commands. Do not use `sleep`; use tmux capture/polling loops with bounded attempts.
- [ ] tmux sync QA output includes an `explore` result referencing `src/tools/delegate-task/sync-session-creator.ts`.
- [ ] tmux sync QA output has no case-insensitive match for `Permission required|missing permission|permission denied`.
- [ ] tmux background QA output includes a background task ID and collected result.
- [ ] `/tmp/oh-my-opencode.log` has no child-session permission rejection for the QA session.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: sync delegated explore can read and run harmless bash
Tool: tmux
Steps: mkdir -p evidence && bun run build > evidence/task-7-build.txt && tmux new-session -d -s omo-delegate-sync-qa 'cd /Users/yeongyu/local-workspaces/omo && bun src/cli/index.ts run --agent Sisyphus --directory /Users/yeongyu/local-workspaces/omo --json "Use task with subagent_type=explore, run_in_background=false, load_skills=[] to inspect src/tools/delegate-task/sync-session-creator.ts and run pwd. Report the permission field behavior and the cwd."' ; poll `tmux capture-pane -pt omo-delegate-sync-qa` until the command exits or the pane shows JSON; save final capture.
Expected: Capture includes a useful exploration result and no permission-required text.
Evidence: evidence/task-7-sync-tmux.txt
Scenario: background delegated explore can read and run harmless bash
Tool: tmux
Steps: tmux new-session -d -s omo-delegate-bg-qa 'cd /Users/yeongyu/local-workspaces/omo && bun src/cli/index.ts run --agent Sisyphus --directory /Users/yeongyu/local-workspaces/omo --json "Launch task subagent_type=explore with run_in_background=true and load_skills=[] to inspect src/shared/agent-tool-restrictions.ts. Wait for completion notification, collect background_output, and report whether read/bash were usable."' ; poll `tmux capture-pane -pt omo-delegate-bg-qa` until JSON or completion; save final capture and relevant log excerpt.
Expected: Capture includes background result and no permission-required text.
Evidence: evidence/task-7-background-tmux.txt
```
Commit: NO | Message: `test(task): capture real delegate-task permission QA` | Files: [`evidence/task-7-build.txt`, `evidence/task-7-sync-tmux.txt`, `evidence/task-7-background-tmux.txt`]
## Final verification wave (MANDATORY — after all implementation tasks)
> Runs in PARALLEL. ALL must APPROVE. Surface results to the caller and wait for an explicit "okay" before declaring complete.
- [ ] F1. Plan compliance audit — every task done, every acceptance criterion met
- [ ] F2. Code quality review — diagnostics clean, idioms match, no dead code
- [ ] F3. Real manual QA — every QA scenario executed with evidence captured
> Summary: Fix OMO's internal prompt gate so failed or timed-out prompt dispatches do not leave a stale reservation that blocks sibling recovery prompts after OpenCode `promptAsync` returns before durable prompt completion. Keep behavior unchanged except the bug fix, prove it with failing-first Bun tests, actual tmux-backed QA, CI, review-work, and Cubic before merge.
> - Route-level regressions for model fallback, runtime fallback, background wakes, and team live delivery
> - Updated `.debugging` journal with root-cause evidence and cleanup ledger
> - PR against `dev`, green CI, review-work pass, Cubic pass, merged branch, removed worktree
> Effort: Medium
> Risk: High — async prompt acceptance is fire-and-forget upstream, so duplicate suppression and recovery retry timing are easy to regress.
## Scope
### Must have
- Work only in `/Users/yeongyu/local-workspaces/gpt 5.5 xhigh` on branch `code-yeongyu/fix-prompt-hang-race`.
- Preserve the sibling OpenCode repo at `/Users/yeongyu/local-workspaces/opencode`; read and run it only as evidence unless an explicit later request changes scope.
- Keep `.debugging` current with hypotheses, red/green evidence, manual QA evidence, artifacts, and cleanup status.
- Add failing-first tests before the fix for the stale prompt reservation behavior.
- Fix the smallest mechanism that makes failed or timed-out prompt dispatches release their reservation promptly while successful dispatches still dedupe immediate duplicate prompts.
- Cover main-session internal prompt routes: model fallback, runtime fallback, session recovery, background-agent parent wakes, and team-mode live delivery/wake hints.
- Run actual manual QA through local commands and tmux sessions owned by this task.
- Commit atomically, create a PR, iterate until CI, review-work, and Cubic are all passing, merge, then remove the worktree.
### Must NOT have (guardrails, anti-slop, scope boundaries)
- Do not modify sibling OpenCode product code.
- Do not kill the tmux server; only kill tmux sessions created by this task.
- Do not add compatibility layers, config switches, broad retry frameworks, or unrelated refactors.
- Do not bypass `src/shared/prompt-async-gate.ts` for any production internal prompt route.
- Do not delete failing tests, suppress type errors, use `as any`, `@ts-ignore`, or `@ts-expect-error`.
- Do not use `git reset --hard`, `git checkout --`, `rm -rf`, `--no-verify`, or direct `bun publish`.
- Do not treat a passing unit suite as enough; actual QA must exercise the local OpenCode/OMO prompt path.
## Verification strategy
> Zero human intervention — all verification is agent-executed.
- Test decision: TDD + Bun test (`bun:test`)
- QA policy: every task has agent-executed scenarios
- Evidence: `evidence/task-<N>-<slug>.<ext>`
## Execution strategy
### Parallel execution waves
> Target 5–8 tasks per wave. <3 per wave (except final) = under-splitting.
> Extract shared dependencies as Wave-1 tasks to maximize parallelism.
> Implementation + Test = ONE task. Never separate.
> Every task MUST have: References + Acceptance Criteria + QA Scenarios + Commit.
- [ ] 1. Confirm root cause and journal evidence
What to do: Update `.debugging` with the confirmed causal chain and the exact evidence already found: OpenCode `promptAsync` returns 204 after forking `SessionPrompt.prompt`, the fork later publishes `session.error` on failure, and OMO's gate can leave a short-lived reservation that overlaps recovery routes. Add the red/green/QA evidence ledger sections before creating more artifacts.
Must NOT do: Do not alter source code in this task. Do not remove existing `.debugging` entries.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/sdk/js/src/gen/types.gen.ts:2723` — `SessionPromptAsyncResponses` type starts.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/sdk/js/src/gen/types.gen.ts:2727` — `promptAsync` success is `204: void`.
- External: `https://github.com/anomalyco/opencode/issues/11616` — public docs issue describes `/prompt_async` as returning immediately and lists `session.error`.
- External: `https://github.com/anomalyco/opencode/issues/12860` — public issue reports `/prompt_async` status can stay unknown after submission.
Acceptance criteria (agent-executable only):
- [ ]`rg -n "Root cause|promptAsync|204|session.error|NoContent|fork" .debugging` prints the updated root-cause section.
- [ ]`rg -n "Artifacts To Revert|worktree|code-yeongyu/fix-prompt-hang-race|tmux" .debugging` confirms the cleanup ledger mentions the branch, worktree, and tmux constraints.
- [ ]`git diff -- .debugging > evidence/task-1-journal.diff` captures only journal changes for this task.
QA scenarios (MANDATORY — task incomplete without these):
What to do: Add failing-first tests in the existing gate test file proving that timed-out and rejected dispatches release their reservation even when `postDispatchHoldMs` is the default, while successful dispatches keep the short post-dispatch hold. Then minimally change `src/shared/prompt-async-gate.ts` so post-dispatch hold is applied only after a real `dispatched` result, not merely after `dispatchAttempted = true`. If the executor confirms that OpenCode's 204 is still too early for recovery routes, keep the gate change minimal and leave route-triggered release to Tasks 4 and 5.
Must NOT do: Do not remove the successful-dispatch hold. Do not add a queue, debounce framework, global lock, or new config option.
- Pattern: `src/shared/prompt-async-gate.ts:204` — current hold applies after any attempted dispatch.
- Pattern: `src/hooks/shared/prompt-async-gate.test.ts:60` — existing successful-dispatch hold test.
- Pattern: `src/hooks/shared/prompt-async-gate.test.ts:282` — existing timeout test uses `postDispatchHoldMs: 0`; add the default-hold regression beside it.
- Pattern: `src/hooks/shared/prompt-async-gate.test.ts:321` — existing rejected-dispatch test currently expects duplicate blocking; update or supersede with the corrected failing-first behavior.
- Test: `src/hooks/shared/prompt-async-gate.test.ts` — co-located Bun tests import from the shared gate re-export.
Acceptance criteria (agent-executable only):
- [ ] Before changing `src/shared/prompt-async-gate.ts`, `bun test src/hooks/shared/prompt-async-gate.test.ts --bail` fails on the new timeout/rejection reservation test; save output to `evidence/task-2-red.txt`.
- [ ] After the minimal fix, `bun test src/hooks/shared/prompt-async-gate.test.ts --bail` passes; save output to `evidence/task-2-green.txt`.
- [ ] `bun test src/shared/prompt-async-route-audit.test.ts src/hooks/shared/prompt-async-gate.test.ts --bail` passes.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: failed promptAsync releases reservation
Tool: bash
Steps: mkdir -p evidence; bun test src/hooks/shared/prompt-async-gate.test.ts --bail 2>&1 | tee evidence/task-2-green.txt
Expected: test output exits 0 and includes the new case where an immediate rejected dispatch lets the next caller attempt dispatch instead of returning reserved.
Evidence: evidence/task-2-green.txt
Scenario: successful promptAsync still dedupes immediate duplicate
Tool: bash
Steps: bun test src/hooks/shared/prompt-async-gate.test.ts --bail 2>&1 | tee evidence/task-2-success-hold.txt
Expected: existing successful hold tests still pass and assert prompt call count remains 1 for immediate duplicate after dispatch.
What to do: Extend `src/shared/prompt-async-route-audit.test.ts` only if needed so the production invariant remains pinned: raw `session.prompt`/`session.promptAsync` calls stay inside the shared gate or documented wrappers, production callers cannot set `postDispatchHoldMs: 0`, and new route wrappers must throw or requeue on `failed` instead of silently dropping prompt failures. Keep the allowlist small and documented.
Must NOT do: Do not add a broad allowlist for convenience. Do not weaken the existing raw-prompt scanner.
- Pattern: `src/shared/prompt-async-route-audit.test.ts:249` — production raw prompt audit test starts.
- Pattern: `src/shared/prompt-async-route-audit.test.ts:270` — production `postDispatchHoldMs: 0` audit starts.
- Pattern: `src/plugin/unstable-agent-babysitter.ts:29` — wrapper currently ignores non-failed statuses and should be assessed by audit or route tests.
- Pattern: `src/features/background-agent/parent-wake-notifier.ts:153` — failed result is thrown and requeued in catch.
- Pattern: `src/features/team-mode/tools/messaging.ts:212` — non-dispatched live delivery falls back to inbox.
Acceptance criteria (agent-executable only):
- [ ] `bun test src/shared/prompt-async-route-audit.test.ts --bail` passes.
- [ ] If the audit is changed, first save a red run in `evidence/task-3-red.txt` proving the audit catches the intended bad pattern.
- [ ] `rg -n "postDispatchHoldMs\\s*:\\s*0" src --glob '*.ts' --glob '!*.test.ts'` returns no production offenders.
- [ ] `bun test src/shared/prompt-async-route-audit.test.ts src/hooks/shared/prompt-async-gate.test.ts --bail` passes.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: raw prompt audit remains strict
Tool: bash
Steps: mkdir -p evidence; bun test src/shared/prompt-async-route-audit.test.ts --bail 2>&1 | tee evidence/task-3-audit.txt
Expected: command exits 0 and reports the production prompt route audit passing with the existing narrow allowlist.
Evidence: evidence/task-3-audit.txt
Scenario: production callers do not disable the hold
Tool: bash
Steps: rg -n "postDispatchHoldMs\\s*:\\s*0" src --glob '*.ts' --glob '!*.test.ts' 2>&1 | tee evidence/task-3-hold-audit.txt; test "${PIPESTATUS[0]}" -eq 1
Expected: no production TypeScript file sets postDispatchHoldMs to 0.
What to do: Add or update model-fallback tests so an OpenCode-style sequence is pinned: first internal `promptAsync` returns/appears accepted, then a `session.error` arrives before the post-dispatch hold expires. Same-model duplicate events must remain deduped, but a legitimate next fallback/recovery route must not be skipped solely because of a stale reservation. Make the smallest route fix in `src/plugin/event.ts` only if the shared gate fix does not satisfy the tests.
Must NOT do: Do not merge model-fallback and runtime-fallback state machines. Do not broaden fallback eligibility.
What to do: Add runtime-fallback tests proving a failed, timed-out, or OpenCode-style async error retry clears `sessionRetryInFlight`, `sessionAwaitingFallbackResult`, fallback timeout, and pending model state so the next eligible fallback attempt can dispatch. Make the smallest fix in `src/hooks/runtime-fallback/auto-retry.ts` if state cleanup or reservation release is incomplete.
Must NOT do: Do not change fallback model selection order, cooldown policy, or visible-response detection.
- Test: `src/hooks/runtime-fallback/success-retry-key-cleanup.test.ts` — cleanup-specific test style.
Acceptance criteria (agent-executable only):
- [ ] Red evidence saved to `evidence/task-5-red.txt` for the new retry cleanup regression before implementation.
- [ ] `bun test src/hooks/runtime-fallback/index.test.ts src/hooks/runtime-fallback/success-retry-key-cleanup.test.ts --bail` passes.
- [ ] New assertions prove a second fallback attempt reaches `promptAsync` after the first failed or timed out attempt.
- [ ] No existing runtime-fallback duplicate suppression test starts dispatching duplicate same-source retries.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: failed runtime fallback retry clears state
Tool: bash
Steps: mkdir -p evidence; bun test src/hooks/runtime-fallback/index.test.ts src/hooks/runtime-fallback/success-retry-key-cleanup.test.ts --bail 2>&1 | tee evidence/task-5-runtime-fallback.txt
Expected: command exits 0 and includes the new cleanup regression.
Evidence: evidence/task-5-runtime-fallback.txt
Scenario: in-flight duplicate suppression still works
Tool: bash
Steps: bun test src/hooks/runtime-fallback/index.test.ts --bail 2>&1 | tee evidence/task-5-inflight.txt
Expected: command exits 0 and the existing in-flight race test still expects a single fallback preparation while the retry is pending.
- [ ] 6. Cover background-agent prompt wake and resume paths
What to do: Add tests around background-agent launch/resume/parent-wake prompt failures so a failed or timed-out gated prompt does not leave the task in a hanging in-between state and does not block the retry/restore path behind a stale reservation. Fix only the affected background-agent path if the shared gate is not enough.
Must NOT do: Do not change background concurrency limits, polling stability thresholds, task state schema, or tmux behavior.
- Pattern: `src/features/background-agent/manager.ts:1293` — resume dispatches through `promptAsyncAfterSessionIdle`.
- Pattern: `src/features/background-agent/manager.ts:1321` — failed resume prompt throws into catch.
- Pattern: `src/features/background-agent/manager.ts:1339` — resume failure can try fallback retry.
- Pattern: `src/features/background-agent/parent-wake-notifier.ts:137` — parent wake dispatches through `promptAsyncAfterSessionIdle`.
- Pattern: `src/features/background-agent/parent-wake-notifier.ts:153` — failed parent wake throws and requeues.
- Pattern: `src/features/background-agent/manager.test.ts:7548` — existing stale launch prompt error regression.
- Pattern: `src/features/background-agent/manager.test.ts:7556` — existing launch prompt can remain pending until rejected.
- Test: `src/features/background-agent/manager.test.ts` — large integration-style manager test file.
- Test: `src/features/background-agent/parent-wake-notifier.test.ts` — use if present; otherwise add focused coverage next to the notifier.
Acceptance criteria (agent-executable only):
- [ ] Red evidence saved to `evidence/task-6-red.txt` before background-agent fix.
- [ ] `bun test src/features/background-agent/manager.test.ts --bail` passes.
- [ ] If a notifier test file exists or is added, `bun test src/features/background-agent/parent-wake-notifier.test.ts --bail` passes.
- [ ] Assertions prove failed parent wake is requeued and failed resume restores or transitions task state instead of leaving an in-flight prompt hang.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: launch/resume failure does not hang task state
Tool: bash
Steps: mkdir -p evidence; bun test src/features/background-agent/manager.test.ts --bail 2>&1 | tee evidence/task-6-background-manager.txt
Expected: command exits 0 and includes the new failed prompt launch/resume regression.
Evidence: evidence/task-6-background-manager.txt
Scenario: parent wake is requeued after failed prompt
Tool: bash
Steps: if [ -f src/features/background-agent/parent-wake-notifier.test.ts ]; then bun test src/features/background-agent/parent-wake-notifier.test.ts --bail; else bun test src/features/background-agent/manager.test.ts --bail; fi 2>&1 | tee evidence/task-6-parent-wake.txt
Expected: command exits 0 and the tested path asserts a wake is requeued after a failed gated prompt.
- [ ] 7. Cover team-mode live delivery and wake hint paths
What to do: Add tests proving team live delivery and idle wake hints recover from failed or gated prompt dispatch by leaving mailbox fallback paths available and not permanently reserving the recipient session. Fix only the affected team route if the shared gate is not enough.
Must NOT do: Do not change team storage schema, member eligibility, worktree creation, or tmux layout behavior.
What to do: Exercise the sibling OpenCode promptAsync behavior against a local session so the evidence shows `prompt_async` returns before the later failure event. Use a task-owned tmux session or direct command with bounded polling. Capture request, status, emitted error, and cleanup commands.
Must NOT do: Do not modify sibling OpenCode code. Do not kill the tmux server. Do not rely on a simulated unit test for this task.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/sdk/js/src/gen/types.gen.ts:2727` — SDK success response is 204 void.
- Pattern: `.debugging:86` — cleanup ledger must track tmux sessions and worktree cleanup.
- Test: `packages/opencode/test/server/httpapi-promptasync-context.test.ts` in sibling repo — upstream already has promptAsync context coverage and can be used as a reference pattern.
Acceptance criteria (agent-executable only):
- [ ] `evidence/task-8-opencode-promptasync.txt` contains a 204/NoContent observation and a later `session.error` observation for the same session.
- [ ] `.debugging` records the manual QA command, evidence path, and owned tmux session name if tmux is used.
- [ ] Any tmux session created by this task is killed by name after evidence capture; `tmux ls` still works and no `kill-server` command is used.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: upstream promptAsync returns before durable completion
Tool: tmux
Steps: mkdir -p evidence; create a task-owned tmux session named omo-promptasync-upstream-qa that starts or connects to local OpenCode, creates a session, posts /prompt_async with an invalid agent, subscribes to events, writes the 204 response and subsequent session.error to evidence/task-8-opencode-promptasync.txt, signals completion with tmux wait-for; then kill only tmux session omo-promptasync-upstream-qa.
Expected: evidence file contains the same session id, HTTP 204 or NoContent for prompt_async, and later session.error with Agent not found or equivalent prompt failure.
Steps: tmux ls 2>&1 | tee evidence/task-8-tmux-ls.txt
Expected: command succeeds or reports no sessions; no task command used tmux kill-server.
Evidence: evidence/task-8-tmux-ls.txt
```
Commit: NO | Message: `n/a` | Files: [`evidence/task-8-opencode-promptasync.txt`, `.debugging`]
- [ ] 9. Manual QA OMO main-session no-hang path
What to do: Run an actual OMO/OpenCode session in a task-owned tmux session and reproduce the original main-session internal prompt race as closely as possible: trigger an internal fallback/recovery prompt, force a prompt failure or timeout, and verify the next recovery/fallback prompt is dispatched or requeued instead of hanging behind `reserved`. Capture `/tmp/oh-my-opencode.log`, session output, and final status.
Must NOT do: Do not declare success from tests alone. Do not kill global tmux server. Do not leave an OpenCode server or task-owned tmux session running.
- Pattern: `/tmp/oh-my-opencode.log` — project logger target from AGENTS.md.
Acceptance criteria (agent-executable only):
- [ ] `evidence/task-9-omo-main-session.txt` contains the actual OMO run transcript and exits without indefinite wait.
- [ ] `evidence/task-9-omo-log.txt` contains prompt gate dispatch/failure/retry evidence and no final stale `reserved` skip for the target session.
- [ ] The owned tmux session is removed by name and no tmux server kill is used.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: OMO main-session recovery does not hang after prompt failure
Tool: tmux
Steps: mkdir -p evidence; truncate or mark /tmp/oh-my-opencode.log with a QA delimiter; start a task-owned tmux session named omo-main-prompt-race-qa from /Users/yeongyu/local-workspaces/gpt 5.5 xhigh; run the built local CLI or local plugin against sibling OpenCode with a prompt that triggers fallback/recovery; wait via tmux wait-for; capture pane to evidence/task-9-omo-main-session.txt and log slice to evidence/task-9-omo-log.txt; kill only omo-main-prompt-race-qa.
Expected: transcript reaches a terminal success or expected handled failure, and log shows any failed/reserved prompt is followed by cleanup/retry/requeue rather than permanent hang.
Evidence: evidence/task-9-omo-main-session.txt
Scenario: no stale prompt reservation after QA
Tool: bash
Steps: rg -n "prompt-async-gate.*(reserved|failed|dispatched)|model-fallback|runtime-fallback" evidence/task-9-omo-log.txt | tee evidence/task-9-prompt-gate-log.txt
Expected: output shows the target session's failed prompt path and a subsequent dispatch/requeue; it does not end with only a reserved skip.
Evidence: evidence/task-9-prompt-gate-log.txt
```
Commit: NO | Message: `n/a` | Files: [`evidence/task-9-omo-main-session.txt`, `evidence/task-9-omo-log.txt`, `.debugging`]
- [ ] 10. Manual QA background and team routes
What to do: Exercise at least one background-agent parent wake/resume path and one team live-delivery/wake path in real local execution or, if team-mode live execution is blocked by credentials/config, use the closest agent-executed CLI/tool invocation plus captured logs and explain the limitation in `.debugging`. The key pass condition is no permanent prompt reservation after a failed prompt delivery; background wakes requeue and team messages remain available through inbox fallback.
Must NOT do: Do not create nested teams. Do not leave team worktrees, mailbox files, or tmux panes unmanaged. Do not remove user team config.
- Pattern: `src/hooks/team-session-events/team-idle-wake-hint.ts:114` — wake hint prompt dispatch.
- Pattern: `src/features/team-mode/AGENTS.md:1` — team-mode overview and guardrails.
Acceptance criteria (agent-executable only):
- [ ] `evidence/task-10-background-route.txt` contains real background route evidence showing no hang and correct requeue/handled failure.
- [ ] `evidence/task-10-team-route.txt` contains real team route evidence or a documented blocked-run fallback with the exact command and reason.
- [ ] `.debugging` records any created team run, worktree, tmux session, mailbox path, and cleanup command.
- [ ] Any created team/task artifacts are cleaned up or explicitly preserved only if they are required evidence.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: background failed prompt route is handled
Tool: tmux
Steps: run a task-owned local OMO session that launches or resumes a background task with a controlled prompt failure; capture task output and /tmp/oh-my-opencode.log to evidence/task-10-background-route.txt; clean up only task-owned tmux sessions.
Expected: background task reaches completed, error, interrupt, or requeued state; it does not remain indefinitely running due to a reserved prompt gate.
Evidence: evidence/task-10-background-route.txt
Scenario: team live delivery failure falls back without stale reservation
Tool: bash
Steps: execute the smallest team-mode command sequence available in this worktree/config to send a member message or wake hint; if full team-mode is unavailable, run the team-mode focused test plus log why real execution is blocked; capture output to evidence/task-10-team-route.txt.
Expected: real route or documented blocked fallback shows failed prompt delivery releases mailbox reservation or leaves inbox fallback available.
Evidence: evidence/task-10-team-route.txt
```
Commit: NO | Message: `n/a` | Files: [`evidence/task-10-background-route.txt`, `evidence/task-10-team-route.txt`, `.debugging`]
- [ ] 11. Local full verification and atomic commits
What to do: Run focused tests, full Bun tests, typecheck, and build locally. Group commits by logical unit if earlier tasks have not already committed. Preserve `.debugging` updates and keep evidence files uncommitted unless the user explicitly wants evidence committed.
Must NOT do: Do not commit unrelated dirty files. Do not modify `package.json` version. Do not use `--no-verify`.
- [ ] `bun test src/features/background-agent/manager.test.ts src/features/team-mode/tools/messaging.test.ts src/hooks/team-session-events/team-idle-wake-hint.test.ts --bail` passes.
- [ ] `bun test` passes.
- [ ] `bun run typecheck` passes.
- [ ] `bun run build` passes.
- [ ] `git status --short` shows only intended committed changes plus untracked evidence if evidence is intentionally not committed.
- [ ] `git log --oneline origin/dev..HEAD` shows atomic conventional commits and no WIP commits.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: full local verification passes
Tool: bash
Steps: mkdir -p evidence; bun test 2>&1 | tee evidence/task-11-bun-test.txt; bun run typecheck 2>&1 | tee evidence/task-11-typecheck.txt; bun run build 2>&1 | tee evidence/task-11-build.txt
Expected: all three commands exit 0.
Evidence: evidence/task-11-bun-test.txt
Scenario: commit history is clean
Tool: bash
Steps: git status --short | tee evidence/task-11-git-status.txt; git log --oneline origin/dev..HEAD | tee evidence/task-11-git-log.txt
Expected: no unrelated tracked changes remain unstaged/uncommitted; commits are conventional and logically atomic.
- [ ] 12. Create PR, pass CI/reviews, merge, and clean up worktree
What to do: Push branch, create a PR against `dev`, run the verification loop until CI, review-work, and Cubic all pass. Use a PR body file under `/tmp/pull-request-prompt-hang-race-<timestamp>.md` and get user confirmation before `gh pr create` per project instruction. After all gates pass, merge as requested by the PR workflow, then remove `/Users/yeongyu/local-workspaces/gpt 5.5 xhigh` worktree after merge.
Must NOT do: Do not create the PR body inline. Do not merge before CI, review-work, and Cubic all pass. Do not remove the worktree before merge. Do not delete user data or unrelated worktrees.
Parallelization: Can parallel: NO | Wave 4 | Blocks: [] | Blocked by: [8, 9, 10, 11]
References (executor has NO interview context — be exhaustive):
- External: `/Users/yeongyu/.agents/skills/git-master/SKILL.md` — commit history must be atomic before push.
- Pattern: `package.json:25` — build command mirrors CI build.
- Pattern: `package.json:36` — typecheck command mirrors CI typecheck.
- Pattern: `package.json:38` — test command mirrors CI root test.
- Pattern: `.debugging:88` — worktree removal is already tracked as a cleanup artifact.
- Pattern: `.debugging:89` — branch cleanup is already tracked as a cleanup artifact.
Acceptance criteria (agent-executable only):
- [ ] PR exists and targets `dev`; `gh pr view --json number,baseRefName,headRefName,url` saved to `evidence/task-12-pr.json`.
- [ ] `gh pr checks --watch --fail-fast` passes for the PR head.
- [ ] review-work final report has no blocking issues and is saved to `evidence/task-12-review-work.txt`.
- [ ] Cubic comment says no issues found, or equivalent pass status, saved to `evidence/task-12-cubic.txt`.
- [ ] PR is merged.
- [ ] `git worktree list` no longer contains `/Users/yeongyu/local-workspaces/gpt 5.5 xhigh` after merge cleanup.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: PR gates all pass
Tool: bash
Steps: mkdir -p evidence; gh pr view --json number,baseRefName,headRefName,url > evidence/task-12-pr.json; gh pr checks --watch --fail-fast 2>&1 | tee evidence/task-12-ci.txt; run review-work and save its final report to evidence/task-12-review-work.txt; query PR comments/reviews for Cubic and save pass evidence to evidence/task-12-cubic.txt.
Expected: PR targets dev, CI exits 0, review-work has no blocking issues, and Cubic reports no issues found.
Evidence: evidence/task-12-ci.txt
Scenario: merged branch worktree cleanup
Tool: bash
Steps: after merge, run git worktree list | tee evidence/task-12-worktrees-before-cleanup.txt; remove only /Users/yeongyu/local-workspaces/gpt 5.5 xhigh with git worktree remove; run git worktree list | tee evidence/task-12-worktrees-after-cleanup.txt.
Expected: after-cleanup evidence does not contain /Users/yeongyu/local-workspaces/gpt 5.5 xhigh.
Move project-scoped OMO state from `.sisyphus` to `.omo`, while preserving existing legacy state by copying `.sisyphus` into `.omo` on plugin startup when `.sisyphus` is detected.
## Task Graph
1. Add shared legacy workspace migration helper.
- Depends on: none.
- Acceptance: copies nested files from `.sisyphus` into `.omo`, creates missing directories, does not overwrite existing `.omo` files, and returns whether anything migrated.
2. Invoke migration at plugin startup.
- Depends on: task 1.
- Acceptance: startup calls the helper before managers/tools/hooks are created.
3. Switch runtime state constants to `.omo`.
- Depends on: task 1.
- Acceptance: Boulder and run-continuation writes land under `.omo`.
4. Switch guardrails and prompt-facing workspace paths to `.omo`.
- Depends on: task 3.
- Acceptance: Prometheus, Atlas, notepad, write-existing guard, and plan extraction surfaces point at `.omo`.
5. Update docs, ignore rules, and generated schema.
- Depends on: tasks 3-4.
- Acceptance: user-facing storage docs and schema examples no longer advertise `.sisyphus` for active workspace state.
6. Verify and ship.
- Depends on: tasks 1-5.
- Acceptance: focused tests, typecheck, full test suite, build, manual QA, CI, GPT-5.2 review, and Cubic all pass before merge.
- Guardrail: Prometheus may write `.omo/plans/*.md` and is blocked outside `.omo`.
- Adjacent compatibility: rules injector still discovers legacy `.sisyphus/rules` alongside `.omo/rules` if kept for transition.
## Commit Strategy
1.`feat(workspace): migrate legacy sisyphus state to omo`
- Migration helper, startup invocation, runtime constants, and direct tests.
2.`docs(workspace): document omo workspace paths`
- Docs, `.gitignore`, schema output, and prompt/docs wording updates if separated cleanly.
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.