Skip to content

fix: prevent parent task from hanging when subtask delegation returns after per-mode API profile switch - #1

Merged
DScoNOIZ merged 8 commits into
mainfrom
fix/issue-457-delegation-return
Jun 3, 2026
Merged

DScoNOIZ merged 8 commits into
mainfrom
fix/issue-457-delegation-return

Conversation

@DScoNOIZ

@DScoNOIZ DScoNOIZ commented Jun 3, 2026

Copy link
Copy Markdown
Owner

Description

Fixes Zoo-Code-Org#457 — a critical bug where subtask delegation never returns to the parent when two modes use different API configuration profiles (per-mode modeApiConfigs).

Problem

When an orchestrator/parent task delegates to a subtask whose mode uses a different modeApiConfigs profile than the parent, the flow silently stalls after the subtask completes. The parent is never resumed, requiring manual intervention.

Root Cause

The per-mode profile switch runs handleModeSwitch → activateProviderProfile, which leaves the parent task record as status: "active" while awaitingChildId still points at the child. Two guards then reject because they require status === "delegated":

  1. reopenParentFromDelegation() — an overly strict cancelledDelegationChildIds.has(childTaskId) guard blocks reopen permanently when cancelTask() fails to detach the parent (race condition in runDelegationTransition). The child is added to the blacklist, and the second attempt_completion call can never recover.

  2. AttemptCompletionTool delegation pre-check — only accepts status === "delegated" for the parent, missing the "active" state that occurs after a per-mode profile switch.

Changes

src/core/webview/ClineProvider.ts

  • Removed cancelledDelegationChildIds check from reopenParentFromDelegation() guard. The blacklist is redundant: cancelTask() sets parent status to "active" before adding to the blacklist. If the parent is still "delegated" and awaiting this child, it is safe to reopen. The status + awaitingChildId check already handles orphaned children.

src/core/tools/AttemptCompletionTool.ts

  • Added parent verification before delegation: verify parentHistory.status === "delegated" and parentHistory.awaitingChildId === task.taskId before calling delegateToParent(). If parent detached (cancelled/resumed), skip delegation gracefully.
  • Exempt subtask delegation from didToolFailInCurrentTurn guard (&& !task.parentTaskId). Subtasks finishing their assigned work should not be blocked by a parent's error flag.
  • Added console.warn diagnostics for failed reopen attempts.
  • Fixed error logging — removed stale historyLookupTaskId variable that could point at the wrong task.

Testing

  • Same-profile delegation still works (existing behavior preserved)
  • Cross-profile delegation now returns correctly
  • Parent verification prevents corrupting unrelated tasks
  • Subtask with cancelled parent falls through to normal completion flow

JamesRobert20 and others added 8 commits June 2, 2026 13:09
…de-Org#344)

* feat(zoo-gateway): add provider types, handler, and model fetcher

Co-authored-by: Cursor <[email protected]>

* fix(zoo-gateway): respect readonly client, real version header, safer fetch

- Stop reassigning RouterProvider.client; thread Zoo enrichment headers
  through openAiHeaders so a single OpenAI client is used.
- Replace npm_package_version (never populated at extension runtime)
  with Package.version from the shared package shim.
- Default the model list to [] on a structurally broken response so we
  log and recover instead of crashing on response.data.data being
  undefined.
- Bypass inFlightRefresh de-duplication for zoo-gateway: a refresh
  triggered after sign-out/sign-in must not return the previous user's
  in-flight response.
- Add fetcher unit tests covering auth header, timeout, error
  redaction, and bad-response handling.

Co-authored-by: Cursor <[email protected]>

* fix(types): include zoo-gateway key in requestRouterModels record so RouterName stays exhaustive

Co-authored-by: Cursor <[email protected]>

* test(router-models): expect zoo-gateway in requestRouterModels responses

Co-authored-by: Cursor <[email protected]>

* test(zoo-gateway): add ZooGatewayHandler unit tests for codecov patch

Cover constructor auth guard, base URL resolution, streaming, task/mode headers, temperature, cache breakpoints, tool calls, and completePrompt.

Co-authored-by: Cursor <[email protected]>

* test(zoo-gateway): mock cached-token + clear-token auth helpers

The downstream stack (settings-ui) calls getCachedZooCodeToken and
clearZooCodeToken from the auth handler. CI on stacked PRs merges base
into head so this spec runs against the cached-token-aware handler;
expand the auth module mock so the auth guard test exercises the real
throw path instead of vitest's missing-mock-export error.

