windmill-labs/windmill · error

write_app_file only writes frontend files. Use write_app_run

Error message

write_app_file only writes frontend files. Use write_app_runnable to set inline backend script content.

What it means

The write_app_file copilot tool can only create/modify frontend files (svelte components and other files in the app's files map). When the target path resolves to a backend runnable (inline script), the tool throws and directs the model to use write_app_runnable instead, because backend code lives in the runnables map, not the files map.

Source

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

		)
	}

	toolCallbacks.setToolStatus(toolId, {
		content: `Found ${totalMatchCount} match${totalMatchCount === 1 ? '' : 'es'} in ${fileCount} file${
			fileCount === 1 ? '' : 's'
		}`
	})
	return out.join('\n')
}

async function writeAppFile(
	args: { path: string; file_path: string; content: string },
	ctx: WriteDraftCtx
): Promise<string> {
	const { workspace, toolId, toolCallbacks } = ctx
	const target = resolveAppFileTarget(args.file_path)
	if (target.kind !== 'frontend') {
		throw new Error(
			`write_app_file only writes frontend files. Use write_app_runnable to set inline backend script content.`
		)
	}
	assertNotGeneratedAppFile(target.filePath)

	toolCallbacks.setToolStatus(toolId, {
		content: `Writing ${target.filePath} to app "${args.path}"...`
	})

	const { value } = await loadAppDraftValue(args.path, workspace)
	value.files = { ...value.files, [target.filePath]: args.content }
	const result = await saveAppDraft(workspace, args.path, value)
	return finishAppDraftWrite(result, ctx, () => ({
		content: `Updated ${target.filePath} in app "${args.path}"`,
		message: `Updated draft app "${args.path}" with frontend file "${target.filePath}".`
	}))
}

View on GitHub (pinned to e474e8803c)

Solutions

  1. Call write_app_runnable with the runnable key/path and the script content instead
  2. Re-read the app schema to identify which targets are frontend files vs backend runnables
  3. If the backend code should be a separate file rather than an inline script, restructure the app so the code lives in a frontend-accessible location or a dedicated script

Example fix

// before
write_app_file({ path: 'f/app_main', file_path: 'backend/process.py', content: 'def run(): ...' })
// after
write_app_runnable({ path: 'f/app_main', key: 'process', content: 'def run(): ...' })
Defensive patterns

Strategy: validation

Validate before calling

const target = resolveAppFileTarget(args.file_path)
if (target.kind !== 'frontend') {
  // route to write_app_runnable instead
}

Type guard

function isFrontendTarget(t: { kind: string }): t is { kind: 'frontend'; filePath: string } {
  return t.kind === 'frontend'
}

Try / catch

try {
  await writeAppFile(args)
} catch (e) {
  if (e instanceof Error && e.message.includes('write_app_runnable')) {
    await writeAppRunnable({ path: args.path, key: args.file_path, content: args.content })
  }
}

Prevention

When it happens

Trigger: The AI model calls write_app_file with `file_path` that resolveAppFileTarget classifies as backend (e.g. a `.py`/`.ts` runnable path like `backend/script.py` or a runnable key) instead of a frontend file path.

Common situations: Model confusion about the app's dual structure (files vs runnables); trying to edit the inline backend script of a runnable via the file-writing tool; misinterpreting the app schema returned by read tools.

Related errors


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