can1357/oh-my-pi · error

Share upload to ${base} failed: server returned no usable id

Error message

Share upload to ${base} failed: server returned no usable id

What it means

After a successful POST, uploadToServer parses the JSON response and validates that body.id is a string matching /^[A-Za-z0-9_-]{10,64}$/. If the server's JSON lacks a usable id (different shape, empty id, HTML error page parsed as null), the upload is considered failed because the returned id is what composes the share URL.

Source

Thrown at packages/coding-agent/src/export/share.ts:684

async function uploadToServer(sealed: Uint8Array, base: string): Promise<string> {
	let res: Response;
	try {
		res = await fetch(base, {
			method: "POST",
			headers: { "Content-Type": "application/octet-stream" },
			body: sealed,
		});
	} catch (err) {
		throw new Error(`Share upload to ${base} failed: ${err instanceof Error ? err.message : String(err)}`);
	}
	if (!res.ok) {
		const detail = (await res.text().catch(() => "")).trim().slice(0, 200);
		throw new Error(`Share upload to ${base} failed: HTTP ${res.status}${detail ? ` (${detail})` : ""}`);
	}
	const body = (await res.json().catch(() => null)) as { id?: unknown } | null;
	const id = body && typeof body.id === "string" ? body.id : "";
	if (!/^[A-Za-z0-9_-]{10,64}$/.test(id)) {
		throw new Error(`Share upload to ${base} failed: server returned no usable id`);
	}
	return id;
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Log/inspect the raw server response to see what was actually returned; align the server to return {"id":"<10-64 chars of [A-Za-z0-9_-]>"}.
  2. Update the self-hosted share server to the matching version of the client.
  3. Check for a proxy/CDN or captive portal rewriting responses; bypass it and test directly.
  4. If you control both sides and want full URLs, extend the client validation instead of shipping non-conforming ids.

Example fix

// before (server)
res.json({ url: `https://share.example.com/s/${token}` });
// after (server)
res.json({ id: token }); // token matches /^[A-Za-z0-9_-]{10,64}$/
Defensive patterns

Strategy: type-guard

Type guard

function hasUsableId(body: unknown): body is { id: string } {
  return typeof body === 'object' && body !== null
    && typeof (body as { id?: unknown }).id === 'string'
    && /^[A-Za-z0-9_-]{10,64}$/.test((body as { id: string }).id);
}

Try / catch

try {
  const id = await uploadToServer(base, sealed);
} catch (err) {
  if (err instanceof Error && err.message.includes('no usable id')) {
    // capture raw response for diagnostics; check server/client version compatibility
  } else throw err;
}

Prevention

When it happens

Trigger: The share server responds 2xx but with a body that is not the expected {id} JSON: an API change on the server, a proxy returning an HTML page with 200, a server returning {url: ...} instead of {id: ...}, or an id that is too short/long or contains invalid characters.

Common situations: Self-hosted share server running an incompatible version (older/newer response schema); CDN or captive portal injecting HTML into a 200 response; misconfigured server returning an empty body with 200.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/891cb10bbfbbf394. Report an issue: GitHub.