windmill-labs/windmill · error
resource ${path} has no string "content" — is it an ${SKILLS
Error message
resource ${path} has no string "content" — is it an ${SKILLS_RESOURCE_TYPE}? What it means
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.
Source
Thrown at frontend/src/lib/components/copilot/chat/skills/skillResources.ts:118
// `fatal: false` replaces the partial code point a byte-aligned cut can leave
// with U+FFFD; dropping it keeps the tail clean.
const cut = new TextDecoder('utf-8').decode(encoded.slice(0, maxBytes)).replace(/�$/, '')
return `${cut}… [truncated]`
}
/** The SKILL.md body of one skill. Throws rather than returning `''` when the
* resource holds no readable body: an empty string reaches the model as a
* successful read of a skill with no instructions, which it would then act on.
*
* Deliberately unbounded — the editor loads through here and saves what it loaded,
* so truncating would rewrite an over-long skill the first time someone opened it.
* Bounding belongs at the prompt boundary, where the cost actually is. */
export async function readSkillBody(workspace: string, path: string): Promise<string> {
const value = (await ResourceService.getResourceValue({ workspace, path })) as
| { content?: unknown }
| undefined
if (typeof value?.content !== 'string') {
throw new Error(`resource ${path} has no string "content" — is it an ${SKILLS_RESOURCE_TYPE}?`)
}
return value.content
}
export async function saveSkillResource(
workspace: string,
path: string,
description: string,
instructions: string,
{ overwrite = false }: { overwrite?: boolean } = {}
): Promise<void> {
await ResourceService.createResource({
workspace,
updateIfExists: overwrite,
requestBody: {
path,
description,
value: { content: instructions },View on GitHub (pinned to e474e8803c)
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
Example fix
// before
const body = await readSkillBody(ws, 'u/admin/skills/deploy')
// after
let body
try {
body = await readSkillBody(ws, 'u/admin/skills/deploy')
} catch {
body = 'Skill body unavailable: resource is not a valid skill resource.'
} Defensive patterns
Strategy: type-guard
Validate before calling
const res = await ResourceService.getResourceValue({ workspace, path })
const ok = !!res && typeof (res as any).content === 'string'
if (!ok) throw new Error(`${path} is not a valid skill resource`) Type guard
function isSkillResource(v: unknown): v is { content: string } {
return typeof v === 'object' && v !== null && typeof (v as any).content === 'string'
} Try / catch
let body: string
try {
body = await readSkillBody(ws, path)
} catch (e) {
body = `Skill unavailable: ${(e as Error).message}`
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- AI response was empty
- AI response contained empty code block
- AI response did not contain valid code. Please try rephrasin
- An unexpected error occurred. Please try again.
- No flow helpers found
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/9197e705a7d4b0a3.
Report an issue: GitHub.