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

  1. Rename the mod id to lowercase alphanumeric with hyphens only (e.g. 'My_Mod' → 'my-mod').
  2. Validate the id before calling modSourcePath/saveModToDrafts, e.g. /^[0-9a-z-]+$/.test(modId).
  3. If the id comes from user input or metadata files, sanitize by lowercasing and replacing invalid characters with hyphens before use.
  4. 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

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


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)