tinyhumansai/openhuman · error
Discord link start response missing required string field…
Error message
Discord link start response missing required string field: linkToken
What it means
Thrown by expectDiscordLinkStart() while validating the payload of the Discord link-start RPC: the unwrapped response object does not contain a non-empty string `linkToken`. The linkToken is the one-time token the user DMs to the Discord bot to pair their account; without it the pairing flow cannot proceed. This is a response-shape contract break between the UI's validator and whatever the core returned.
Solutions
- Restart/update the core so the Discord connections controller matches the UI's expected schema (linkToken + instructions).
- Inspect the raw RPC payload: log the response before expectDiscordLinkStart to see which field name/shape the core actually sent.
- Check that the Discord channel provider is configured in the core (bot token present), since a missing provider can yield a stubbed start response.
- If the core legitimately returns snake_case, normalize in expectObject's unwrap step instead of failing.
Example fix
// before
if (typeof record.linkToken !== 'string' || !record.linkToken) {
throw new Error('Discord link start response missing required string field: linkToken');
}
// after — tolerate snake_case from older cores, fail loudly otherwise
const linkToken = typeof record.linkToken === 'string' && record.linkToken
? record.linkToken
: typeof record.link_token === 'string' ? record.link_token : null;
if (!linkToken) {
throw new Error('Discord link start response missing required string field: linkToken');
} Defensive patterns
Strategy: type-guard
Type guard
function isDiscordLinkStartPayload(v: unknown): v is { linkToken: string; instructions: string } {
const r = v as Record<string, unknown> | null;
return Boolean(r)
&& typeof r!.linkToken === 'string' && r!.linkToken.length > 0
&& typeof r!.instructions === 'string';
} Try / catch
try {
const start = await startDiscordLink();
} catch (e) {
if (e instanceof Error && e.message.includes('missing required string field: linkToken')) {
notify('Pairing unavailable — restart Core and retry.');
} else throw e;
} Prevention
- Keep core and UI versions aligned after updates.
- Log the raw RPC payload in dev when a schema validator fails.
- Reuse the shipped expect* validators for mocks so tests match production shapes.
When it happens
Trigger: The core's Discord link.start handler returned an error object or partial result that still unwrapped to a record (e.g. {ok:false}); version skew where an older core's response uses a different field name (link_token snake_case not mapped); an envelope-unwrapping step (unwrapCliEnvelope) stripped the wrong layer and left an empty object.
Common situations: UI newer than the running core after a partial update or with OPENHUMAN_CORE_REUSE_EXISTING=1; a core handler change renamed fields without a frontend counterpart; the Discord provider not configured server-side so start degrades to a stub response.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Discord link complete response missing required boolean…
- Discord link start response missing required string field…
- Channel connect response missing status
- MISSING_ARTIFACT_PATH
- Model test RPC returned no result for
AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17).
Data as JSON: /api/errors/301fde105afdcbcf.
Report an issue: GitHub.
Appendix: source
Thrown at app/src/services/api/channelConnectionsApi.ts:79
if (!Array.isArray(unwrapped)) {
throw new Error(`${context} returned an invalid response shape`);
}
return unwrapped as T[];
}
function expectObject<T extends object>(payload: unknown, context: string): T {
const unwrapped = unwrapCliEnvelope<unknown>(payload);
const record = asRecord(unwrapped);
if (!record) {
throw new Error(`${context} returned an invalid response shape`);
}
return record as T;
}
function expectDiscordLinkStart(payload: unknown): DiscordLinkStartResult {
const record = expectObject<Record<string, unknown>>(payload, 'Discord link start');
if (typeof record.linkToken !== 'string' || !record.linkToken) {
throw new Error('Discord link start response missing required string field: linkToken');
}
if (typeof record.instructions !== 'string') {
throw new Error('Discord link start response missing required string field: instructions');
}
return { linkToken: record.linkToken, instructions: record.instructions };
}
function expectDiscordLinkComplete(payload: unknown): DiscordLinkCheckResult {
const record = expectObject<Record<string, unknown>>(payload, 'Discord link complete');
if (typeof record.linked !== 'boolean') {
throw new Error('Discord link complete response missing required boolean field: linked');
}
const details =
record.details !== undefined && record.details !== null
? (record.details as Record<string, unknown>)
: null;
return { linked: record.linked, details };
}View on GitHub (pinned to 7491200858)