tinyhumansai/openhuman · warning
threads_token_usage returned an empty envelope
Error message
threads_token_usage returned an empty envelope
What it means
Thrown by fetchThreadTokenUsage() when the 'openhuman.threads_token_usage' response's .data is falsy (null, undefined, or the whole response missing). The contract says a thread with no completed turns returns zeros with hasUsage: false — so an empty envelope means the core either predates the method's zero-fill behavior, returned null for an unknown thread_id, or wrapped nothing.
Source
Thrown at app/src/services/api/threadUsageApi.ts:70
subagents?: ThreadSubagentUsageWire[];
}
interface Envelope<T> {
data?: T;
}
/**
* Fetch a thread's persisted token/cost totals from the core (read back from
* its session transcripts). Returns zeros with `hasUsage: false` for a thread
* that has no completed turns yet.
*/
export async function fetchThreadTokenUsage(threadId: string): Promise<ThreadTokenUsage> {
const response = await callCoreRpc<Envelope<ThreadTokenUsageWire>>({
method: 'openhuman.threads_token_usage',
params: { thread_id: threadId },
});
const d = response?.data;
if (!d) throw new Error('threads_token_usage returned an empty envelope');
return {
threadId: d.thread_id,
inputTokens: d.input_tokens,
outputTokens: d.output_tokens,
cachedInputTokens: d.cached_input_tokens,
costUsd: d.cost_usd,
turnCount: d.turn_count,
lastTurnInputTokens: d.last_turn_input_tokens,
lastTurnOutputTokens: d.last_turn_output_tokens,
contextWindow: d.context_window,
model: d.model,
updated: d.updated,
hasUsage: d.has_usage,
subagents: (d.subagents ?? []).map(s => ({
agentId: s.agent_id,
inputTokens: s.input_tokens,
outputTokens: s.output_tokens,
costUsd: s.cost_usd,View on GitHub (pinned to a221052e0d)
Solutions
- Verify the thread exists on the core side (threads list / the chat itself loads)
- Update core and app together — newer cores return a zeroed record with hasUsage: false instead of null
- If it persists, curl openhuman.threads_token_usage with the thread_id and inspect the raw result
- As a client-side mitigation, catch this specific error and render zeros/hasUsage: false (usage display is non-critical)
Example fix
// before
const usage = await fetchThreadTokenUsage(threadId);
// after
let usage: ThreadTokenUsage;
try {
usage = await fetchThreadTokenUsage(threadId);
} catch (e) {
if (e instanceof Error && e.message.includes('empty envelope')) {
usage = zeroUsage(threadId); // display zeros, mark hasUsage: false
} else throw e;
} Defensive patterns
Strategy: fallback
Validate before calling
// Skip the fetch for threads you know are brand-new if the UI can tolerate zeros: if (!threadHasCompletedTurns(threadId)) renderZeroUsage(); // avoids calling on older cores
Type guard
function isThreadUsageEnvelope(v: unknown): v is { data: { thread_id: string; input_tokens: number } } {
const r = v as Record<string, unknown> | null | undefined;
const d = r?.data as Record<string, unknown> | undefined;
return !!d && typeof d.thread_id === 'string' && typeof d.input_tokens === 'number';
} Try / catch
try {
const usage = await fetchThreadTokenUsage(threadId);
renderUsage(usage);
} catch (e) {
if (e instanceof Error && e.message.includes('empty envelope')) {
renderUsage(zeroUsage(threadId)); // usage display degrades to zeros
} else throw e;
} Prevention
- Update core and app together so threads_token_usage zero-fills instead of returning null
- Treat usage/cost display as non-critical: always have a zero-usage fallback ready
- Verify thread ids passed in actually exist on the current core workspace
When it happens
Trigger: Calling fetchThreadTokenUsage(threadId) for a thread id the core does not know (deleted, wrong workspace, or not yet persisted); an older core returning {data: null} instead of a zeroed usage record; a mock resolving {data: null}.
Common situations: Opening thread usage/cost UI immediately after creating a thread on an older core; pointing the app at a workspace whose threads db was reset; frontend/core version skew on the threads_token_usage controller.
Related errors
- RPC envelope contains undefined data
- Discord link start response missing required string field: l
- Discord link start response missing required string field: i
- Discord link complete response missing required boolean fiel
- Channel connect response missing status
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/898fa1c0c29bafea.
Report an issue: GitHub.