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
- Pin/restore the OpenCode client version whose client exposes the `_client` handle, or update this store to use the new public API surface.
- Initialize the OpenCode client properly before constructing the extensions store and pass the real instance.
- For tests, build the mock with a `_client: { get: async () => ({ data: [...] }) }` field to satisfy the probe.
- 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
- Pin the OpenCode SDK version tested against the store
- Re-initialize the client after SDK upgrades
- Type the client contract explicitly instead of probing internals
- Cover client shape in tests
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
- OpenWork server cannot remove skills for this workspace.
- OpenWork server unavailable. Connect to remove skills.
- ${message}
- skill_not_found
- skill name must be kebab-case
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/14516e8225ff599d.
Report an issue: GitHub.