Skip to content

fix(install): verify release assets exist before trusting advertised version; test isolation fixes#404

Merged
rhuanbarreto merged 10 commits into
mainfrom
fix/vscode-settings-test-pollution
Jun 10, 2026
Merged

fix(install): verify release assets exist before trusting advertised version; test isolation fixes#404
rhuanbarreto merged 10 commits into
mainfrom
fix/vscode-settings-test-pollution

Conversation

@rhuanbarreto

@rhuanbarreto rhuanbarreto commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Two areas of work bundled on this branch:

1. Installer hardening (v0.44 incident follow-up)

docs/public/version.json is deployed by Cloudflare when the release PR merges to main - before release.yml creates the GitHub release and ~10-15 min before release-binaries.yml uploads platform assets. During the v0.44 incident the release job failed after the merge, so the endpoint advertised a version with no installable assets and both install scripts failed with a download 404.

install.sh and install.ps1 now:

  • Verify the platform asset exists (HEAD request) before trusting any advertised version (version.json or GitHub API)
  • Fall back gracefully: warn and walk the 10 most recent releases (newest first), installing the newest one whose asset actually exists - releases/latest alone is not enough, since a release is published before its assets finish uploading
  • Fail fast on pinned versions: ARCHGATE_VERSION with missing assets now produces a clear error pointing at the releases page
  • Strip CR from jq output in install.sh: jq on Windows Git Bash emits CRLF, which left a carriage return on every parsed tag and silently broke the release walk

Also fixes the ARCH-010 prefer-bun-json warning in src/helpers/plugin-install.ts (last remaining check warning).

2. Test isolation fixes (earlier commits on this branch)

Stops tests from touching real user-scope directories and replaces first-party mock.module usage with spyOn (vscode-settings, plugin-install, session-context, init-project).

Testing

  • bun run validate passes (1262 tests, 39/39 ADR rules, 0 warnings)
  • install.sh: POSIX syntax check, live resolution test, simulated missing-asset fallback (resolves next installable release), pinned-bogus-version error path, full e2e install into a temp dir
  • install.ps1: Parser::ParseFile clean, ASCII-only verified (ARCH-021), same resolution scenarios exercised in isolation

Related

Summary by CodeRabbit

  • Bug Fixes

    • Improved installer reliability by validating platform-specific assets exist before downloading
    • Enhanced version resolution with automatic fallback to newer releases when advertised versions lack available assets
    • Better error handling and reporting for missing installation assets
  • Documentation

    • Updated guidance with new troubleshooting patterns and best practices

The v0.44.0 release failed on an order-dependent test flake:

