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
- Provide a non-empty prefix with exactly one trailing '*', e.g. --cancellationPipeName "\\\\.\\pipe\\ts-cancellation-*" so the per-request pipe becomes <prefix><request_seq>.
- 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.
- Validate the argument before launching tsserver: length > 1, exactly one '*' and it must be the last character, and the remainder non-empty.
- 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
- Treat --cancellationPipeName as structured input: a non-empty base plus at most one trailing '*'.
- Prefer a fully-qualified pipe path (e.g. \\\\.\\pipe\\<name>[-*]) to avoid empty prefixes.
- Drop the trailing '*' (or the whole flag) if per-request cancellation is not required.
- Add an integration test that asserts the spawned tsserver starts cleanly with your cancellation args.
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
- Request: ${request.command} not allowed in LanguageServiceMo
- Request: ${request.command} not allowed in LanguageServiceMo
- No Project.
- The project's language service is disabled.
- Project '${project.getProjectName()}' does not contain docum
AI-assisted analysis of microsoft/TypeScript@b465fdbfe1 (2026-08-12).
Data as JSON: /api/errors/26143814e532db9c.
Report an issue: GitHub.