{"record":{"id":"9197e705a7d4b0a3","repo":"windmill-labs/windmill","slug":"resource-path-has-no-string-content-is-it-a","errorCode":null,"errorMessage":"resource ${path} has no string \"content\" — is it an ${SKILLS_RESOURCE_TYPE}?","messagePattern":"resource (.+?) has no string \"content\" — is it an (.+?)\\?","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/copilot/chat/skills/skillResources.ts","lineNumber":118,"sourceCode":"\t// `fatal: false` replaces the partial code point a byte-aligned cut can leave\n\t// with U+FFFD; dropping it keeps the tail clean.\n\tconst cut = new TextDecoder('utf-8').decode(encoded.slice(0, maxBytes)).replace(/�$/, '')\n\treturn `${cut}… [truncated]`\n}\n\n/** The SKILL.md body of one skill. Throws rather than returning `''` when the\n * resource holds no readable body: an empty string reaches the model as a\n * successful read of a skill with no instructions, which it would then act on.\n *\n * Deliberately unbounded — the editor loads through here and saves what it loaded,\n * so truncating would rewrite an over-long skill the first time someone opened it.\n * Bounding belongs at the prompt boundary, where the cost actually is. */\nexport async function readSkillBody(workspace: string, path: string): Promise<string> {\n\tconst value = (await ResourceService.getResourceValue({ workspace, path })) as\n\t\t| { content?: unknown }\n\t\t| undefined\n\tif (typeof value?.content !== 'string') {\n\t\tthrow new Error(`resource ${path} has no string \"content\" — is it an ${SKILLS_RESOURCE_TYPE}?`)\n\t}\n\treturn value.content\n}\n\nexport async function saveSkillResource(\n\tworkspace: string,\n\tpath: string,\n\tdescription: string,\n\tinstructions: string,\n\t{ overwrite = false }: { overwrite?: boolean } = {}\n): Promise<void> {\n\tawait ResourceService.createResource({\n\t\tworkspace,\n\t\tupdateIfExists: overwrite,\n\t\trequestBody: {\n\t\t\tpath,\n\t\t\tdescription,\n\t\t\tvalue: { content: instructions },","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/copilot/chat/skills/skillResources.ts#L100-L136","documentation":"readSkillBody fetches a Windmill resource value and expects it to be a skill resource: an object with a string `content` field holding the SKILL.md body. If the resource is missing, of a different resource type, or its value lacks a string content field, it throws this error rather than returning '' — an empty string would reach the model as 'a skill with no instructions', which the agent would then wrongly act on.","triggerScenarios":"ResourceService.getResourceValue returns undefined (resource deleted or 404 path), or returns a value whose `content` is a non-string (object, number) — e.g. the resource at the given path is not of SKILLS_RESOURCE_TYPE or was created with a different value shape.","commonSituations":"A skill resource was edited by hand in the resource editor and its value no longer has {content: \"...\"}; the path passed by the agent points at an unrelated resource; the skills resource type doesn't exist in a fresh instance so the read resolves to nothing; stale skill listing after deletion.","solutions":["Verify the resource exists at `path` and is of the skill resource type","Re-create or repair the skill so its value is {content: \"<SKILL.md body>\"} (saveSkillResource does this)","If it is a leftover/broken resource, delete it and re-list skills before reading","Wrap the read in a try/catch when loading skill instructions so the agent gets a readable message instead of a crash"],"exampleFix":"// before\nconst body = await readSkillBody(ws, 'u/admin/skills/deploy')\n// after\nlet body\ntry {\n  body = await readSkillBody(ws, 'u/admin/skills/deploy')\n} catch {\n  body = 'Skill body unavailable: resource is not a valid skill resource.'\n}","handlingStrategy":"type-guard","validationCode":"const res = await ResourceService.getResourceValue({ workspace, path })\nconst ok = !!res && typeof (res as any).content === 'string'\nif (!ok) throw new Error(`${path} is not a valid skill resource`)","typeGuard":"function isSkillResource(v: unknown): v is { content: string } {\n  return typeof v === 'object' && v !== null && typeof (v as any).content === 'string'\n}","tryCatchPattern":"let body: string\ntry {\n  body = await readSkillBody(ws, path)\n} catch (e) {\n  body = `Skill unavailable: ${(e as Error).message}`\n}","preventionTips":["Always create skills via saveSkillResource so the value shape stays {content: string}","Don't hand-edit skill resources in the generic resource editor","Re-list skills after deletions instead of caching paths","Confirm the skills resource type exists on fresh instances"],"tags":["copilot","resources","skills","shape-mismatch"],"backgroundTag":"unexpected-resource-shape","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}