Co-authored-by: Cursor <[email protected]>

* test(zoo-gateway): align expected OpenAI headers with Zoo Code branding

Co-authored-by: Cursor <[email protected]>

* fix(zoo-gateway): defer auth check, fail-closed models, reuse Vercel schemas

Co-authored-by: Cursor <[email protected]>

* fix(zoo-gateway): accept models list without created/description fields

Co-authored-by: Cursor <[email protected]>

* refactor(zoo-gateway): centralize auth-scoped cache-skip via AUTH_SCOPED_PROVIDERS

Co-authored-by: Cursor <[email protected]>

* chore(types): drop retired 'roo' provider from getApiProtocol allowlist

Co-authored-by: Cursor <[email protected]>

---------

Co-authored-by: James Mtendamema <[email protected]>
Co-authored-by: Cursor <[email protected]>
…rg#242) (Zoo-Code-Org#247)

When "use custom temperature" is off, the OpenAI-Compatible provider still sent temperature: 0 (the fallback), so the model's server-side default never applied. Per the discussion on Zoo-Code-Org#242 (option A), omit the temperature field in that case. Preserves the supportsTemperature gate (Zoo-Code-Org#233), model-required defaults (deepseek-reasoner), and a deliberately-set 0.

Co-authored-by: Armando Vaquera <[email protected]>
…Code-Org#161) (Zoo-Code-Org#274)

