Require the latest assistant tool-wait turn itself to be stale before a shouldReply parent wake can bypass tool-call deferral. This prevents an all-complete background wake from forking a second parent prompt loop when OpenCode has repaired the tail to a synthetic user message.
Tests:
- bun test src/features/background-agent/parent-wake-user-message-race.test.ts src/features/background-agent/task-completion-cleanup.test.ts src/hooks/shared/prompt-async-gate.test.ts src/shared/prompt-async-route-audit.test.ts --bail
- bun run typecheck
- bun test
Keep the built-in lsp MCP registered even when the submodule CLI artifact is missing.
The fallback command initializes the lsp-tools-mcp submodule, prefers the source CLI without dirtying the checkout with dist output, and keeps npm build as a last resort when Bun cannot run the source entrypoint.
Plan: plans/fix-lsp-mcp-missing-cli.md
The bundled createTodoDescriptionOverrideHook returns { 'tool.definition': fn }, but plugin-interface.ts never exposes 'tool.definition' as an OpenCode hook handler. Result: the hook is constructed by createToolGuardHooks (line 132-134 of src/plugin/hooks/create-tool-guard-hooks.ts) but the function is never invoked, so todowrite keeps using OpenCode's core 7 KB description instead of the 1.4 KB TODOWRITE_DESCRIPTION. User-defined plugins under ~/.config/opencode/plugin/*.js use the same hook contract and work fine, confirming the contract itself is functional in opencode 1.14.28+.
Fix: add src/plugin/tool-definition.ts (createToolDefinitionHandler) that forwards the OpenCode 'tool.definition' input/output pair into hooks.todoDescriptionOverride. Wire it into plugin-interface.ts alongside tool.execute.before/after.
Regression coverage: src/plugin/tool-definition.test.ts covers (a) todowrite override applied, (b) other tools left untouched, (c) null hook is a no-op.
After the 4.2.0 unified-dispatch refactor (a42f894f / df198d8b / fee515c5 / 989ab717 / dd3fecaf / 1bbe065c / 12bd6580), at least one caller in the new prompt-async-gate path forwards a FallbackModelObject (or some other non-string shape) into parsers that statically claim 'model: string'. The downstream .trim() call then throws 'model.trim is not a function', which rejects the session.processor promise and surfaces as 'Aborted process' + UI 'interrupted'. The issue (#4145) reports this aborts 90% of subagent dispatches across every provider on 4.2.0 + opencode 1.15.4.
This patch adds a 'typeof x !== "string"' runtime guard at the four parser entrypoints called from the dispatch path:
- src/shared/fallback-chain-from-models.ts :: parseVariantFromModel, parseFallbackModelEntry
- src/tools/delegate-task/model-string-parser.ts :: parseVariantFromModelID, parseModelString
- src/shared/model-string-parser.ts (duplicate file with same API) :: parseVariantFromModelID, parseModelString
- src/features/claude-code-agent-loader/claude-model-mapper.ts :: mapClaudeModelString
Each parser now returns undefined / { modelID: "" } for non-string input instead of throwing. This unblocks subagent dispatch and leaves the underlying caller bug for a follow-up.
Regression coverage: three new tests in src/shared/fallback-chain-from-models.test.ts pin the non-string behavior (object, null/undefined, number). Existing 38 tests still pass. Total: 41/41 green, typecheck clean.
When OpenCode reports the original assistant error after a fallback retry has already been accepted, keep waiting for the pending fallback model instead of clearing the awaiting flag.
This prevents a duplicate stale session.error from advancing the fallback chain and dispatching a second assistant retry prompt.
The publish-main job runs npm publish which triggers prepublishOnly →
build:lsp-tools-mcp. That script needs packages/lsp-tools-mcp/ populated,
but actions/checkout@v4 doesn't init submodules by default.
Without this, publish-main failed with 'npm error code 1' on an empty
packages/lsp-tools-mcp/ directory, blocking v4.2.0 release.
Aligns with ci.yml which already uses submodules: recursive everywhere.