From 10949d0cd85f8affca394910a30de1480ff052ea Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Mon, 19 Oct 2026 10:04:10 -0400 Subject: [PATCH 1/2] fix(@angular/cli): only load autocompletion in interactive shells Previously, `ng completion` appended an unconditional `source <(ng completion script)` statement to the user's shell configuration file. When non-interactive shells (such as those spawned by scripts, automation tools, or coding agents) sourced the configuration file, they still paid the overhead of starting Node.js and generating the completion script despite tab completion not being used. This change wraps the `source <(ng completion script)` command in an `if [[ $- == *i* ]]; then` check so that autocompletion is only loaded when the shell is interactive, and updates the manual setup documentation accordingly. Closes #34307 --- .../commands/completion/long-description.md | 4 +- .../angular/cli/src/utilities/completion.ts | 2 +- .../tests/commands/completion/completion.ts | 67 +++++-------------- 3 files changed, 21 insertions(+), 52 deletions(-) diff --git a/packages/angular/cli/src/commands/completion/long-description.md b/packages/angular/cli/src/commands/completion/long-description.md index b75803ac9cb0..ac0f7ce8aa8f 100644 --- a/packages/angular/cli/src/commands/completion/long-description.md +++ b/packages/angular/cli/src/commands/completion/long-description.md @@ -42,7 +42,9 @@ more control over exactly where this configuration lives, you can manually set i shell run at startup: ```bash -source <(ng completion script) +if [[ $- == *i* ]]; then + source <(ng completion script) +fi ``` This is equivalent to what `ng completion` will automatically set up, and gives power users more diff --git a/packages/angular/cli/src/utilities/completion.ts b/packages/angular/cli/src/utilities/completion.ts index 0e23e1f03348..be93fbfcbb4f 100644 --- a/packages/angular/cli/src/utilities/completion.ts +++ b/packages/angular/cli/src/utilities/completion.ts @@ -240,7 +240,7 @@ export async function initializeAutocomplete(): Promise { try { await fs.appendFile( rcFile, - '\n\n# Load Angular CLI autocompletion.\nsource <(ng completion script)\n', + '\n\n# Load Angular CLI autocompletion.\nif [[ $- == *i* ]]; then\n source <(ng completion script)\nfi\n', ); } catch (err) { assertIsError(err); diff --git a/tests/e2e/tests/commands/completion/completion.ts b/tests/e2e/tests/commands/completion/completion.ts index 939bdb8c306b..2db5885fad48 100644 --- a/tests/e2e/tests/commands/completion/completion.ts +++ b/tests/e2e/tests/commands/completion/completion.ts @@ -11,6 +11,13 @@ import { const testRegistry = getGlobalVariable('package-registry'); +const AUTOCOMPLETION_SNIPPET = ` +# Load Angular CLI autocompletion. +if [[ $- == *i* ]]; then + source <(ng completion script) +fi +`.trim(); + export default async function () { // Windows Cmd and Powershell do not support autocompletion. Run a different set of tests to // confirm autocompletion fails gracefully. @@ -33,11 +40,7 @@ export default async function () { ); const rcContents = await fs.readFile(path.join(home, '.bashrc'), 'utf-8'); - const expected = ` -# Load Angular CLI autocompletion. -source <(ng completion script) - `.trim(); - if (!rcContents.includes(expected)) { + if (!rcContents.includes(AUTOCOMPLETION_SNIPPET)) { throw new Error(`~/.bashrc does not contain autocompletion script. Contents:\n${rcContents}`); } }); @@ -55,11 +58,7 @@ source <(ng completion script) ); const rcContents = await fs.readFile(path.join(home, '.zshrc'), 'utf-8'); - const expected = ` -# Load Angular CLI autocompletion. -source <(ng completion script) - `.trim(); - if (!rcContents.includes(expected)) { + if (!rcContents.includes(AUTOCOMPLETION_SNIPPET)) { throw new Error(`~/.zshrc does not contain autocompletion script. Contents:\n${rcContents}`); } }); @@ -79,11 +78,7 @@ source <(ng completion script) ); const rcContents = await fs.readFile(bashrc, 'utf-8'); - const expected = `# Other commands... - -# Load Angular CLI autocompletion. -source <(ng completion script) -`; + const expected = `# Other commands...\n\n${AUTOCOMPLETION_SNIPPET}\n`; if (rcContents !== expected) { throw new Error(`~/.bashrc does not match expectation. Contents:\n${rcContents}`); } @@ -104,11 +99,7 @@ source <(ng completion script) ); const rcContents = await fs.readFile(bashProfile, 'utf-8'); - const expected = `# Other commands... - -# Load Angular CLI autocompletion. -source <(ng completion script) -`; + const expected = `# Other commands...\n\n${AUTOCOMPLETION_SNIPPET}\n`; if (rcContents !== expected) { throw new Error(`~/.bash_profile does not match expectation. Contents:\n${rcContents}`); } @@ -129,11 +120,7 @@ source <(ng completion script) ); const rcContents = await fs.readFile(profile, 'utf-8'); - const expected = `# Other commands... - -# Load Angular CLI autocompletion. -source <(ng completion script) -`; + const expected = `# Other commands...\n\n${AUTOCOMPLETION_SNIPPET}\n`; if (rcContents !== expected) { throw new Error(`~/.profile does not match expectation. Contents:\n${rcContents}`); } @@ -159,11 +146,7 @@ source <(ng completion script) ); const bashrcContents = await fs.readFile(bashrc, 'utf-8'); - const bashrcExpected = `# \`.bashrc\` commands... - -# Load Angular CLI autocompletion. -source <(ng completion script) -`; + const bashrcExpected = `# \`.bashrc\` commands...\n\n${AUTOCOMPLETION_SNIPPET}\n`; if (bashrcContents !== bashrcExpected) { throw new Error(`~/.bashrc does not match expectation. Contents:\n${bashrcContents}`); } @@ -194,11 +177,7 @@ source <(ng completion script) ); const rcContents = await fs.readFile(zshrc, 'utf-8'); - const expected = `# Other commands... - -# Load Angular CLI autocompletion. -source <(ng completion script) -`; + const expected = `# Other commands...\n\n${AUTOCOMPLETION_SNIPPET}\n`; if (rcContents !== expected) { throw new Error(`~/.zshrc does not match expectation. Contents:\n${rcContents}`); } @@ -219,11 +198,7 @@ source <(ng completion script) ); const rcContents = await fs.readFile(zshProfile, 'utf-8'); - const expected = `# Other commands... - -# Load Angular CLI autocompletion. -source <(ng completion script) -`; + const expected = `# Other commands...\n\n${AUTOCOMPLETION_SNIPPET}\n`; if (rcContents !== expected) { throw new Error(`~/.zsh_profile does not match expectation. Contents:\n${rcContents}`); } @@ -244,11 +219,7 @@ source <(ng completion script) ); const rcContents = await fs.readFile(profile, 'utf-8'); - const expected = `# Other commands... - -# Load Angular CLI autocompletion. -source <(ng completion script) -`; + const expected = `# Other commands...\n\n${AUTOCOMPLETION_SNIPPET}\n`; if (rcContents !== expected) { throw new Error(`~/.profile does not match expectation. Contents:\n${rcContents}`); } @@ -274,11 +245,7 @@ source <(ng completion script) ); const zshrcContents = await fs.readFile(zshrc, 'utf-8'); - const zshrcExpected = `# \`.zshrc\` commands... - -# Load Angular CLI autocompletion. -source <(ng completion script) -`; + const zshrcExpected = `# \`.zshrc\` commands...\n\n${AUTOCOMPLETION_SNIPPET}\n`; if (zshrcContents !== zshrcExpected) { throw new Error(`~/.zshrc does not match expectation. Contents:\n${zshrcContents}`); } From 521fb0e3ff9ea2d870816b51433fcc9f37800177 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Mon, 19 Oct 2026 19:56:58 -0400 Subject: [PATCH 2/2] fixup! fix(@angular/cli): only load autocompletion in interactive shells --- packages/angular/cli/src/utilities/completion.ts | 2 +- tests/e2e/tests/commands/completion/completion.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/angular/cli/src/utilities/completion.ts b/packages/angular/cli/src/utilities/completion.ts index be93fbfcbb4f..052231ea964e 100644 --- a/packages/angular/cli/src/utilities/completion.ts +++ b/packages/angular/cli/src/utilities/completion.ts @@ -240,7 +240,7 @@ export async function initializeAutocomplete(): Promise { try { await fs.appendFile( rcFile, - '\n\n# Load Angular CLI autocompletion.\nif [[ $- == *i* ]]; then\n source <(ng completion script)\nfi\n', + '\n\n# Load Angular CLI autocompletion for interactive shells.\nif [[ $- == *i* ]]; then\n source <(ng completion script)\nfi\n', ); } catch (err) { assertIsError(err); diff --git a/tests/e2e/tests/commands/completion/completion.ts b/tests/e2e/tests/commands/completion/completion.ts index 2db5885fad48..5b81490c8400 100644 --- a/tests/e2e/tests/commands/completion/completion.ts +++ b/tests/e2e/tests/commands/completion/completion.ts @@ -12,7 +12,7 @@ import { const testRegistry = getGlobalVariable('package-registry'); const AUTOCOMPLETION_SNIPPET = ` -# Load Angular CLI autocompletion. +# Load Angular CLI autocompletion for interactive shells. if [[ $- == *i* ]]; then source <(ng completion script) fi