microsoft/TypeScript · error · Error

Invalid name for template cancellation pipe: it should have

Error message

Invalid name for template cancellation pipe: it should have length greater than 2 characters and contain only one '*'.

What it means

Thrown while parsing the --cancellationPipeName command-line argument in tsserver/nodeServer's createCancellationToken. When the supplied name ends with '*', tsserver treats it as a per-request template: the prefix (everything before the final '*') is concatenated with each request sequence to form per-request named-pipe names. The prefix must be non-empty and must not itself contain another '*'. This guards against degenerate/malformed inputs like "*", "**", or "a*b*" that would produce empty or ambiguous pipe names. Note the message text says 'length greater than 2 characters', but the code actually enforces only namePrefix.length > 0 and no embedded '*'.

Source

Thrown at src/tsserver/nodeServer.ts:695

    let cancellationPipeName: string | undefined;
    for (let i = 0; i < args.length - 1; i++) {
        if (args[i] === "--cancellationPipeName") {
            cancellationPipeName = args[i + 1];
            break;
        }
    }
    if (!cancellationPipeName) {
        return ts.server.nullCancellationToken;
    }
    // cancellationPipeName is a string without '*' inside that can optionally end with '*'
    // when client wants to signal cancellation it should create a named pipe with name=<cancellationPipeName>
    // server will synchronously check the presence of the pipe and treat its existence as indicator that current request should be canceled.
    // in case if client prefers to use more fine-grained schema than one name for all request it can add '*' to the end of cancellationPipeName.
    // in this case pipe name will be build dynamically as <cancellationPipeName><request_seq>.
    if (cancellationPipeName.charAt(cancellationPipeName.length - 1) === "*") {
        const namePrefix = cancellationPipeName.slice(0, -1);
        if (namePrefix.length === 0 || namePrefix.includes("*")) {
            throw new Error("Invalid name for template cancellation pipe: it should have length greater than 2 characters and contain only one '*'.");
        }
        let perRequestPipeName: string | undefined;
        let currentRequestId: number;
        return {
            isCancellationRequested: () => perRequestPipeName !== undefined && pipeExists(perRequestPipeName),
            setRequest(requestId: number) {
                currentRequestId = requestId;
                perRequestPipeName = namePrefix + requestId;
            },
            resetRequest(requestId: number) {
                if (currentRequestId !== requestId) {
                    throw new Error(`Mismatched request id, expected ${currentRequestId}, actual ${requestId}`);
                }
                perRequestPipeName = undefined;
            },
        };
    }
    else {

View on GitHub (pinned to b465fdbfe1)

Solutions

  1. Provide a non-empty prefix with exactly one trailing '*', e.g. --cancellationPipeName "\\\\.\\pipe\\ts-cancellation-*" so the per-request pipe becomes <prefix><request_seq>.
  2. If you do not need per-request cancellation, drop the trailing '*' entirely and pass a plain pipe name; tsserver will use one shared pipe for all requests.
  3. Validate the argument before launching tsserver: length > 1, exactly one '*' and it must be the last character, and the remainder non-empty.
  4. Remove the --cancellationPipeName flag to disable pipe-based cancellation entirely (tsserver falls back to ts.server.nullCancellationToken).

Example fix

// before
// --cancellationPipeName "*"   // throws: empty prefix

// after (per-request template)
// --cancellationPipeName "\\\\.\\pipe\\vtl-cancellation-*"

// after (single shared pipe, no template)
// --cancellationPipeName "\\\\.\\pipe\\vtl-cancellation"
Defensive patterns

Strategy: validation

Validate before calling

// Validate the --cancellationPipeName value before spawning tsserver.
function isValidCancellationPipeName(name: string): boolean {
  if (!name.endsWith("*")) return true;                 // fixed pipe name is fine
  const prefix = name.slice(0, -1);
  return prefix.length > 0 && !prefix.includes("*");    // non-empty, no stray '*'
}
if (!isValidCancellationPipeName(cancellationPipeName)) {
  throw new Error(`Bad --cancellationPipeName: ${cancellationPipeName}`);
}

Type guard

function isTemplateCancellationPipeName(name: string): boolean {
  return name.endsWith("*");
}
function templatePrefix(name: string): string | null {
  if (!isTemplateCancellationPipeName(name)) return null;
  const prefix = name.slice(0, -1);
  return prefix.length > 0 && !prefix.includes("*") ? prefix : null;
}

Try / catch

// This throws during tsserver startup (before requests), so a runtime catch inside
// the client is not useful. Validate the argument client-side before launching.

Prevention

When it happens

Trigger: Launching tsserver/nodeServer with `--cancellationPipeName "*"` (prefix empty), `--cancellationPipeName "**"` (prefix is '*' which includes '*'), or any value whose trailing '*' is preceded by another '*' or nothing. The check fires at server startup inside createCancellationToken, before any request is served.

Common situations: Editor/IDE integrations (VS Code, Vim/CoC, Emacs lsp-mode, custom LSP clients) that pass --cancellationPipeName with a templated suffix and mis-build the value — e.g. appending '*' to an empty base, double-escaping the '*', or quoting the arg incorrectly so only '*' reaches the arg parser. Copy-paste errors between configs that previously used a fixed (non-templated) pipe name.

Related errors


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