microsoft/TypeScript · error · Error

Global option '${key}' already exists

Error message

Global option '${key}' already exists

What it means

Thrown by parseTestData when a global @option (a // @key: value line outside any @FileName block) is declared more than once. The harness stores global options in a plain object keyed by lower-cased name and refuses to overwrite an existing key. File-metadata keys (those in fileMetadataNames, e.g. @FileName, @symlink) are exempt and handled separately.

Source

Thrown at src/harness/fourslashImpl.ts:4804

        }
        else if (line.substr(0, 3) === "///" && currentFileContent !== undefined) {
            throw new Error("Three-slash line in the middle of four-slash region at line " + i);
        }
        else if (line.substr(0, 2) === "//") {
            const possiblySymlinks = Harness.TestCaseParser.parseSymlinkFromTest(line, symlinks);
            if (possiblySymlinks) {
                symlinks = possiblySymlinks;
            }
            else {
                // Comment line, check for global/file @options and record them
                const match = optionRegex.exec(line.substr(2));
                if (match) {
                    const key = match[1].toLowerCase();
                    const value = match[2];
                    if (!ts.contains(fileMetadataNames, key)) {
                        // Check if the match is already existed in the global options
                        if (globalOptions[key] !== undefined) {
                            throw new Error(`Global option '${key}' already exists`);
                        }
                        globalOptions[key] = value;
                    }
                    else {
                        switch (key) {
                            case MetadataOptionNames.fileName:
                                // Found an @FileName directive, if this is not the first then create a new subfile
                                nextFile();
                                currentFileName = ts.isRootedDiskPath(value) ? value : basePath + "/" + value;
                                currentFileOptions[key] = value;
                                break;
                            case MetadataOptionNames.symlink:
                                currentFileSymlinks = ts.append(currentFileSymlinks, value);
                                break;
                            default:
                                // Add other fileMetadata flag
                                currentFileOptions[key] = value;
                        }

View on GitHub (pinned to b465fdbfe1)

Solutions

  1. Search the test file for repeated @key lines (case-insensitive) and keep only one.
  2. If you intended per-file settings, move the option inside the relevant @FileName block so it is treated as file metadata rather than a global option.
  3. Rename one of the options if they genuinely represent different settings.

Example fix

// before
// @module: commonjs
// @module: esnext
// after
// @module: esnext
Defensive patterns

Strategy: validation

Validate before calling

function assertUniqueGlobalOptions(lines: string[]) {
    const seen = new Set<string>();
    for (const line of lines) {
        const m = /^\/\/\s*@([\w-]+):/.exec(line);
        if (m && !seen.has(m[1].toLowerCase())) seen.add(m[1].toLowerCase());
        else if (m) throw new Error(`Duplicate global option '${m[1]}'`);
    }
}

Prevention

When it happens

Trigger: A fourslash test file contains two or more // @compilerOption: ... lines with the same key outside of any @FileName directive, so globalOptions[key] is already defined when the second is parsed.

Common situations: Merging two test files duplicates a global setting; an author sets the same option twice expecting the later one to win. Configuration keys are case-insensitive so a different casing still collides.

Related errors


AI-assisted analysis of microsoft/TypeScript@b465fdbfe1 (2026-08-12). Data as JSON: /api/errors/33ade2be36edd411. Report an issue: GitHub.