windmill-labs/windmill · error

' ' does not match runnable ' ' language. Use backend/…

Error message

'${target.path}' does not match runnable '${target.key}' language. Use backend/${target.key}/main.${expectedExtension}.

What it means

For inline backend runnables, applyPatch enforces that the patch path's extension matches the runnable's script language: TypeScript runnables use main.ts, Python (python3) runnables use main.py (getBackendInlineScriptExtension). This error is thrown when the extension in the path disagrees with the runnable's actual language.

Solutions

  1. Use the extension matching the runnable's language: backend/<key>/main.ts for TypeScript, backend/<key>/main.py for python3
  2. Check inlineScript.language on the runnable before constructing the path
  3. If the language itself is wrong, change the runnable's language through the app definition rather than patching a mismatched file

Example fix

// before
applyPatch({ path: 'backend/etl/main.ts', ... }) // runnable.language = python3
// after
applyPatch({ path: 'backend/etl/main.py', ... })
Defensive patterns

Strategy: validation

Validate before calling

const expected = runnable.inlineScript?.language === 'python3' ? 'py' : 'ts'
if (!path.endsWith(`main.${expected}`)) {
  throw new Error(`Use backend/${key}/main.${expected} for this runnable`)
}

Type guard

function pathMatchesRunnableLanguage(path: string, r: BackendRunnable): boolean {
  const expected = r.inlineScript?.language === 'python3' ? 'py' : 'ts'
  return path.endsWith(`main.${expected}`)
}

Try / catch

try {
  await patchTool({ path, ... })
} catch (e) {
  const m = e instanceof Error && e.message.match(/Use (backend\/.*)\./)
  if (m) { return patchTool({ path: m[1], ... }) } // retry with corrected extension
  throw e
}

Prevention

When it happens

Trigger: Patching 'backend/step/main.py' when the runnable's inlineScript.language is not python3 (defaults to ts), or patching 'main.ts' for a Python runnable. resolveAppPatchTarget only matches .ts/.py paths, but either can be chosen regardless of the runnable's real language.

Common situations: Model assuming Python for a TS step; a runnable whose language was changed after the model read an older file listing; generated paths defaulting to .ts for Python-heavy apps.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at frontend/src/lib/components/copilot/chat/app/core.ts:631

				const frontendContent = helpers.getFrontendFile(target.path)
				if (frontendContent === undefined) {
					throw new Error(`Frontend file '${target.path}' not found.`)
				}
				currentContent = frontendContent
			} else {
				backendRunnable = helpers.getBackendRunnable(target.key)
				if (!backendRunnable) {
					throw new Error(`Backend runnable '${target.key}' not found.`)
				}
				if (backendRunnable.type !== 'inline' || !backendRunnable.inlineScript) {
					throw new Error(
						`'${target.path}' points to backend runnable '${target.key}', but only inline runnables can be patched as files.`
					)
				}

				const expectedExtension = getBackendInlineScriptExtension(backendRunnable)
				if (target.extension !== expectedExtension) {
					throw new Error(
						`'${target.path}' does not match runnable '${target.key}' language. Use backend/${target.key}/main.${expectedExtension}.`
					)
				}

				currentContent = backendRunnable.inlineScript.content ?? ''
			}

			const updatedContent = findAndReplace(
				currentContent,
				oldString,
				newString,
				replaceAll,
				'current file content'
			)

			toolCallbacks.setToolStatus(toolId, {
				content: `Patching '${target.path}'...`
			})

View on GitHub (pinned to e474e8803c)