ramensoftware/windhawk · error
Mod id must only contain the following characters: 0-9…
Error message
Mod id must only contain the following characters: 0-9, a-z, and a hyphen (-)
What it means
getDraftPath joins a mod id onto the drafts directory path, so an invalid id could escape that folder (e.g. via '../' or illegal filename characters). It validates the id with isValidModId — only digits 0-9, lowercase a-z, and hyphens are allowed — and throws this error when validation fails.
Solutions
- Rename the mod id to lowercase alphanumeric with hyphens only (e.g. 'My_Mod' → 'my-mod').
- Validate the id before calling modSourcePath/saveModToDrafts, e.g. /^[0-9a-z-]+$/.test(modId).
- If the id comes from user input or metadata files, sanitize by lowercasing and replacing invalid characters with hyphens before use.
- For a mod id that legitimately needs other characters, contact the Windhawk project — the format is enforced by design to keep drafts paths safe.
Example fix
// before
const p = workspaceUtils.modSourcePath('My_Mod');
// after
const id = 'My_Mod'.toLowerCase().replace(/[^0-9a-z-]/g, '-');
const p = workspaceUtils.modSourcePath(id); Defensive patterns
Strategy: validation
Validate before calling
const isValidModId = (id) => /^[0-9a-z-]+$/.test(id);
if (!isValidModId(modId)) throw new Error(`Invalid mod id: ${modId}`); Try / catch
try {
const p = utils.modSourcePath(modId);
} catch (e) {
if (e.message.startsWith('Mod id must only contain')) {
showInvalidIdMessage(modId);
}
} Prevention
- Validate mod ids against /^[0-9a-z-]+$/ at every entry point (UI input, metadata parse, CLI arg).
- Normalize external ids: lowercase and replace invalid characters with hyphens before use.
- Never build draft paths from raw user-supplied strings without the id check.
When it happens
Trigger: Calling getDraftPath (via modSourcePath, saveModToDrafts, loadModFromDrafts, etc.) with a mod id containing uppercase letters, underscores, spaces, dots, slashes, or non-ASCII characters — e.g. a mod named 'My_Mod' or 'pkg/submod'.
Common situations: Manually editing a mod's id in metadata to a CamelCase or underscore name; importing/pasting a mod id from another source (Windhawk mods use lowercase-hyphen ids like 'taskbar-clock-customization'); path-traversal strings in adversarial input.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- Initial settings arrays must contain at least one template…
- Invalid object array schema definition.
- Unknown setting type for value
- Mod id must be specified in the source code
- archive is too large
AI-assisted analysis of ramensoftware/windhawk@61d99ed8e1 (2026-09-12).
Data as JSON: /api/errors/e70abe772b626aec.
Report an issue: GitHub.
Appendix: source
Thrown at src/windhawk-vscode/src/utils/editorWorkspaceUtils.ts:113
// Ignore if file doesn't exist.
if (e.code !== 'ENOENT') {
throw e;
}
}
this.initializeEditorSettings(compileFlags);
if (modSourceFromDrafts) {
// Write the new content after initializing, so that git won't stage the draft changes.
fs.writeFileSync(this.getFilePath('mod.wh.cpp'), modSourceFromDrafts);
}
}
// Reject an id that would escape the drafts folder before it reaches any of
// the filesystem calls below.
private getDraftPath(modId: string) {
if (!isValidModId(modId)) {
throw new Error('Mod id must only contain the following characters: 0-9, a-z, and a hyphen (-)');
}
return path.join(this.getDraftsPath(), modId + '.wh.cpp');
}
public saveModToDrafts(modId: string) {
const modSourcePath = this.getDraftPath(modId);
fs.mkdirSync(this.getDraftsPath(), { recursive: true });
fs.copyFileSync(this.getFilePath('mod.wh.cpp'), modSourcePath);
}
public loadModFromDrafts(modId: string) {
const modSourcePath = this.getDraftPath(modId);
if (fs.existsSync(modSourcePath)) {
return fs.readFileSync(modSourcePath, 'utf8');
}
return null;View on GitHub (pinned to 61d99ed8e1)