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
- Inspect the bundled webview template and restore a literal '<head>' opening tag.
- If the build step rewrites the tag, fix the bundler/minifier config to preserve the plain <head> literal.
- Update the injection code to match the new template anchor if the template intentionally changed (e.g. match /<head[^>]*>/i).
- 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
- Add a unit test asserting the bundled template contains the literal '<head>' anchor.
- Lock bundler/minifier settings that rewrite HTML tag shapes (attributes, casing) on the template.
- Treat the webview template as a contract: document the required anchors (<head>, body markers).
- Run the dev build once after any tooling upgrade to confirm CSP injection still succeeds.
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)