tinyhumansai/openhuman · error · Error
no thread id in create_new envelope: ${JSON.stringify(create
Error message
no thread id in create_new envelope: ${JSON.stringify(created).slice(0, 200)} What it means
While seeding the prior-chat canary thread, the audit script (scripts/debug/agent-prepare-context-audit.mjs) calls openhuman.threads_create_new and extracts the new thread id from the response envelope via `created?.data?.id || created?.id`. If neither shape is present it throws with a 200-char dump of the envelope. This is a defensive API-contract check: the script accepts two known envelope layouts and refuses to guess further.
Source
Thrown at scripts/debug/agent-prepare-context-audit.mjs:480
// Seed a *prior* conversation the scout can find via `transcript_search`. Plants
// a distinctive canary fact so a transcript-recall case can prove the scout read
// past chat (the summary should echo the canary). Returns { threadId, canary,
// query } or null on failure (seeding is best-effort — the audit still runs).
async function seedTranscript(opts) {
const canary = `deploy-canary-${randomBytes(4).toString("hex")}`;
const nowIso = new Date().toISOString();
try {
// create_new auto-generates the thread id; pull it from the envelope.
const created = await rpc(
opts.coreUrl,
opts.token,
"openhuman.threads_create_new",
{ labels: ["apc-audit-seed"] },
opts.rpcTimeoutMs,
);
const threadId = created?.data?.id || created?.id;
if (!threadId)
throw new Error(
`no thread id in create_new envelope: ${JSON.stringify(created).slice(0, 200)}`,
);
const message = {
id: `seed-${randomBytes(4).toString("hex")}`,
content: `Earlier I told you the staging deploy passphrase is "${canary}". Please remember it for later.`,
type: "text",
extraMetadata: {},
sender: "user",
createdAt: nowIso,
};
await rpc(
opts.coreUrl,
opts.token,
"openhuman.threads_message_append",
{ thread_id: threadId, message },
opts.rpcTimeoutMs,
);
return {View on GitHub (pinned to a221052e0d)
Solutions
- Read the dumped envelope in the error — it shows exactly where the id actually lives, telling you how stale the core is
- Rebuild/restart against a core from this branch: cargo build --bin openhuman-core and run with --spawn-core, or point --core-url at the freshly built core
- If you don't need the transcript-recall case, run with --no-seed-transcript to skip the seeding RPC entirely
- If the envelope changed intentionally, update the extraction line (created?.data?.id || created?.id) to match the new shape
Example fix
# before
$ node scripts/debug/agent-prepare-context-audit.mjs
Error: no thread id in create_new envelope: {"data":{"thread":{"id":"th_123"}}}
# after (run against a core built from this branch, or skip seeding)
$ node scripts/debug/agent-prepare-context-audit.mjs --spawn-core
# or
$ node scripts/debug/agent-prepare-context-audit.mjs --no-seed-transcript Defensive patterns
Strategy: type-guard
Validate before calling
const created = await rpc(coreUrl, token, "openhuman.threads_create_new", { labels: ["apc-audit-seed"] }, timeout);
if (!extractThreadId(created)) {
console.error("unexpected envelope:", JSON.stringify(created).slice(0, 400));
throw new Error("threads_create_new envelope drifted from {data.id}|{id} — core/script version mismatch");
} Type guard
function extractThreadId(envelope) {
if (envelope && typeof envelope === "object") {
const id = envelope?.data?.id ?? envelope?.id;
if (typeof id === "string" && id.length > 0) return id;
}
return null;
} Try / catch
try {
threadId = extractThreadId(await rpc(coreUrl, token, "openhuman.threads_create_new", params));
} catch (e) {
if (/no thread id in create_new envelope/.test(e.message)) {
// version skew: dump the envelope, skip the canary case rather than abort the whole audit
console.warn(`${e.message} — continuing with --no-seed-transcript semantics`);
} else throw e;
} Prevention
- Pin the core build to the same commit as the audit script — envelope drift is almost always version skew
- When a call must match multiple known envelope shapes, extract via a helper that returns null and fail with the dumped payload, like this script does
- Use --no-seed-transcript to decouple the transcript-recall case when you only care about the other audit cases
When it happens
Trigger: The target core's threads_create_new returns a differently-shaped envelope — an older/newer core version that wraps the id elsewhere, a proxy or RPC shim that rewrites the response, or (subtly) a JSON-RPC error that somehow bypassed the earlier body.error guard. Builtin to the --no-seed-transcript-less path only: passing --no-seed-transcript skips this call entirely.
Common situations: Running the audit against a stale installed core instead of one built from the current branch (the script header warns about exactly this); envelope refactors in the threads controller; RPC-routing middleware (dev proxies, tokens services) normalizing response bodies.
Related errors
- RPC ${method} error: ${JSON.stringify(body.error).slice(0, 3
- Invalid ${paramName}: '${value}'. Must be a valid integer ID
- Invalid ${paramName}: ${String(value)}. Type must be an inte
- Model test RPC returned no result for ${workload} via ${prov
- Login token invalid or expired
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/f4d92e07e32eab29.
Report an issue: GitHub.