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

  1. Re-read the app draft to list the actual runnable keys and use one of those.
  2. Fix the key spelling/casing in the tool call.
  3. If the runnable was removed, recreate it before editing its file.
  4. 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

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


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/837f6aafb40300b0. Report an issue: GitHub.