Skip to content

fix(cli): version and updater checks#155

Open
coryrylan wants to merge 1 commit into
mainfrom
topic-cli-version-updater
Open

fix(cli): version and updater checks#155
coryrylan wants to merge 1 commit into
mainfrom
topic-cli-version-updater

Conversation

@coryrylan

@coryrylan coryrylan commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator
  • Added support for versioning output files in the build process, ensuring that the correct version is written to bundled files.
  • Introduced a new utility function to replace bundled constants in the output files, improving maintainability.
  • Updated the installation script to utilize the embedded build SHA and version, enhancing the manifest generation.
  • Refactored error handling in the update check to ensure proper feedback when the SHA is missing.

Summary by CodeRabbit

  • New Features
    • Manifest generation now includes both build SHA and version, plus platform checksums derived from bundled artifacts.
  • Bug Fixes
    • Update detection is now strictly SHA-based; malformed or missing manifest data fails early with clearer messaging.
    • Global skill install/update skips prompts when the target file already exists (including CI) and reports whether it was installed vs updated.
    • CLI tool completion and exit behavior is unified, including more reliable upgrade behavior with piped command failures propagating.
  • Chores
    • Streamlined environment/build settings across the toolchain.

@coryrylan coryrylan self-assigned this Jun 26, 2026
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR centralizes Elements environment injection, updates CLI build metadata generation, refactors CLI exit handling, tightens update checks, and changes global skill installation to overwrite existing files without prompting.

Changes

Release and CLI flow

Layer / File(s) Summary
Shared env constants
projects/internals/vite/src/configs/{env.js,build.js,build.node.js,test.js,test.node.js}
getElementsEnvValues() and getElementsEnv() replace inline Elements env literals in the shared Vite configs.
Build version and manifest
projects/cli/vite.config.ts, projects/cli/scripts/write-install-manifest.mjs, projects/cli/src/install.test.ts
The CLI build config falls back to package.json version data, and the install-manifest script reads bundled BUILD_SHA and VERSION values to write sha, version, and platforms.
Injected URL consumers
projects/internals/tools/src/{api/utils.ts,playground/utils.ts,playground/utils.test.ts}, projects/site/src/_11ty/utils/env.js
Registry, ESM CDN, and site env consumers now read build-provided globals, and the playground import map test expects an exact @nvidia-elements/core URL.
Upgrade command URLs
projects/internals/tools/src/cli/{utils.ts,utils.test.ts}
upgradeCommands now run the macOS/Linux installer pipeline under bash -o pipefail -c, and the CLI utils tests check the wrapper and public pages host URLs.
Tool execution helpers
projects/cli/src/{utils.ts,utils.test.ts}
runAsyncTool accepts interactiveProgress, spinner cleanup moves into finally, and the new exit helpers format results, compute exit codes, and terminate the process.
CLI entrypoint completion
projects/cli/src/index.ts
The CLI entrypoint uses the shared exit helpers for upgrade and completed tool runs, and disables interactive progress for the upgrade path.
Update manifest checks
projects/cli/src/{update.ts,update.test.ts}
fetchLatestSha validates manifest sha values, and update tests cover sha-driven availability checks.
Global Elements skill install
projects/cli/src/{install.ts,install.test.ts}
installGlobalElementsSkill distinguishes missing-path warnings from existing-file updates, skips prompting when the skill file already exists, and logs installed versus updated outcomes.

Sequence Diagram(s)

Build manifest flow

sequenceDiagram
  participant WriteInstallManifest
  participant DistIndex
  participant DistManifest
  WriteInstallManifest->>DistIndex: read bundled BUILD_SHA and VERSION
  WriteInstallManifest->>DistManifest: write { sha, version, platforms }
Loading

CLI completion flow

sequenceDiagram
  participant CLIEntrypoint
  participant runAsyncTool
  participant exitWithCompleteToolResult
  participant notifyUpdate
  participant ProcessExit
  CLIEntrypoint->>runAsyncTool: upgrade command with interactiveProgress: false
  runAsyncTool-->>CLIEntrypoint: result
  CLIEntrypoint->>exitWithCompleteToolResult: result, tool, start, end, notifyUpdate
  exitWithCompleteToolResult->>notifyUpdate: notifyUpdate() when exitCode is 0
  exitWithCompleteToolResult->>ProcessExit: exit(exitCode)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Suggested reviewers

  • jareddlc
  • johnyanarella