- init-project.test.ts ("vscode returns auto-installed") exercised the
  real configureVscodeSettings with mocked credentials, writing the
  marketplace URL into the runner's REAL ~/.config/Code/User/settings.json
  (and developers' real VS Code settings on local runs).
- vscode-settings.test.ts overrode HOME per-test, but Bun caches
  os.homedir() on Linux, so the override was silently ignored and the
  tests read/wrote the real (now polluted) settings file.
- Bun's test-file execution order is non-deterministic across runs, so
  the failure only appeared when init-project.test.ts happened to run
  first - the same tree passed CI on the release PR and failed on main
  13 minutes later.

Fixes (test-only, no production changes):

- init-project.test.ts: spy out configureVscodeSettings so initProject
  never writes user-scope state.
- vscode-settings.test.ts: replace HOME env overrides with
  spyOn(os, "homedir").mockReturnValue(tempDir) - verified the spy
  reaches named imports in other modules. Keep the APPDATA env override
  for the Windows branch, which reads Bun.env at call time.
- vscode-settings.test.ts: restore saved env by deleting originally
  unset keys instead of Object.assign stringifying undefined.
- ARCH-005: capture both rules (mock os.homedir(), never write real
  user-scope state in tests).

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
Sweep of all test files overriding HOME/APPDATA/XDG_* envs, auditing
each against what the production code actually reads:

- env overrides are SAFE for consumers of paths.ts resolvers
  (archgateHomeDir, opencodeConfigDir, opencodeDbPath, cursorUserDir,
  copilotSessionStateDir) and upgrade.ts/install-info.ts - all read
  Bun.env.HOME / USERPROFILE / XDG_* at call time. No changes needed
  for clean, upgrade, auth, binary-upgrade, credential-store, sentry,
  telemetry*, update-check, opencode-settings, plugin-install-cleanup,
  session-context-opencode tests.

- session-context.test.ts / session-context-cursor.test.ts worked
  around Bun's homedir() caching by writing into the REAL
  ~/.claude/projects and ~/.cursor/projects with unique names. Now
  spy os.homedir() into a mkdtemp tempHome instead (readClaudeCode-
  Session/readCursorSession call homedir() at call time).

- session-context-copilot.test.ts created real ~/.copilot/session-state
  and tracked created dirs for cleanup. Now overrides Bun.env.HOME to a
  mkdtemp tempHome (copilotSessionStateDir reads Bun.env at call time).

- plugin-install.test.ts was the worst case: installCursorPlugin /
  installOpencodePlugin tests mocked fetch and Bun.spawn but NOT the
  filesystem prep in installEditorPluginBundle - every test run
  created dirs under and DELETED archgate-* agents/skills from the
  developer's real ~/.cursor and ~/.config/opencode. Now redirects
  Bun.env.HOME + XDG_CONFIG_HOME to a mkdtemp tempHome file-wide.

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
…-install tests

mock.module("../../src/helpers/platform") is process-global: it replaces
the whole platform module (only resolveCommand exported, everything else
undefined) for every test file in the run and is not undone by
mock.restore(). ARCH-005 forbids mock.module on first-party modules for
exactly this reason - it is the same order-dependent flake class as the
user-scope pollution fixed in the previous commits.

Convert to the documented pattern: import * as platform + per-test
spyOn(platform, "resolveCommand") installed in beforeEach and restored
via mock.restore() in afterEach. The spy reaches the named import inside
plugin-install.ts through Bun's live bindings.

Full suite passes, confirming no other test file depended on the leaked
global mock.

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
mock.restore() already restores all spyOn spies, but restore it
explicitly for symmetry with spawnSpy and clearer intent.
(CodeRabbit review feedback on PR #401.)

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
…tall-cleanup

CI failed after the previous commit removed plugin-install.test.ts's
mock.module("platform"): plugin-install-cleanup.test.ts ALSO globally
mocked the platform module (and paths). On Linux file order the cleanup
file runs first, its leaked module mock is what plugin-install's new
spyOn wrapped and "restored" to, and platform.test.ts / doctor.test.ts
then saw a bare mock returning undefined instead of the real
resolveCommand. The two competing mock.modules had been masking each
other; removing only one exposed the other.

Convert the last first-party mock.modules to the ARCH-005 patterns:

- platform: per-test spyOn(platform, "resolveCommand") in beforeEach,
  restored in afterEach.
- paths: no mock at all - cursorUserDir(), opencodeConfigDir(), and
  internalPath() read Bun.env.HOME / XDG_CONFIG_HOME at call time, so
  the existing tempDir env override (now including HOME, saved and
  restored per-test) isolates the real resolvers. Drops the
  cursorDirOverride indirection.
- env restore now deletes originally-unset keys instead of assigning
  undefined.

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
…version

The static version endpoint (cli.archgate.dev/version.json) is deployed by
Cloudflare when the release PR merges to main - before the GitHub release is
created and 10-15 minutes before release-binaries.yml uploads the platform
assets. During the v0.44 incident the release job failed after the merge, so
the endpoint advertised a version with no installable assets and both install
scripts failed.

Harden version resolution in install.sh and install.ps1:

- Verify the platform asset actually exists (HEAD request) before trusting
  any advertised version
- On a miss, warn and fall back to walking the 10 most recent GitHub
  releases (newest first), installing the newest one whose asset exists -
  releases/latest alone is not enough since a release is published before
  its assets finish uploading
- Pinned ARCHGATE_VERSION now fails fast with a clear error when its asset
  is missing instead of dying mid-download
- Strip CR from jq/grep output in install.sh: jq on Windows Git Bash emits
  CRLF, which left a carriage return on every parsed tag and broke the
  release walk

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
…arse

Resolves the ARCH-010 prefer-bun-json warning in mergeCursorHooks.

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
- version.json is deployed before release assets exist (v0.44 incident)
- jq on Windows Git Bash emits CRLF line endings
- review-context misses uncommitted changes when a base ref is detected
  (tracked in #403)

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a74562bf-ba83-4fa8-8330-c978001921bc

📥 Commits

Reviewing files that changed from the base of the PR and between 652c0f9 and 724041b.

📒 Files selected for processing (2)
  • install.ps1
  • tests/helpers/session-context-copilot.test.ts

📝 Walkthrough

Walkthrough

This PR enhances installer robustness by validating that platform-specific release assets exist before download, documents associated patterns and the test isolation policy, tightens Copilot session test coverage to ensure the missing-directory error path is exercised, and simplifies JSON parsing in the plugin install helper.

Changes

Installer robustness and documentation

Layer / File(s) Summary
Architecture guidance and distribution patterns
.claude/agent-memory/archgate-developer/MEMORY.md
Test isolation guidance clarifies that production code must not be altered for testability; new patterns document Windows CRLF handling and archgate review-context gaps; distribution notes explain that installed versions may differ from advertised due to asset verification fallbacks.
Asset validation and version resolution in installers
install.sh, install.ps1
Both installers introduce asset URL construction and GitHub HEAD request verification; version selection now validates pinned and advertised versions against actual platform assets, with fallback to searching recent GitHub releases for the newest downloadable tag.
Copilot session test isolation and coverage
tests/helpers/session-context-copilot.test.ts
The missing-session-state error path test now explicitly removes the directory before invocation and asserts the exact error message, ensuring the code path is reliably exercised.
JSON parsing simplification in plugin install
src/helpers/plugin-install.ts
mergeCursorHooks switches from manual JSON.parse() to Bun.file().json() for cleaner code with identical error handling.

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

  • archgate/cli#401: Both PRs touch tests/helpers/session-context-copilot.test.ts by adjusting how Copilot session-state directories are set up/removed to reliably exercise the "missing directory" error path.

Poem

🐰 A rabbit hops through GitHub releases,
Checking assets exist before fetching,
No broken downloads in the warren!
Tests now mocked for isolation clean,
The CLI grows more robust indeed.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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 PR title accurately summarizes the two main changes: installer asset verification and test isolation fixes.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 fix/vscode-settings-test-pollution

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

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 10, 2026

Copy link
Copy Markdown

Deploying archgate-cli with  Cloudflare Pages  Cloudflare Pages

Latest commit: 724041b
Status: ✅  Deploy successful!
Preview URL: https://52b8a08a.archgate-cli.pages.dev
Branch Preview URL: https://fix-vscode-settings-test-pol.archgate-cli.pages.dev

View logs

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Code Coverage

Metric Value
Lines 90.2% (6793 / 7528)
Threshold 90% minimum — met
Platforms Linux + Windows

Full HTML report available in workflow artifacts.

Per-directory breakdown
Directory Coverage Lines
src/commands/ 88.1% 2078 / 2359
src/engine/ 93.1% 1379 / 1481
src/formats/ 100.0% 141 / 141
src/helpers/ 90.1% 3195 / 3547

@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: 1

Caution

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

⚠️ Outside diff range comments (1)
tests/helpers/session-context-copilot.test.ts (1)

229-236: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Test intent no longer matches setup for missing-directory behavior.

Line 229’s case says “directory does not exist,” but beforeEach (Line 29) always creates stateDir, so this path is never exercised. Remove stateDir inside this test before calling readCopilotSession() to validate the intended branch.

Suggested fix
   test("returns error when session-state directory does not exist", async () => {
+    rmSync(stateDir, { recursive: true, force: true });
     const result = await readCopilotSession(
       "/nonexistent/path/that/wont/match"
     );
-    // This may return "no sessions found for this project" or "no session-state dir"
-    // depending on whether ~/.copilot/session-state/ exists
     expect(result.ok).toBe(false);
+    if (!result.ok) {
+      expect(result.error).toContain("No Copilot CLI session-state directory found");
+    }
   });
🤖 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 `@tests/helpers/session-context-copilot.test.ts` around lines 229 - 236, The
test "returns error when session-state directory does not exist" currently never
exercises the missing-directory branch because beforeEach creates stateDir;
modify the test to remove or unset that directory before calling
readCopilotSession (e.g., delete the stateDir or set stateDir to a non-existent
path) so readCopilotSession("/nonexistent/path/that/wont/match") truly hits the
"no session-state dir" behavior; update the test to reference the same stateDir
variable used in beforeEach and ensure cleanup is performed so subsequent tests
are unaffected.
🤖 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 `@install.ps1`:
- Around line 63-65: The empty catch block in install.ps1 should log the caught
error for debuggability while still falling through to the GitHub API; update
the catch associated with the preceding try (the block that currently just
comments "Fall through to GitHub API") to emit a verbose/debug message (e.g.,
via Write-Verbose or Write-Debug) that includes the exception ($_ or
$_.Exception.Message) and context about what operation failed, then continue to
fall through as before.

---

Outside diff comments:
In `@tests/helpers/session-context-copilot.test.ts`:
- Around line 229-236: The test "returns error when session-state directory does
not exist" currently never exercises the missing-directory branch because
beforeEach creates stateDir; modify the test to remove or unset that directory
before calling readCopilotSession (e.g., delete the stateDir or set stateDir to
a non-existent path) so readCopilotSession("/nonexistent/path/that/wont/match")
truly hits the "no session-state dir" behavior; update the test to reference the
same stateDir variable used in beforeEach and ensure cleanup is performed so
subsequent tests are unaffected.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ad936983-442f-421d-9502-db3c76c93c64

📥 Commits

Reviewing files that changed from the base of the PR and between 996cff8 and 652c0f9.

📒 Files selected for processing (13)
  • .archgate/adrs/ARCH-005-testing-standards.md
  • .claude/agent-memory/archgate-developer/MEMORY.md
  • .claude/agent-memory/archgate-developer/feedback_no_prod_changes_for_tests.md
  • install.ps1
  • install.sh
  • src/helpers/plugin-install.ts
  • tests/helpers/init-project.test.ts
  • tests/helpers/plugin-install-cleanup.test.ts
  • tests/helpers/plugin-install.test.ts
  • tests/helpers/session-context-copilot.test.ts
  • tests/helpers/session-context-cursor.test.ts
  • tests/helpers/session-context.test.ts
  • tests/helpers/vscode-settings.test.ts

Comment thread install.ps1
…n.json fallback

Address CodeRabbit review on PR #404:

- session-context-copilot: beforeEach always created stateDir, so the
  'directory does not exist' test never hit that branch. Remove the dir
  inside the test and assert the specific error message.
- install.ps1: log the version.json lookup failure via Write-Verbose
  instead of swallowing it silently before falling back to the GitHub API.

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@rhuanbarreto

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@rhuanbarreto rhuanbarreto merged commit be5e7d9 into main Jun 10, 2026
27 checks passed
@rhuanbarreto rhuanbarreto deleted the fix/vscode-settings-test-pollution branch June 10, 2026 11:50
@archgatebot archgatebot Bot mentioned this pull request Jun 10, 2026
rhuanbarreto pushed a commit that referenced this pull request Jun 10, 2026
# archgate

## [0.45.1](v0.45.0...v0.45.1)
(2026-06-10)

### Bug Fixes

* **install:** verify release assets exist before trusting advertised
version; test isolation fixes
([#404](#404))
([be5e7d9](be5e7d9))

---
This PR was generated with
[simple-release](https://github.com/TrigenSoftware/simple-release).

<details>
<summary>📄 Cheatsheet</summary>
<br>



You can configure the bot's behavior through a pull request comment
using the `!simple-release/set-options` command.

### Command Format

````md
!simple-release/set-options

```json
{
  "bump": {},
  "publish": {}
}
```
````

### Useful Parameters

#### Bump

| Parameter | Type | Description |
|-----------|------|-------------|
| `version` | `string` | Force set specific version |
| `as` | `'major' \| 'minor' \| 'patch' \| 'prerelease'` | Release type
|
| `prerelease` | `string` | Pre-release identifier (e.g., "alpha",
"beta") |
| `firstRelease` | `boolean` | Whether this is the first release |
| `skip` | `boolean` | Skip version bump |
| `byProject` | `Record<string, object>` | Per-project bump options for
monorepos |

#### Publish

| Parameter | Type | Description |
|-----------|------|-------------|
| `skip` | `boolean` | Skip publishing |
| `access` | `'public' \| 'restricted'` | Package access level |
| `tag` | `string` | Tag for npm publication |

### Usage Examples

#### Force specific version

````md
!simple-release/set-options

```json
{
  "bump": {
    "version": "2.0.0"
  }
}
```
````

#### Force major bump

````md
!simple-release/set-options

```json
{
  "bump": {
    "as": "major"
  }
}
```
````

#### Create alpha pre-release

````md
!simple-release/set-options

```json
{
  "bump": {
    "prerelease": "alpha"
  }
}
```
````

#### Publish with specific access and tag

````md
!simple-release/set-options

```json
{
  "bump": {
    "prerelease": "beta"
  },
  "publish": {
    "access": "public",
    "tag": "beta"
  }
}
```
````

### Access Restrictions

The command can only be used by users with permissions:
- repository owner
- organization member
- collaborator

### Notes

- The last comment with `!simple-release/set-options` command takes
priority
- JSON must be valid, otherwise the command will be ignored
- Parameters apply only to the current release execution
- The command can be updated by editing the comment or adding a new one


</details>

<!--
  Please do not edit this comment.
  simple-release-pull-request: true
  simple-release-branch-from: release
  simple-release-branch-to: main
-->

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant