Fire-and-forget session.abort() calls during subagent completion left
dangling promises that raced with parent session teardown. In Bun on
WSL2/Linux, this triggered a StringImplShape assertion (SIGABRT) as
WebKit GC collected string data still referenced by the inflight request.
Fix: await session.abort() in all four completion/error paths:
- startTask promptAsync error handler (launch path)
- resume promptAsync error handler (resume path)
- cancelTask (explicit cancel path)
- tryCompleteTask (normal completion path)
Also marks the two .catch() error callbacks as async so the await is
valid.
Test: update session.deleted cascade test to flush two microtask rounds
since cancelTask now awaits abort before cleanupPendingByParent.
TTL (pruneStaleTasksAndNotifications) now resets on last activity:
- Uses task.progress.lastUpdate as TTL anchor for running tasks
(was always using startedAt, causing 30-min hard deadline)
- Added taskTtlMs config option for user-adjustable TTL
- Error message shows actual TTL duration, not hardcoded '30 minutes'
- 3 new tests for the new behavior
- completedTaskSummaries now includes status and error info
- notifyParentSession: noReply=false for failed tasks so parent reacts
- Batch notification distinguishes successful vs failed/cancelled tasks
- notification-template updated to show task errors
- task-poller: session-gone tests (85 new lines)
- CI: add Bun shim to PATH for legacy plugin migration tests
When a subagent session disappears from the status registry (process
crashed), the main agent was waiting the full stale timeout before
acting. Fix:
- Add sessionGoneTimeoutMs config option (default 60s, vs 30min normal)
- task-poller: use shorter timeout when session is gone from status
- manager: verify session existence when gone, fail crashed tasks
immediately with descriptive error
- Add legacy-plugin-toast hook for #2823 migration warnings
- Update schema with new config option
Two issues fixed:
1. process-cleanup.ts used fire-and-forget void Promise for shutdown
handlers — now properly collects and awaits all cleanup promises
via Promise.allSettled, with dedup guard to prevent double cleanup
2. TmuxSessionManager was never registered for process cleanup —
now registered in create-managers.ts via registerManagerForCleanup
Also fixed setTimeout().unref() which could let the process exit
before cleanup completes.
When .sisyphus/ is gitignored, task state written during worktree execution
is lost when the worktree is removed. Fix:
- add worktree-sync.ts: syncSisyphusStateFromWorktree() copies .sisyphus/
contents from worktree to main repo directory
- update start-work.ts template: documents the sync step as CRITICAL when
worktree_path is set in boulder.json
- update work-with-pr/SKILL.md: adds explicit sync step before worktree removal
- export from boulder-state index
- test: 5 scenarios covering no-.sisyphus, nested dirs, overwrite stale state
- Add legacy plugin startup warning when oh-my-opencode config detected
- Update CLI installer and TUI installer for new package name
- Split monolithic config-manager.test.ts into focused test modules
- Add plugin config detection tests for legacy name fallback
- Update processed-command-store to use plugin-identity constants
- Add claude-code-plugin-loader discovery test for both config names
- Update chat-params and ultrawork-db tests for plugin identity
Part of #2823
- model-fallback hook: mock selectFallbackProvider and add _resetForTesting()
to test-setup.ts to clear module-level state between files
- fallback-retry-handler: add afterAll(mock.restore) and use mockReturnValueOnce
to prevent connected-providers mock leaking to subsequent test files
- opencode-config-dir: use win32.join for Windows APPDATA path construction
so tests pass on macOS (path.join uses POSIX semantics regardless of
process.platform override)
- system-loaded-version: use resolveSymlink from file-utils instead of
realpathSync to handle macOS /var -> /private/var symlink consistently
All 4456 tests pass (0 failures) on full bun test suite.
Add support for object-style entries in fallback_models arrays, enabling
per-model configuration of variant, reasoningEffort, temperature, top_p,
maxTokens, and thinking settings.
- Zod schema for FallbackModelObject with full validation
- normalizeFallbackModels() and flattenToFallbackModelStrings() utilities
- Provider-agnostic model resolution pipeline with fallback chain
- Session prompt params state management
- Fallback chain construction with prefix-match lookup
- Integration across delegate-task, background-agent, and plugin layers
Scoped packages like @scope/pkg were truncated to just 'pkg' because
basename() strips the scope prefix. Fix:
- Detect scoped packages (starting with @) and find version separator
after the scope slash, not at the leading @
- Return full scoped name (@scope/pkg) instead of calling basename
- Add regression test for scoped package name preservation
Use port 0 fallback when findAvailablePort fails, read the actual bound
port from server.port. Tests refactored to use mock server when real
socket binding is unavailable in CI.
🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously, when an explicit model was configured, the agent name was
omitted to prevent opencode's built-in agent fallback chain from
overriding the user-specified model. This removes that conditional logic
and always passes the agent name alongside the model. Tests are updated
to reflect this behavior change.
🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Keep BackgroundManager launch and resume from sending both agent and model so OpenCode does not override configured subagent models. Add launch and resume regressions for the live production path.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- #2741: Pass inheritedModel as fallback in subagent-resolver when user hasn't
configured an override, ensuring custom provider models take priority
- #2648: Fix getPlanProgress to treat plans with 0 checkboxes as incomplete
instead of complete (total > 0 && completed === total)
- #2779: Relax Hephaestus single-task guard to accept multi-step sub-tasks
from Atlas delegation, only rejecting genuinely independent tasks
Fixes#2741, fixes#2648, fixes#2779
Tests 'should use default timeout when config not provided' (manager.test.ts)
and 'should use DEFAULT_MESSAGE_STALENESS_TIMEOUT_MS when not configured'
(task-poller.test.ts) failed in CI because Date.now() drifted between
test setup (when creating timestamps like Date.now() - 46*60*1000) and
actual execution inside checkAndInterruptStaleTasks().
On slower CI machines, this drift pushed borderline values across
the threshold, causing tasks that should be stale to remain 'running'.
Fix: Mock Date.now with spyOn to return a fixed time, ensuring
consistent timeout calculations regardless of execution speed.
Tests prove rootDescendantCounts is never decremented on task completion,
cancellation, or error — making maxDescendants a lifetime quota instead of
a concurrent-active cap. All 4 tests fail (RED phase) before the fix.
Refs: code-yeongyu/oh-my-openagent#2700
rootDescendantCounts was incremented on every spawn but never decremented
when tasks reached terminal states (completed, cancelled, error, interrupt,
stale-pruned). This made maxDescendants=50 a session-lifetime quota instead
of its intended semantics as a concurrent-active agent cap.
Fix: add unregisterRootDescendant() in five terminal-state handlers:
- tryCompleteTask(): task completes successfully
- cancelTask(): running task cancelled (wasRunning guard prevents
double-decrement for pending tasks already handled by
rollbackPreStartDescendantReservation)
- session.error handler: task errors
- promptAsync catch (startTask): task interrupted on launch
- promptAsync catch (resume): task interrupted on resume
- onTaskPruned callback: stale task pruned (wasPending guard)
Fixes: code-yeongyu/oh-my-openagent#2700
When the resource URL has a sub-path (e.g. https://mcp.sentry.dev/mcp),
the RFC 8414 path-suffixed well-known URL may not exist. Fall back to
the root well-known URL before giving up.
This matches OpenCode core's behavior and fixes authentication for
servers like Sentry that serve OAuth metadata only at the root path.