Possibly related PRs

  • NVIDIA/elements#131 — It also touches installer manifest generation and the dist/manifest.json contract used by CLI installers.
  • NVIDIA/elements#152 — It targets the same site env test file that is removed in this PR.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main changes around versioning and update-check behavior.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch topic-cli-version-updater

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 OpenGrep (1.23.0)
projects/site/src/_11ty/utils/env.js

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[00.13][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist

projects/internals/vite/src/configs/test.node.js

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[00.25][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist

projects/internals/vite/src/configs/test.js

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[00.21][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

projects/cli/src/install.test.ts

Parsing error: error TS5012: Cannot read file '/tsconfig.json': ENOENT: no such file or directory, open '/tsconfig.json'.

projects/cli/src/update.test.ts

Parsing error: error TS5012: Cannot read file '/tsconfig.json': ENOENT: no such file or directory, open '/tsconfig.json'.

projects/cli/src/utils.test.ts

Parsing error: error TS5012: Cannot read file '/tsconfig.json': ENOENT: no such file or directory, open '/tsconfig.json'.

  • 3 others

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
projects/cli/src/utils.ts (1)

145-150: 🎯 Functional Correctness | 🟠 Major

Tighten isReport to validate report entries.
isReport() accepts any truthy status/message, so malformed data can be narrowed to Report and exitCodeForResult() can return success. Require each entry to be an object, message to be a string, and status to match the supported report statuses.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@projects/cli/src/utils.ts` around lines 145 - 150, The isReport guard is too
permissive and can incorrectly narrow malformed data to Report. Update isReport
to validate each entry in the object is itself an object, ensure message is a
string, and verify status matches the supported report statuses instead of just
being truthy. Keep the check localized in isReport so exitCodeForResult
continues to rely on a correctly validated Report shape.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@projects/cli/src/install.test.ts`:
- Around line 760-782: In the install test that calls spawnSync for
writeInstallManifest, assert the subprocess result succeeded before reading and
parsing dist/manifest.json. Move the check on result.status (and include
result.stderr in the failure path if needed) ahead of the readFile/JSON.parse
step in the test using createInstallerContext and writeInstallManifest, so
failures surface the actual exit error instead of a file-not-found.

In `@projects/cli/src/utils.ts`:
- Line 185: The formatted result in the rendering logic is appending the debug
metadata directly after the prior content, so update the formatting in the utils
rendering path to prepend a separator before the debug block. Locate the
concatenation that adds the [debug] metadata in the result-building flow and
ensure it starts on a new line so the output is separated cleanly from the
rendered content.

---

Outside diff comments:
In `@projects/cli/src/utils.ts`:
- Around line 145-150: The isReport guard is too permissive and can incorrectly
narrow malformed data to Report. Update isReport to validate each entry in the
object is itself an object, ensure message is a string, and verify status
matches the supported report statuses instead of just being truthy. Keep the
check localized in isReport so exitCodeForResult continues to rely on a
correctly validated Report shape.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 872843f0-0461-4b73-9063-626b276e6d00

📥 Commits

Reviewing files that changed from the base of the PR and between c58c856 and 9cd318b.

📒 Files selected for processing (20)
  • projects/cli/scripts/write-install-manifest.mjs
  • projects/cli/src/index.ts
  • projects/cli/src/install.test.ts
  • projects/cli/src/install.ts
  • projects/cli/src/update.test.ts
  • projects/cli/src/update.ts
  • projects/cli/src/utils.test.ts
  • projects/cli/src/utils.ts
  • projects/cli/vite.config.ts
  • projects/internals/tools/src/api/utils.ts
  • projects/internals/tools/src/cli/utils.test.ts
  • projects/internals/tools/src/cli/utils.ts
  • projects/internals/tools/src/playground/utils.test.ts
  • projects/internals/tools/src/playground/utils.ts
  • projects/internals/vite/src/configs/build.js
  • projects/internals/vite/src/configs/build.node.js
  • projects/internals/vite/src/configs/env.js
  • projects/internals/vite/src/configs/test.js
  • projects/internals/vite/src/configs/test.node.js
  • projects/site/src/_11ty/utils/env.js

Comment thread projects/cli/src/install.test.ts
Comment thread projects/cli/src/utils.ts
@coryrylan coryrylan force-pushed the topic-cli-version-updater branch from 9cd318b to b9fc8cd Compare June 26, 2026 20:32

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@projects/cli/src/update.test.ts`:
- Around line 58-69: Add a regression test in fetchLatestSha coverage for a
manifest that returns an empty sha string, since the current test only covers a
missing sha shape. Update the existing test suite in update.test.ts alongside
the fetchLatestSha cases to stub fetch with a manifest containing sha: '' and
assert it rejects with the same failure path as the missing-sha case, so the
runtime guard in fetchLatestSha stays enforced for both invalid manifest shapes.

In `@projects/cli/src/update.ts`:
- Around line 24-28: The SHA guard in the manifest-fetching logic is too loose
because the `sha` value can be an empty string and still pass. Update the `sha`
validation in the update flow so the `sha` returned from `res.json()` must be a
non-empty string before returning it, and continue throwing the missing-SHA
error for blank values; this change should be applied in the manifest parsing
path used by `isUpdateAvailable()` so invalid manifests do not get treated as
“no update available.”
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: dc3c67f8-cb01-45f2-b49c-5749b00c7bd5

📥 Commits

Reviewing files that changed from the base of the PR and between 9cd318b and b9fc8cd.

📒 Files selected for processing (20)
  • projects/cli/scripts/write-install-manifest.mjs
  • projects/cli/src/index.ts
  • projects/cli/src/install.test.ts
  • projects/cli/src/install.ts
  • projects/cli/src/update.test.ts
  • projects/cli/src/update.ts
  • projects/cli/src/utils.test.ts
  • projects/cli/src/utils.ts
  • projects/cli/vite.config.ts
  • projects/internals/tools/src/api/utils.ts
  • projects/internals/tools/src/cli/utils.test.ts
  • projects/internals/tools/src/cli/utils.ts
  • projects/internals/tools/src/playground/utils.test.ts
  • projects/internals/tools/src/playground/utils.ts
  • projects/internals/vite/src/configs/build.js
  • projects/internals/vite/src/configs/build.node.js
  • projects/internals/vite/src/configs/env.js
  • projects/internals/vite/src/configs/test.js
  • projects/internals/vite/src/configs/test.node.js
  • projects/site/src/_11ty/utils/env.js

Comment on lines +58 to +69
it('should throw error when manifest is missing sha', async () => {
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ version: '2.1.0' })
})
);

await expect(fetchLatestSha()).rejects.toThrow('Failed to fetch latest build info');
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add a blank-sha regression case.

This covers sha being absent, but '' is another invalid manifest shape that should be pinned down once the runtime guard is tightened. Otherwise the remaining bad-manifest path can regress unnoticed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@projects/cli/src/update.test.ts` around lines 58 - 69, Add a regression test
in fetchLatestSha coverage for a manifest that returns an empty sha string,
since the current test only covers a missing sha shape. Update the existing test
suite in update.test.ts alongside the fetchLatestSha cases to stub fetch with a
manifest containing sha: '' and assert it rejects with the same failure path as
the missing-sha case, so the runtime guard in fetchLatestSha stays enforced for
both invalid manifest shapes.

Comment on lines +24 to +28
const { sha } = (await res.json()) as { sha?: string };
if (typeof sha !== 'string') {
throw new Error('Missing sha');
}
return sha;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject blank manifest SHAs too.

typeof sha === 'string' still accepts '', which means an invalid manifest can pass this guard and then get treated downstream as "no update available" because isUpdateAvailable() only reports when latestSha !== ''. That leaves the missing-SHA failure mode only partially covered.

Proposed fix
-    if (typeof sha !== 'string') {
+    if (typeof sha !== 'string' || sha.trim() === '') {
       throw new Error('Missing sha');
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { sha } = (await res.json()) as { sha?: string };
if (typeof sha !== 'string') {
throw new Error('Missing sha');
}
return sha;
const { sha } = (await res.json()) as { sha?: string };
if (typeof sha !== 'string' || sha.trim() === '') {
throw new Error('Missing sha');
}
return sha;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@projects/cli/src/update.ts` around lines 24 - 28, The SHA guard in the
manifest-fetching logic is too loose because the `sha` value can be an empty
string and still pass. Update the `sha` validation in the update flow so the
`sha` returned from `res.json()` must be a non-empty string before returning it,
and continue throwing the missing-SHA error for blank values; this change should
be applied in the manifest parsing path used by `isUpdateAvailable()` so invalid
manifests do not get treated as “no update available.”

- Added support for versioning output files in the build process, ensuring that the correct version is written to bundled files.
- Introduced a new utility function to replace bundled constants in the output files, improving maintainability.
- Updated the installation script to utilize the embedded build SHA and version, enhancing the manifest generation.
- Refactored error handling in the update check to ensure proper feedback when the SHA is missing.

Signed-off-by: Cory Rylan <crylan@nvidia.com>
@coryrylan coryrylan force-pushed the topic-cli-version-updater branch from b9fc8cd to 76d2980 Compare June 26, 2026 21:08

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
projects/cli/src/utils.ts (1)

185-185: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Missing separator before [debug] block.

Line 185 still appends [debug] directly onto the rendered result (...}[debug]), with no leading newline, despite the prior fix being marked as addressed. Prefix the debug block with \n.

Suggested fix
-    formattedResult += `[debug]\n[command]: ${tool.metadata.command}`;
+    formattedResult += `\n[debug]\n[command]: ${tool.metadata.command}`;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@projects/cli/src/utils.ts` at line 185, The rendered output in utils.ts still
appends the [debug] block directly after the main result, so update the
formatting in the code that builds formattedResult to prefix the debug section
with a leading newline before [debug]. Use the existing string assembly around
formattedResult and tool.metadata.command to keep the debug block separated from
the prior content.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@projects/cli/scripts/write-install-manifest.mjs`:
- Around line 29-34: The getBundledConstant helper builds a RegExp from the
unescaped name argument, so update that pattern construction to safely escape
any regex metacharacters before interpolation. Keep the lookup logic in
getBundledConstant and preserve the missing-constant error path, but make the
guard/message reflect that this is a no-match case rather than a falsy value
check.

In `@projects/cli/src/install.ts`:
- Around line 398-405: The install flow in installSkill should keep the
CI/non-interactive policy check from shouldInstallGlobalElementsSkill(context)
separate from the existing-file shortcut. Call the policy gate first so CI still
blocks global installation, then use hasExistingSkill only to decide whether to
skip the prompt and whether to avoid overwriting an existing SKILL.md without
confirmation. Preserve the behavior around writeGlobalElementsSkill(skillPath)
and the Installed/Updated log message, but make sure reruns cannot bypass the
gate or silently replace a preexisting file.

---

Duplicate comments:
In `@projects/cli/src/utils.ts`:
- Line 185: The rendered output in utils.ts still appends the [debug] block
directly after the main result, so update the formatting in the code that builds
formattedResult to prefix the debug section with a leading newline before
[debug]. Use the existing string assembly around formattedResult and
tool.metadata.command to keep the debug block separated from the prior content.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 983fe57d-b011-4197-a3cc-586d20e1abac

📥 Commits

Reviewing files that changed from the base of the PR and between b9fc8cd and 76d2980.

📒 Files selected for processing (21)
  • projects/cli/scripts/write-install-manifest.mjs
  • projects/cli/src/index.ts
  • projects/cli/src/install.test.ts
  • projects/cli/src/install.ts
  • projects/cli/src/update.test.ts
  • projects/cli/src/update.ts
  • projects/cli/src/utils.test.ts
  • projects/cli/src/utils.ts
  • projects/cli/vite.config.ts
  • projects/internals/tools/src/api/utils.ts
  • projects/internals/tools/src/cli/utils.test.ts
  • projects/internals/tools/src/cli/utils.ts
  • projects/internals/tools/src/playground/utils.test.ts
  • projects/internals/tools/src/playground/utils.ts
  • projects/internals/vite/src/configs/build.js
  • projects/internals/vite/src/configs/build.node.js
  • projects/internals/vite/src/configs/env.js
  • projects/internals/vite/src/configs/test.js
  • projects/internals/vite/src/configs/test.node.js
  • projects/site/src/_11ty/utils/env.js
  • projects/site/src/_11ty/utils/env.test.ts
💤 Files with no reviewable changes (1)
  • projects/site/src/_11ty/utils/env.test.ts

Comment on lines +29 to +34
function getBundledConstant(content, name) {
const value = content.match(new RegExp(`(?:var|const|let)\\s+${name}\\s*=\\s*["']([^"']+)["']`))?.[1];
if (!value) {
throw new Error(`Missing bundled ${name} constant`);
}
return value;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor: escape name before building the RegExp.

name is interpolated unescaped into the pattern. Today the only callers pass BUILD_SHA/VERSION (regex-safe), but a future caller with regex metacharacters would silently produce a wrong pattern. Also consider that !value rejects a legitimately empty constant — though the [^"']+ group already requires at least one character, so the guard is effectively a missing-match check; a clearer message would help.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@projects/cli/scripts/write-install-manifest.mjs` around lines 29 - 34, The
getBundledConstant helper builds a RegExp from the unescaped name argument, so
update that pattern construction to safely escape any regex metacharacters
before interpolation. Keep the lookup logic in getBundledConstant and preserve
the missing-constant error path, but make the guard/message reflect that this is
a no-match case rather than a falsy value check.

Comment on lines +398 to +405
const hasExistingSkill = existsSync(skillPath);
if (!hasExistingSkill && !(await shouldInstallGlobalElementsSkill(context))) {
return;
}

try {
await writeGlobalElementsSkill(skillPath);
context.log(`Installed agent skill at ${skillPath}`);
context.log(`${hasExistingSkill ? 'Updated' : 'Installed'} agent skill at ${skillPath}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Keep the CI/install-policy gate separate from the prompt shortcut.

Lines 398-400 now skip shouldInstallGlobalElementsSkill() whenever SKILL.md already exists, but that helper is also the only place that blocks global installs in CI. As written, a rerun in CI will overwrite an existing ~/.agents/skills/elements/SKILL.md without any prompt, and it will silently replace any preexisting file at that path. Preserve the non-interactive policy check before the hasExistingSkill fast path, then use the existence check only to suppress the prompt.

Suggested fix
 async function installGlobalElementsSkill(context: InstallContext): Promise<void> {
   const skillPath = getGlobalElementsSkillPath(context.env, context.platform);
   if (!skillPath) {
     context.warn('Could not install Elements agent skill. HOME is not set.');
     return;
   }

+  if (context.env.CI) {
+    return;
+  }
+
   const hasExistingSkill = existsSync(skillPath);
   if (!hasExistingSkill && !(await shouldInstallGlobalElementsSkill(context))) {
     return;
   }

   try {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const hasExistingSkill = existsSync(skillPath);
if (!hasExistingSkill && !(await shouldInstallGlobalElementsSkill(context))) {
return;
}
try {
await writeGlobalElementsSkill(skillPath);
context.log(`Installed agent skill at ${skillPath}`);
context.log(`${hasExistingSkill ? 'Updated' : 'Installed'} agent skill at ${skillPath}`);
const skillPath = getGlobalElementsSkillPath(context.env, context.platform);
if (!skillPath) {
context.warn('Could not install Elements agent skill. HOME is not set.');
return;
}
if (context.env.CI) {
return;
}
const hasExistingSkill = existsSync(skillPath);
if (!hasExistingSkill && !(await shouldInstallGlobalElementsSkill(context))) {
return;
}
try {
await writeGlobalElementsSkill(skillPath);
context.log(`${hasExistingSkill ? 'Updated' : 'Installed'} agent skill at ${skillPath}`);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@projects/cli/src/install.ts` around lines 398 - 405, The install flow in
installSkill should keep the CI/non-interactive policy check from
shouldInstallGlobalElementsSkill(context) separate from the existing-file
shortcut. Call the policy gate first so CI still blocks global installation,
then use hasExistingSkill only to decide whether to skip the prompt and whether
to avoid overwriting an existing SKILL.md without confirmation. Preserve the
behavior around writeGlobalElementsSkill(skillPath) and the Installed/Updated
log message, but make sure reruns cannot bypass the gate or silently replace a
preexisting file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant