windmill-labs/windmill · error

delete_app_file only deletes frontend files. Use delete_app_

Error message

delete_app_file only deletes frontend files. Use delete_app_runnable for backend runnables.

What it means

The delete_app_file copilot tool only removes entries from the app's frontend files map. When the given file_path resolves to a backend runnable, it throws and points at delete_app_runnable, since runnables are stored in a separate map with their own lifecycle.

Source

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

	})

	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}".`
	}))
}

async function deleteAppFile(
	args: { path: string; file_path: string },
	ctx: WriteDraftCtx
): Promise<string> {
	const { workspace, toolId, toolCallbacks } = ctx
	const target = resolveAppFileTarget(args.file_path)
	if (target.kind !== 'frontend') {
		throw new Error(
			`delete_app_file only deletes frontend files. Use delete_app_runnable for backend runnables.`
		)
	}
	assertNotGeneratedAppFile(target.filePath)

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

	const { value } = await loadAppDraftValue(args.path, workspace)
	if (!(target.filePath in value.files)) {
		throw new Error(`Frontend file "${target.filePath}" not found in app "${args.path}".`)
	}
	const { [target.filePath]: _removed, ...remaining } = value.files
	value.files = remaining
	const result = await saveAppDraft(workspace, args.path, value)
	return finishAppDraftWrite(result, ctx, () => ({
		content: `Removed ${target.filePath} from app "${args.path}"`,

View on GitHub (pinned to e474e8803c)

Solutions

  1. Call delete_app_runnable with the runnable key instead
  2. Re-read the app to list actual frontend files before deleting
  3. Refresh the model's context with the current app schema

Example fix

// before
delete_app_file({ path: 'f/app_main', file_path: 'backend/legacy.py' })
// after
delete_app_runnable({ path: 'f/app_main', key: 'legacy' })
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

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

Try / catch

try {
  await deleteAppFile(args)
} catch (e) {
  if (e instanceof Error && e.message.includes('delete_app_runnable')) {
    await deleteAppRunnable({ path: args.path, key: args.file_path })
  }
}

Prevention

When it happens

Trigger: The AI model calls delete_app_file with a `file_path` that resolveAppFileTarget classifies as a backend runnable rather than a frontend file.

Common situations: Model tries to clean up by deleting what it thinks is a file but is actually an inline backend script; stale app schema in the model's context causes it to mistake runnables for files.

Related errors


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