{"record":{"id":"11aa4ab2be8e85ed","repo":"Mintplex-Labs/anything-llm","slug":"workspace-slug-or-thread-threadslug-is-not-v","errorCode":null,"errorMessage":"Workspace ${slug} or thread ${threadSlug} is not valid.","messagePattern":"Workspace (.+?) or thread (.+?) is not valid\\.","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"server/endpoints/api/workspaceThread/index.js","lineNumber":591,"sourceCode":"      }\n      */\n      try {\n        const { slug, threadSlug } = request.params;\n        const {\n          message,\n          mode = null,\n          userId,\n          attachments = [],\n          reset = false,\n        } = reqBody(request);\n        const workspace = await Workspace.get({ slug: String(slug) });\n        const thread = await WorkspaceThread.get({\n          slug: String(threadSlug),\n          workspace_id: workspace.id,\n        });\n\n        if (!workspace || !thread) {\n          response.status(400).json({\n            id: uuidv4(),\n            type: \"abort\",\n            textResponse: null,\n            sources: [],\n            close: true,\n            error: `Workspace ${slug} or thread ${threadSlug} is not valid.`,\n          });\n          return;\n        }\n\n        const resolvedMode = mode ?? workspace.chatMode;\n        if (\n          (!message?.length || !VALID_CHAT_MODE.includes(resolvedMode)) &&\n          !reset\n        ) {\n          response.status(400).json({\n            id: uuidv4(),\n            type: \"abort\",","sourceCodeStart":573,"sourceCodeEnd":609,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/3aec848f2885144aa8f1e53b9731a04310d5d558/server/endpoints/api/workspaceThread/index.js#L573-L609","documentation":"HTTP 400 from POST /api/v1/workspace/:slug/thread/:threadSlug/stream-chat when either the workspace or thread lookup is null, with one message covering both cases. Subtlety: the code calls WorkspaceThread.get({workspace_id: workspace.id}) BEFORE the null check on workspace, so a completely missing workspace usually throws a TypeError ('Cannot read properties of null') that lands in the catch handler instead; this 400 therefore fires most often when the workspace resolves but the thread does not, or when Workspace.get returns undefined.","triggerScenarios":"Valid workspace slug with a thread slug that is deleted or belongs to another workspace (400); completely unknown workspace slug often surfaces as the catch path with a TypeError message rather than this 400; sending thread name instead of thread slug.","commonSituations":"Stale thread references after thread deletion; duplicated workspaces reusing thread slugs; clients assuming thread slugs are global.","solutions":["Check the thread exists under this workspace via the thread chats endpoint first","If the error string appears with a valid workspace, focus on the threadSlug - it is the usual culprit","If the server log shows a TypeError instead, your workspace slug itself is wrong - resolve it via GET /api/v1/workspaces","Create the thread with POST /v1/workspace/:slug/thread/new when missing and use the returned slug"],"exampleFix":"// before\nawait streamThreadChat(slug, threadSlug, message);\n// after\nconst ok = await fetch(`${BASE}/api/v1/workspace/${slug}/thread/${threadSlug}/chats`, { headers: AUTH }).then(r => r.ok);\nif (!ok) threadSlug = (await createThread(slug)).slug; // then stream","handlingStrategy":"validation","validationCode":"const ok = await fetch(`${BASE}/api/v1/workspace/${slug}/thread/${threadSlug}/chats`, { headers: AUTH }).then(r => r.ok);\nif (!ok) threadSlug = (await createThread(slug)).slug;","typeGuard":"function isWorkspaceOrThreadInvalid(d) { return d?.type === 'abort' && /is not valid/.test(d.error ?? ''); }","tryCatchPattern":"try { await streamThreadChat(slug, threadSlug, body); }\ncatch (e) { if (isWorkspaceOrThreadInvalid(e.payload)) { await revalidateSlugs(); } else throw e; }","preventionTips":["Pre-validate both slugs (workspace first - a missing workspace can surface as a TypeError 500 here, not this 400)","Create missing threads before streaming","Handle the abort chunk inside the SSE consumer"],"tags":["anythingllm","developer-api","workspace-thread","stream-chat","http-400","not-found"],"backgroundTag":"resource-not-found","analyzedSha":"3aec848f2885144aa8f1e53b9731a04310d5d558","analyzedAt":"2026-08-18T10:02:21.017Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}