windmill-labs/windmill · error · Error
Backend runnable "${target.key}" not found in app "${appPath
Error message
Backend runnable "${target.key}" not found in app "${appPath}". What it means
When extracting a backend runnable's source from an app draft value, core.ts looks up value.runnables[target.key]. This error is thrown when no runnable exists under that key in the draft of the app at appPath, meaning the target key is stale, mistyped, or the app was changed by another step.
Source
Thrown at frontend/src/lib/components/copilot/chat/global/core.ts:1772
}
function getInlineScriptExtension(runnable: PersistedRunnable | undefined): 'ts' | 'py' {
return runnable?.inlineScript?.language === 'python3' ? 'py' : 'ts'
}
/**
* Resolve a backend file target to its inline script body, validating that the
* runnable exists, is inline, and matches the requested file extension. Throws
* with a clear message otherwise.
*/
function getInlineRunnableContent(
value: AppDraftValue,
target: { kind: 'backend'; filePath: string; key: string; extension: 'ts' | 'py' },
appPath: string
): { content: string; runnable: PersistedRunnable } {
const runnable = value.runnables[target.key] as PersistedRunnable | undefined
if (!runnable) {
throw new Error(`Backend runnable "${target.key}" not found in app "${appPath}".`)
}
if (runnable.type !== 'inline' && runnable.type !== 'runnableByName') {
throw new Error(
`Runnable "${target.key}" is not inline. Use read_workspace_item on the referenced ${runnable.runType ?? 'item'} instead.`
)
}
const expected = getInlineScriptExtension(runnable)
if (target.extension !== expected) {
throw new Error(
`Runnable "${target.key}" language is ${expected}. Use backend/${target.key}/main.${expected}.`
)
}
return { content: runnable.inlineScript?.content ?? '', runnable }
}
async function loadAppValueForRead(path: string, workspace: string): Promise<AppDraftValue> {
const draft = await getGlobalDraft(workspace, 'app', path)
if (draft && draft.value && typeof draft.value === 'object' && 'files' in draft.value) {View on GitHub (pinned to e474e8803c)
Solutions
- Re-read the app draft to list the actual runnable keys and use one of those.
- Fix the key spelling/casing in the tool call.
- If the runnable was removed, recreate it before editing its file.
- Derive targets from fresh tool output each time rather than caching them across steps.
Example fix
// before
readBackendFile({ appPath: 'f/app', key: 'fetch' }) // not in draft
// after
readBackendFile({ appPath: 'f/app', key: 'get_data' }) // key from the app draft listing Defensive patterns
Strategy: validation
Validate before calling
const draft = await readAppDraft(appPath)
if (!(target.key in draft.value.runnables)) {
throw new Error(`key ${target.key} not in ${Object.keys(draft.value.runnables)}`)
} Type guard
function hasRunnable(v: AppDraftValue, key: string): boolean {
return Object.prototype.hasOwnProperty.call(v.runnables ?? {}, key)
} Try / catch
try {
await readBackendFile({ appPath, key })
} catch (e) {
if (e.message.includes('not found in app')) {
const draft = await readAppDraft(appPath)
const realKey = Object.keys(draft.value.runnables).find((k) => k === key || k.includes(key))
if (realKey) await readBackendFile({ appPath, key: realKey })
}
} Prevention
- Re-read the app draft immediately before targeting backend runnables
- Never reuse keys cached from an earlier version of the app
- Match keys exactly (case-sensitive) from the runnables map, don't guess
When it happens
Trigger: Calling an app backend-file read/edit tool with key:"fetch" while the draft's runnables map only has keys like "get_data"; the runnable was deleted or renamed after the target was computed; the app path was redeployed and keys regenerated.
Common situations: The AI guesses backend runnable keys instead of reading the app structure first; concurrent edits removed the runnable; a copy of a target object from an earlier app version is reused.
Related errors
- inlineScript is required when runnable type is "inline".
- path is required when runnable type is "script", "flow", or
- "${filePath}" is generated automatically from backend runnab
- Pipeline nodes must be in the open folder — use 'f/${folder}
- Invalid note at index ${index}: contained_node_ids "${id}" d
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/837f6aafb40300b0.
Report an issue: GitHub.