Skip to content

fix(client): replace a service that failed on an earlier start - #62520

Closed
frankxai wants to merge 1 commit into
anomalyco:devfrom
frankxai:failed-service-recovery
Closed

frankxai wants to merge 1 commit into
anomalyco:devfrom
frankxai:failed-service-recovery

Conversation

@frankxai

@frankxai frankxai commented Nov 7, 2026

Copy link
Copy Markdown

Issue for this PR

Closes #62326

Type of change

  • Bug fix

What does this PR do?

ensure() treated a registered service that answers /api/info with 500 as fatal. A boot that fails after registering therefore leaves a process that blocks every later start with Background service failed to start, until someone runs opencode service stop — the state in #62326 and in the triager's reproduction.

The two situations are now separate:

  • A failure that happens while this call is starting the service is still reported once, and is not respawned within that call.
  • A failure that was already registered before this call started a contender is terminated and replaced, so the next start recovers once the boot cause is gone.

The registration and handoff contracts are unchanged. The Effect and Promise clients are updated together.

How did you verify your code works?

  • Fixture tests cover both halves. A pre-registered failed owner is replaced: the old process exits, a new one serves, and onStart reports missing. A service that fails during this call still rejects with Background service failed to start.
  • bun test test/service.test.ts test/promise-service.test.ts in packages/client: 43 pass, 4 platform skips, 0 fail.
  • bun run typecheck:tsc in packages/client and packages/cli, plus oxlint on the four changed files.
  • The POSIX-only lifecycle case "a server that fails to boot reports the failure without respawning" keeps its meaning: the failing call recruits and reports once, so it does not respawn inside that call.

Visual

<svg xmlns="http://www.w3.org/2000/svg" width="240" height="72" viewBox="0 0 240 72" role="img" aria-label="A background service that failed on an earlier start is replaced on the next start">
  <g fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
    <rect x="12" y="16" width="58" height="40" rx="6" opacity=".45"/>
    <path d="M30 28 L52 44 M52 28 L30 44" opacity=".55"/>
    <path d="M74 36 H100" stroke-dasharray="3 4" opacity=".5"/>
    <path d="M100 36 l-6 -4 M100 36 l-6 4"/>
    <rect x="112" y="16" width="58" height="40" rx="6" opacity=".45"/>
    <path d="M126 36 l8 8 l16 -16"/>
  </g>
  <circle cx="86" cy="36" r="3" fill="currentColor">
    <animate attributeName="cx" values="78;100" dur="2.6s" repeatCount="indefinite"/>
    <animate attributeName="opacity" values="0.2;1;0.2" dur="2.6s" repeatCount="indefinite"/>
  </circle>
  <text x="12" y="66" font-family="ui-monospace,SFMono-Regular,monospace" font-size="7" fill="currentColor" fill-opacity=".55">failed start, replaced on the next start</text>
</svg>

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

ensure() treated any registered service answering 500 as fatal, so a boot failure left by an earlier run blocked every later start until someone ran 'opencode service stop'. A failure this call started is still reported once; a failure already registered before this call starts a contender is now terminated and replaced. Applies to the Effect and Promise clients.
@opencodejr

opencodejr Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

Thanks for picking this up, and for the clear write-up and tests.

I think this duplicates #55311, which fixes the same stuck state in the same fail branch of both clients' ensure(). A registered service that failed to boot keeps blocking later starts with Background service failed to start (#62326, and earlier #55107). That PR stops the failed service before reporting the error, so the next start recovers without a manual opencode service stop.

Your version replaces the failed service on the next start instead. If you think that behaves better (for example, not showing the error once after an earlier run left a failed service behind), it would help to say so on #55311, or to test it there against the reproduction in #62326.

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.

Windows: "Background service failed to start" after update until opencode service stop is run

1 participant