* feat(zai): expose configurable max output tokens for GLM models (Zoo-Code-Org#161)

Add a standalone "max output tokens" slider for models that advertise
`supportsMaxTokens` (e.g. Z.ai GLM) but do not surface a reasoning budget, and
send the chosen value to the provider as `max_tokens`.

- ThinkingBudget: extract the max-tokens slider into a small render helper shared
  by the standalone control and the reasoning-budget branch, and surface it for
  binary-reasoning models that also support a configurable max.
- getModelMaxOutputTokens: honor the user's `modelMaxTokens` override for
  `supportsMaxTokens` models (capped at the model ceiling) instead of the 20%
  context-window clamp, so the runtime budget matches what is sent to the provider.
- ProviderSettingsManager.export(): preserve `modelMaxTokens` for configurable-max
  models while dropping it for models that support neither reasoning budgets nor a
  configurable max.

* Update src/shared/api.ts

* Update webview-ui/src/components/settings/ThinkingBudget.tsx

---------

Co-authored-by: Armando Vaquera <[email protected]>
Co-authored-by: edelauna <[email protected]>
* feat(zoo-gateway): add provider types, handler, and model fetcher

Co-authored-by: Cursor <[email protected]>

* fix(zoo-gateway): respect readonly client, real version header, safer fetch

- Stop reassigning RouterProvider.client; thread Zoo enrichment headers
  through openAiHeaders so a single OpenAI client is used.
- Replace npm_package_version (never populated at extension runtime)
  with Package.version from the shared package shim.
- Default the model list to [] on a structurally broken response so we
  log and recover instead of crashing on response.data.data being
  undefined.
- Bypass inFlightRefresh de-duplication for zoo-gateway: a refresh
  triggered after sign-out/sign-in must not return the previous user's
  in-flight response.
- Add fetcher unit tests covering auth header, timeout, error
  redaction, and bad-response handling.

Co-authored-by: Cursor <[email protected]>

* test(zoo-gateway): mock cached-token + clear-token auth helpers

The downstream stack (settings-ui) calls getCachedZooCodeToken and
clearZooCodeToken from the auth handler. CI on stacked PRs merges base
into head so this spec runs against the cached-token-aware handler;
expand the auth module mock so the auth guard test exercises the real
throw path instead of vitest's missing-mock-export error.

Co-authored-by: Cursor <[email protected]>

* feat(zoo-gateway): add settings UI, validation, and i18n

Co-authored-by: Cursor <[email protected]>

* fix(zoo-gateway): dynamic dashboard URL and cached-token fallback

- Resolve ModelPicker serviceUrl from zooCodeBaseUrl so staging/dev
  environments link to the matching dashboard.
- Fall back to getCachedZooCodeToken() in the handler and model fetcher
  when the profile has not been seeded yet (auth before webview open).

Co-authored-by: Cursor <[email protected]>

* fix(deps): drop stale webview-ui package.json drift that broke frozen-lockfile installs

Co-authored-by: Cursor <[email protected]>

* fix(i18n): restore googleCloudCredentialsPathWarning and automaticFetch 'free' search hint dropped on rebase

Co-authored-by: Cursor <[email protected]>

* fix(zoo-gateway): pick default model from fetched list, prefer Sonnet 4.5

Resolves Sonnet 4.5 from the gateway model catalog instead of a static Vercel slug so test (Bedrock) and live accounts both get a valid default. Reassigns stale profile model IDs when they are not in the catalog.

Co-authored-by: Cursor <[email protected]>

* test(zoo-gateway): cover dynamic default model picker for codecov patch

Exports pickZooGatewayDefaultModelId so the helper is unit-testable and adds
component tests for the auto-default useEffect (no-op while catalog loads,
auto-pick on empty profile, repair stale id, no-op when valid).

Co-authored-by: Cursor <[email protected]>

* fix(i18n): restore UTF-8 for Google Cloud warning and model picker strings

Co-authored-by: Cursor <[email protected]>

* fix(zoo-gateway): settings UI sign-in button, validation tests, defer auth

Co-authored-by: Cursor <[email protected]>

* refactor(zoo-gateway): localize auth-state UX to provider component

Move the zoo-gateway sign-in error out of the shared form-validation effect
in ApiOptions and into ZooGateway.tsx, where it renders inline via
ApiErrorMessage. ApiOptions can then drop zooCodeIsAuthenticated from its
useEffect dependency list, and validateApiConfigurationExcludingModelErrors
short-circuits zoo-gateway entirely.

Also rename the inline isSonnet45ModelId helper to isClaudeSonnetModelId
and let pickZooGatewayDefaultModelId express the version-priority order
directly, so the helper has no version baked into its name.

Co-authored-by: Cursor <[email protected]>

* fix(zoo-gateway): enforce org provider check, simplify sign-in copy

The settings-form short-circuit for zoo-gateway also bypassed the
organization PROVIDER_NOT_ALLOWED check, so a workspace that disallows
zoo-gateway could not surface that error. Scope the short-circuit to
the keys/sign-in check only and let the org allowlist check run for
every provider.

Drop the quoted CTA from zooGatewaySignIn in all 18 locales: the
sign-in button is rendered immediately below the inline error, so a
duplicate label was just a drift hazard.

Co-authored-by: Cursor <[email protected]>

* refactor(webview): move provider model config + docs slugs out of ApiOptions

Lift the inline PROVIDER_MODEL_CONFIG map and the docs-slug lookup out
of ApiOptions.tsx into webview-ui/src/components/settings/utils/providerModelConfig.ts
behind getProviderModelConfig and getProviderDocsSlug helpers. ApiOptions
now reads provider-specific model fields, defaults, and docs slugs through
those helpers, leaving the component focused on rendering.

Co-authored-by: Cursor <[email protected]>

* fix(i18n): align French zooGatewaySignIn to formal vous register

validation.zooGatewaySignIn used "Connecte-toi" while
providers.zooGateway.signInDescription uses "Connectez-vous".
Use vous consistently across both strings.

Co-authored-by: Cursor <[email protected]>

---------

Co-authored-by: James Mtendamema <[email protected]>
Co-authored-by: Cursor <[email protected]>
…ode-Org#277)

* feat(terminal): add inline terminal profile selection (Zoo-Code-Org#119)

Add a 'terminalProfile' setting that lets users choose which VS Code
terminal profile the inline terminal uses. On Windows the default
cmd/PowerShell shell may use a non-UTF-8 code page (e.g. GBK) and garble
output; selecting a UTF-8 profile such as Git Bash resolves this.

The setting reuses VS Code's terminal profile concept: when set, the
profile name is resolved against terminal.integrated.profiles.<platform>
to derive shellPath/shellArgs for createTerminal. When empty/unset the
default terminal behavior is preserved unchanged.

Adds backend unit tests for profile resolution and a webview test for
the settings dropdown wiring.

* fix(test): relax spy types for overloaded VS Code APIs (Zoo-Code-Org#119)

* fix(test): use ES import instead of require() in terminal profile spec (Zoo-Code-Org#119)

* refactor(terminal): address review feedback on inline terminal profile (Zoo-Code-Org#119)

- Route profile names through a dedicated allowlisted `requestTerminalProfiles`
  message instead of the generic `getVSCodeSetting` (which reads any key the
  webview supplies); the extension reads the profiles and returns only names.
- Preserve the profile's `env` (sanitized to string/null; null unsets a var),
  merged onto the base env in createTerminal.
- Clarify the setting copy (en + es) vs the 'Use Inline Terminal' description.
- Add tests: updateSettings->setTerminalProfile bridge, resolveWebviewView
  startup hydration, and profile env preservation/sanitization.

* fix(terminal): resolve profile path[] to first existing candidate (Zoo-Code-Org#119)

VS Code selects the first terminal-profile path candidate that exists on
disk; mirror that instead of always taking index 0, falling back to the
first non-empty candidate when none exist. Addresses CodeRabbit review on Zoo-Code-Org#277.

* fix(terminal): hide inline profile picker when inline execution is off

The terminal-profile dropdown only affects inline execution, which is active
when shell integration is disabled. It previously stayed visible and editable
even when inline mode was off, where it has no effect. Guard it with
terminalShellIntegrationDisabled (defaulting to shown, matching the checkbox),
mirroring the inline-only settings below. Addresses PR Zoo-Code-Org#277 review (edelauna).

* fix(terminal): address review feedback

* refactor(terminal): scope profile picker to VS Code integrated terminal, filter source-only profiles

* fix(terminal): scope profile config to user settings, filter shellArgs, fix PATH join

* refactor(terminal): address review feedback on profile picker

* refactor(terminal): address review feedback on profile picker

* refactor(terminal): promote no_shell_integration payload to typed object

* feat(terminal): replace pWaitFor with event-based shell integration wait; add cmd.exe fast-path

* fix(terminal): retry via execa silently when shell integration fails before submission

* fix(terminal): close idle terminals when profile changes

* fix(terminal): skip ZDOTDIR injection with profile override; clear cached profile on picker open

* test(e2e): smoke-test VS Code terminal profile override lifecycle

* fix(terminal): avoid replaying commands after shell integration failure

* fix(terminal): harden profile settings and cleanup

* Revert "feat(terminal): replace pWaitFor with event-based shell integration wait; add cmd.exe fast-path"

This reverts commit 40e17b1.

* fix(TerminalProcess): warning even when shell opens

---------

Co-authored-by: Armando Vaquera <[email protected]>
Co-authored-by: Elliott de Launay <[email protected]>
…de-Org#347)

* feat(zoo-gateway): auth callback, profile token sync, and sign-out

Co-authored-by: Cursor <[email protected]>

* test(zoo-gateway): cover auth callback profile sync and sign-out

Add ClineProvider tests for handleZooCodeCallback, ensureZooGatewayProfileSeeded, and webviewMessageHandler zooCodeSignOut to satisfy codecov patch on PR Zoo-Code-Org#347.

Co-authored-by: Cursor <[email protected]>

* fix(zoo-gateway): surface 401/402/403 errors with actionable toasts

Clear the cached token on 401 and offer sign-in. On insufficient credits
or budget limits, open the credits page. On account frozen/banned, open
support. Errors still propagate to the task layer after the toast.

Co-authored-by: Cursor <[email protected]>

* i18n(zoo-gateway): backfill zooAuth translations for 17 non-English locales

Adds session_expired, out_of_credits, account_unavailable, budget_exceeded
under zooAuth.errors and a new zooAuth.buttons block (sign_in, add_credits,
contact_support) introduced by the gateway 401/402/403 UX so check-translations
passes.

Co-authored-by: Cursor <[email protected]>

* test(zoo-gateway): cover surfaceGatewayApiError UX branches for codecov patch

Adds vscode + i18n mocks and asserts the 401/402/403/429 paths in
surfaceGatewayApiError: token clear + sign-in URL on 401, add-credits
URL on 402 and budget-coded 429, support URL on 403, no-op on 429
without a budget code or on errors without a status. Also verifies the
helper still runs before completePrompt rewraps the upstream error.

Co-authored-by: Cursor <[email protected]>

* fix(zoo-gateway): address PR review feedback on auth, seeding, and errors

Co-authored-by: Cursor <[email protected]>

* test(zoo-gateway): cover stale baseUrl seeding path

Co-authored-by: Cursor <[email protected]>

* fix(zoo-gateway): sign-out clears stale profile tokens, simplify model fetch

Co-authored-by: Cursor <[email protected]>

* test(zoo-gateway): drop stale profile-scan test for requestRouterModels

Co-authored-by: Cursor <[email protected]>

* fix(zoo-gateway): treat verify 5xx as transient, do not clear token

The website's /api/extension/auth/verify route now returns 503 when the
backend can't reach the database, instead of crashing. The extension
previously treated any non-OK response from this endpoint as a
definitively invalid token, which meant a transient backend hiccup
would silently clear the user's session and force a fresh sign-in.

verifyZooCodeToken now returns "unreachable" for 5xx responses (same
classification as a network error), so initZooCodeAuth keeps the cached
token in place and reports subscription status as "unknown" until the
backend recovers. handleAuthCallback shows the could-not-verify message
on 5xx so users see this is a temporary issue rather than a bad token.

Co-authored-by: Cursor <[email protected]>

* refactor(zoo-gateway): split error classification from UX, extract callback fan-out

Address review feedback on PR Zoo-Code-Org#347:

- zoo-gateway.ts: split surfaceGatewayApiError into a pure
  classifyGatewayApiError (error -> action) plus a thin UX layer that
  switches on the action. The classifier is exported and covered by
  focused unit tests, so the status/code -> action mapping no longer
  needs the VS Code notification mocks to verify.
- handleUri.ts: extract the per-instance token propagation loop into a
  propagateZooGatewayCallback helper, keeping the /auth-callback case
  focused on routing. Behaviour (sequential read-modify-write, per
  instance error isolation) is unchanged.

Co-authored-by: Cursor <[email protected]>

* fix: isolate per-profile token cleanup on sign-out and assert state refresh

Wrap per-profile work in the zoo-gateway sign-out cleanup loop in its own
try/catch so one corrupted profile or failed write no longer aborts cleanup
of the remaining profiles. Also assert postStateToWebview runs on the
handleZooCodeCallback persistence-failure path.

Co-authored-by: Cursor <[email protected]>

---------

Co-authored-by: James Mtendamema <[email protected]>
Co-authored-by: Cursor <[email protected]>
…gs (Zoo-Code-Org#346)

* feat(privacy): remove extension LLM telemetry and observability section

Delete the extension-side LLM telemetry path (zoo-telemetry.ts and its
Task.ts hook) so BYOK requests no longer send usage metadata to zoocode.dev.
Drop the "Zoo Code Observability" section from PRIVACY.md entirely: server-side
gateway request logging is not extension behavior and is documented at
zoocode.dev like any other provider's, so the extension privacy policy should
not single it out.

Co-authored-by: Cursor <[email protected]>

* refactor(zoo-code-auth): drop subscription-status helpers with telemetry

The checkSubscriptionStatus / getCachedSubscriptionStatus helpers existed only
to gate the now-removed LLM telemetry path. Remove them and their tests.

Co-authored-by: Cursor <[email protected]>

---------

Co-authored-by: Cursor <[email protected]>
… after per-mode API profile switch

**Problem:**
When an orchestrator/parent task delegates to a subtask whose mode is bound to a different API-configuration profile (modeApiConfigs) than the parent, the subtask completes and calls attempt_completion, but control never returns to the parent. The run silently waits for manual click instead of auto-resuming.

**Root cause:**
handleModeSwitch → activateProviderProfile leaves the parent task record as status="active" while awaitingChildId still points at the child. Two guards then reject because they require parent status to be exactly "delegated":

1. reopenParentFromDelegation() had an overly strict guard checking cancelledDelegationChildIds.has(childTaskId). When cancelTask() fails to detach the parent due to a runDelegationTransition race, the child is added to the blacklist. On the second attempt_completion call, the guard blocks reopen permanently.

2. Guard in attempt_completion delegation pre-check only accepts status === "delegated".

**Fix:**
- Remove cancelledDelegationChildIds from reopenParentFromDelegation guard. The cancelledDelegationChildIds blacklist is redundant — cancelTask() already sets parent status to "active" before adding to blacklist. If parent is still "delegated" and awaiting this child, it is safe to reopen.
- Add parentHistory verification before delegation: check parentHistory.status === "delegated" && parentHistory.awaitingChildId === task.taskId before calling delegateToParent.
- Exempt subtask delegation from didToolFailInCurrentTurn guard (&& !task.parentTaskId) — the subtask is legitimately finishing assigned work.

Fixes Zoo-Code-Org#457
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Subtask never returns to parent after a per-mode model/profile switch (attempt_completion silently waits for human)

3 participants