windmill-labs/windmill · error

Backend runnable "${key}" not found in app "${path}".

Error message

Backend runnable "${key}" not found in app "${path}".

What it means

Thrown by the delete_app_runnable tool when the given `key` is not present in the app draft's `runnables` map, so there is nothing to remove. Note that unlike the lookup error (395), this one does not enumerate available runnables.

Source

Thrown at frontend/src/lib/components/copilot/chat/global/core.ts:6065

		background: args.background,
		detachAfterMs: waitSecondsToDetachMs(args.wait_seconds),
		label: `${path} / ${key}`
	})
}

async function deleteAppRunnable(
	args: { path: string; key: string },
	ctx: WriteDraftCtx
): Promise<string> {
	const { workspace, toolId, toolCallbacks } = ctx
	const { path, key } = args
	toolCallbacks.setToolStatus(toolId, {
		content: `Removing runnable "${key}" from app "${path}"...`
	})

	const { value } = await loadAppDraftValue(path, workspace)
	if (!(key in value.runnables)) {
		throw new Error(`Backend runnable "${key}" not found in app "${path}".`)
	}
	const { [key]: _removed, ...remaining } = value.runnables
	value.runnables = remaining
	await recomputeAppPolicy(value)
	const result = await saveAppDraft(workspace, path, value)
	return finishAppDraftWrite(result, ctx, () => ({
		content: `Removed runnable "${key}" from app "${path}"`,
		message: `Removed runnable "${key}" from draft app "${path}".`
	}))
}

const triggerLabels: Record<TriggerKind, string> = {
	http: 'HTTP trigger',
	websocket: 'WebSocket trigger',
	kafka: 'Kafka trigger',
	nats: 'NATS trigger',
	postgres: 'Postgres trigger',
	mqtt: 'MQTT trigger',

View on GitHub (pinned to e474e8803c)

Solutions

  1. Re-read the app draft to list existing runnables and retry with a valid key
  2. If already deleted, treat the removal as done and move on
  3. Rebase/reset the draft if you expected the runnable to be present from the deployed version

Example fix

// before
delete_app_runnable({ path: 'f/app_main', key: 'old_job' }) // not in draft
// after
// verify with a read of the app draft, then target an existing key:
delete_app_runnable({ path: 'f/app_main', key: 'oldJob' })
Defensive patterns

Strategy: validation

Validate before calling

const { value } = await loadAppDraftValue(path, workspace)
if (!(key in (value.runnables ?? {}))) {
  return // already gone; no-op
}

Type guard

function runnableExists(runnables: Record<string, unknown> | undefined, key: string): boolean {
  return !!runnables && key in runnables
}

Try / catch

try {
  await deleteAppRunnable(args)
} catch (e) {
  if (e instanceof Error && e.message.includes('not found in app')) {
    // treat as already deleted; skip instead of retrying
  }
}

Prevention

When it happens

Trigger: delete_app_runnable is called with a key absent from `value.runnables` — already deleted, typo, or key that only exists in the deployed (not draft) version.

Common situations: Double-delete after a previous successful removal in the same session; model hallucinating a runnable name; draft was reset/rebased so the runnable no longer exists in it.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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