ramensoftware/windhawk · error

The webview template has no

Error message

The webview template has no <head> tag to inject the Content-Security-Policy into

What it means

The webview's HTML template must contain a literal <head> tag because the extension injects the base href and a strict Content-Security-Policy by patching that anchor. If the bundled template no longer includes '<head>', the extension throws instead of silently producing a webview without a CSP (which would be a security regression).

Solutions

  1. Inspect the bundled webview template and restore a literal '<head>' opening tag.
  2. If the build step rewrites the tag, fix the bundler/minifier config to preserve the plain <head> literal.
  3. Update the injection code to match the new template anchor if the template intentionally changed (e.g. match /<head[^>]*>/i).
  4. Rebuild the template bundle after fixing and confirm the CSP is injected into the served webview HTML.

Example fix

// before (template)
<head profile="">
// after
<head>
// or, code-side: match the actual tag
// const headTag = html.match(/<head[^>]*>/i)?.[0];
Defensive patterns

Strategy: validation

Validate before calling

// before patching/injecting
const html = fs.readFileSync(templatePath, 'utf8');
if (!html.includes('<head>')) {
  throw new Error(`${templatePath}: '<head>' anchor missing; fix template or bundler`);
}

Try / catch

try {
  const html = buildWebviewHtml(templatePath, nonce);
} catch (e) {
  if (e.message.includes('no <head> tag')) {
    reportTemplateAnchorMissing(templatePath); // fail the build/dev loop loudly
  } else { throw e; }
}

Prevention

When it happens

Trigger: The bundled webview template file changed shape — a build/minification step emits <head ...attrs> or uppercase <HEAD>, a framework rewrites the template, or the template was hand-edited and the '<head>' anchor was removed or renamed.

Common situations: Upgrading a bundler/minifier that lowercases-transforms or expands the head tag (e.g. <head profile="">); switching the template to a different framework boilerplate; a manual template edit dropping or renaming the head element; string minification altering the literal.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of ramensoftware/windhawk@61d99ed8e1 (2026-09-12). Data as JSON: /api/errors/6cc9e9b1e489e1ec. Report an issue: GitHub.

Appendix: source

Thrown at src/windhawk-vscode/src/extension.ts:2184

	const nonce = crypto.randomBytes(16).toString('hex');

	const cspRulesWithNonce = cspRules.map(rule =>
		rule.startsWith('script-src ') ? rule + ` 'nonce-${nonce}'` : rule
	);

	const webviewPathOnDisk = baseDebugReactUiPath
		? vscode.Uri.file(baseDebugReactUiPath)
		: vscode.Uri.joinPath(extensionUri, 'webview');

	const baseWebviewUri = webview.asWebviewUri(webviewPathOnDisk);
	let html = fs.readFileSync(vscode.Uri.joinPath(webviewPathOnDisk, 'index.html').fsPath, 'utf8');

	// The base href, the CSP and the body markers are injected by patching the
	// bundled template. A template change which drops either anchor tag has to
	// fail here, not silently produce a webview without a CSP.
	const headTag = '<head>';
	if (!html.includes(headTag)) {
		throw new Error('The webview template has no <head> tag to inject the Content-Security-Policy into');
	}

	// The replacements go through replacer functions, not replacement strings,
	// so that a `$` sequence in an injected value is inserted verbatim instead
	// of being expanded as a replacement pattern.
	html = html.replace(headTag, () => `<head>
		<base href="${baseWebviewUri.toString()}/">
		<meta http-equiv="Content-Security-Policy" content="${cspRulesWithNonce.join('; ')};">
		<script nonce="${nonce}">(() => {
			let lastFocused = null;
			document.addEventListener('focusin', (e) => { lastFocused = e.target; });
			document.addEventListener('focusout', () => {
				setTimeout(() => {
					if (document.hasFocus() && (!document.activeElement || document.activeElement === document.body)) {
						lastFocused = null;
					}
				}, 0);
			});

View on GitHub (pinned to 61d99ed8e1)