windmill-labs/windmill · error

local script has no content/language

Error message

local script ${path} has no content/language

What it means

Before launching a local (workspace-resident) pipeline script during a cascade run, makeLaunch checks that the script has both `content` and `language`. It throws this error when a locally resolved script came back from the workspace without usable body or language, since runScriptPreview requires both fields.

Solutions

  1. Open the script at that path in the Windmill hub and re-save/deploy it with real content and a language
  2. Re-fetch the script with content included (ensure the resolver requests full script data, not metadata-only)
  3. If the path is wrong, correct the asset/dependency reference to point at the deployed script
  4. For generated drafts, populate `content` and `language` before dispatching the cascade run

Example fix

// before
const local = await resolveLocal(path) // { path } only — no content
await launch(path)
// after
const local = await getScriptByPath({ workspace, path }) // full content + language
if (!local?.content || !local?.language) throw new Error(`script ${path} missing on workspace`)
await launch(path)
Defensive patterns

Strategy: validation

Validate before calling

const local = await resolveLocal(path)
if (!local?.content?.trim() || !local?.language) {
  throw new Error(`local script ${path} is empty or has no language; redeploy it before running the cascade`)
}

Type guard

function isRunnableScript(s: { content?: string | null; language?: string | null } | null): s is { content: string; language: string } {
  return !!s && typeof s.content === 'string' && s.content.trim().length > 0 && typeof s.language === 'string'
}

Try / catch

try {
  await launch(path)
} catch (e) {
  if (e instanceof Error && e.message.includes('has no content/language')) {
    const fresh = await getScriptByPath({ workspace, path }) // re-fetch full content
    if (!fresh?.content || !fresh?.language) throw new Error(`script ${path} missing on workspace; fix the asset reference`)
    return launch(path, fresh)
  }
  throw e
}

Prevention

When it happens

Trigger: `resolveLocal(path)` returned a script object with empty/undefined `content` or `language` — e.g. the script exists but its deployment metadata was fetched instead of full content, the script is a draft/empty stub, or the resolver silently returned a skeleton object for a missing path.

Common situations: Script deployed from a flow without content; script fetched with `include_content` off or a partial get; asset graph pointing at a node whose underlying script was deleted or emptied; language not persisted for very old scripts or manually inserted DB rows.

Related errors


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

Appendix: source

Thrown at frontend/src/lib/components/assets/AssetGraph/cascadeRun.ts:86

	tempScriptRefs?: Record<string, string>
	// Extra run args for a specific node (e.g. the uploaded S3Object bound to a
	// `data_upload` cascade root). Merged over `_wmill_skip_asset_dispatch`; all
	// other nodes run with empty inputs as before.
	argsFor?: (path: string) => Record<string, any> | undefined
	onLaunched?: (path: string, jobId: string) => void
}): (path: string) => Promise<string> {
	return async function launch(path: string): Promise<string> {
		const local = opts.resolveLocal?.(path)
		// Caller args (e.g. the run form for the cascade root) must NOT be able to
		// re-enable backend asset dispatch while the client orchestrates the closure
		// — that would double-run downstream / run deployed subscribers. Drop any
		// `_wmill_skip_asset_dispatch` a caller supplied, and always spread it LAST.
		const { _wmill_skip_asset_dispatch: _reserved, ...extra } = opts.argsFor?.(path) ?? {}
		void _reserved
		let jobId: string
		if (local) {
			if (!local.content || !local.language) {
				throw new Error(`local script ${path} has no content/language`)
			}
			jobId = await JobService.runScriptPreview({
				workspace: opts.workspace,
				requestBody: {
					content: local.content,
					language: local.language,
					path,
					args: { ...extra, _wmill_skip_asset_dispatch: true },
					...(local.tag ? { tag: local.tag } : {}),
					...(opts.tempScriptRefs ? { temp_script_refs: opts.tempScriptRefs } : {})
				}
			})
		} else {
			jobId = await JobService.runScriptByPath({
				workspace: opts.workspace,
				path,
				requestBody: { ...extra, _wmill_skip_asset_dispatch: true }
			})

View on GitHub (pinned to e474e8803c)