windmill-labs/windmill · error · Error

Runnable "${target.key}" language is ${expected}. Use backen

Error message

Runnable "${target.key}" language is ${expected}. Use backend/${target.key}/main.${expected}.

What it means

Each inline runnable has a language derived from its stored inline script (via getInlineScriptExtension), and the tool target must supply a matching file extension. This error fires when the target's extension does not match the runnable's actual language, so file paths like backend/<key>/main.ts vs main.py must match.

Source

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

 * 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) {
		return draft.value as AppDraftValue
	}

	const app = await AppService.getAppByPath({ workspace, path })
	return appSourceToDraftValue(app, app)
}

async function loadAppDraftValue(path: string, workspace: string): Promise<LoadedAppDraftValue> {
	const draft = await getGlobalDraft(workspace, 'app', path)

View on GitHub (pinned to e474e8803c)

Solutions

  1. Check the runnable's inlineScript language and use the corresponding extension (e.g. main.ts, main.py)
  2. Retry the tool call with backend/<key>/main.<expected-extension>

Example fix

// before
runBackendFile('backend/myStep/main.py', code)
// after
runBackendFile('backend/myStep/main.ts', code) // matches the inline script's TypeScript
Defensive patterns

Strategy: validation

Validate before calling

const expected = getInlineScriptExtension(runnable);
if (target.extension !== expected) throw new Error(`use main.${expected}`);

Type guard

function matchesExtension(t, runnable) { return t.extension === getInlineScriptExtension(runnable); }

Try / catch

try { writeFile(path, code) } catch (e) { if (e.message.includes('language is')) retryWithExtension(e.message.match(/main\.(\w+)/)?.[1]); }

Prevention

When it happens

Trigger: A tool call provides target.extension that differs from getInlineScriptExtension(runnable), e.g. writing backend/foo/main.py when the inline script is TypeScript.

Common situations: The model guesses the wrong language for an inline script; the runnable was re-authored in a different language than the model assumes.

Related errors


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