different-ai/openwork · error

OpenCode client unavailable.

Error message

OpenCode client unavailable.

What it means

refreshSkills loads the workspace skill list by calling the underlying OpenCode HTTP client. The public client object is probed for the private _client handle that exposes raw .get(); if that internal handle is missing, the raw /skill request cannot be made and this error is thrown.

Source

Thrown at apps/app/src/react-app/domains/settings/state/extensions-store.ts:1528

    if (!client) {
      mutateState((current) => ({
        ...current,
        skills: [],
        skillsStatus: "OpenWork server unavailable. Connect to load skills.",
      }));
      return;
    }

    if (root !== skillsRoot) skillsLoaded = false;
    if (!optionsOverride?.force && skillsLoaded) return;
    if (refreshSkillsInFlight) return;

    refreshSkillsInFlight = true;
    refreshSkillsAborted = false;
    try {
      setStateField("skillsStatus", null);
      const rawClient = client as unknown as { _client?: { get: (input: { url: string }) => Promise<unknown> } };
      if (!rawClient._client) throw new Error("OpenCode client unavailable.");
      const result = await rawClient._client.get({ url: "/skill" }) as {
        data?: Array<{ name: string; description: string; location: string }>;
        error?: unknown;
      };
      if (result?.data === undefined) {
        const err = result?.error;
        const message = err instanceof Error ? err.message : typeof err === "string" ? err : t("skills.failed_to_load");
        throw new Error(message);
      }
      if (refreshSkillsAborted) return;
      const next: SkillCard[] = Array.isArray(result.data)
        ? result.data.map((entry) => ({
            name: entry.name,
            description: entry.description,
            path: formatSkillPath(entry.location),
          }))
        : [];
      mutateState((current) => ({

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Pin/restore the OpenCode client version whose client exposes the `_client` handle, or update this store to use the new public API surface.
  2. Initialize the OpenCode client properly before constructing the extensions store and pass the real instance.
  3. For tests, build the mock with a `_client: { get: async () => ({ data: [...] }) }` field to satisfy the probe.
  4. Wrap the call so this error surfaces as 'skills unavailable' rather than a raw crash, and skip the refresh when the client is unusable.

Example fix

// before
const rawClient = client as unknown as { _client?: { get: ... } };
if (!rawClient._client) throw new Error("OpenCode client unavailable.");
// after — defensive caller check
function canFetchSkills(client: unknown): client is { _client: { get: (i: { url: string }) => Promise<unknown> } } {
  return !!client && typeof client === "object" && "_client" in client &&
    typeof (client as { _client?: { get?: unknown } })._client?.get === "function";
}
Defensive patterns

Strategy: type-guard

Validate before calling

function canFetchSkills(client: unknown): boolean {
  const c = client as { _client?: { get?: unknown } } | null;
  return !!c && !!c._client && typeof c._client.get === "function";
}

Type guard

function hasRawClient(client: unknown): client is { _client: { get: (i: { url: string }) => Promise<unknown> } } {
  const c = client as { _client?: { get?: unknown } };
  return !!c._client && typeof c._client.get === "function";
}

Try / catch

try {
  await store.refreshSkills({ force: true });
} catch (e) {
  if (e instanceof Error && e.message === 'OpenCode client unavailable.') {
    ui.showSkillsUnavailable(); // do not retry until client re-initialized
  } else throw e;
}

Prevention

When it happens

Trigger: Calling refreshSkills (e.g. via installClaudePlugin's refreshSkills({force:true}) or initial store load) when the injected OpenCode client instance lacks the internal _client property — i.e. a client wrapper/version whose internals differ, or client is null/wrong shape.

Common situations: OpenCode SDK was upgraded and the internal `_client` field was renamed or removed; the store was constructed with a mock or different client implementation in tests; the client failed to initialize and an incomplete object was passed in.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/14516e8225ff599d. Report an issue: GitHub.