Skip to content

fix(update): stop reporting an update that never installed - #46

Merged
elkaix merged 5 commits into
mainfrom
fix/windows-update-truthful-install
Aug 9, 2026
Merged

fix(update): stop reporting an update that never installed#46
elkaix merged 5 commits into
mainfrom
fix/windows-update-truthful-install

Conversation

@elkaix

@elkaix elkaix commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Related Issue

No issue — reported directly on Windows. The problem is described below.

Problem

A Windows user on v0.12.0 saw Updating to v0.13.1 / Installing in the background, then
↑ v0.13.1 restart to apply under the prompt. Restarting the terminal still gave v0.12.0, and the
update log recorded the same install as succeeded several times over.

where.exe pythinker put %LOCALAPPDATA%\Programs\Pythinker\pythinker.exe first, and the published
0.13.1 Windows binary is correct (its sha256 matches the channel manifest and it reports 0.13.1), so
the executable that ran was the one the installer targets and the advertised version was real. What
was wrong is that nothing ever checked. Investigating it found four defects:

  • A success is recorded from an exit code alone. The background finalizer writes lastSuccess
    when the installer exits 0. No step asks whether the binary that runs next is the target version,
    so an installer that exits 0 without replacing anything advertises "restart to apply" forever, on
    every launch.
  • doctor crashes on every native install. It reports the package root, and a packaged binary
    ships no package.json, so the command died with
    Error: Could not locate package.json near … — exactly when a user needs it most. The same
    lookup sits on the launch path in install-source detection.
  • npm-family auto-update cannot start on Windows. npm.cmd, pnpm.cmd and yarn.cmd are
    spawned directly, which Node ≥18.20/20.12 refuses (CVE-2024-27980) with EINVAL. The same call
    fails in the npm-prefix lookup, so those installs also classify as unsupported.
  • install.ps1 emits no progress. install.sh writes machine-readable progress: lines on
    stderr and the parent renders them; the PowerShell installer wrote none, so the footer's
    downloading state was unreachable on Windows and an update in flight looked identical to a wedged
    one — the defect fix(update): make the update flow report the truth and stop wedging #38 set out to close, still open on one platform.

What changed

A success now means the new version runs. After an installer exits 0, the version is verified
against the artifact the installer replaced, and a mismatch is recorded as a failure carrying the
reason (… still reports 0.12.0 (expected 0.13.1)) instead of a success. Only native installs are
verified, by probing process.execPath --version: an npm global reinstall rewrites the directory
this process was loaded from, so nothing readable there proves what the next launch runs, and a
wrong answer would park a healthy version.

Verification fails open — a probe that times out (an antivirus scan on a fresh unsigned exe is
the realistic case), cannot run, or prints no version records the success anyway, with a note saying
why it is unproven. doctor prints that note next to the recorded outcome, so the next report of
"it says updated but it did not" is answerable in one command.

Windows package-manager shims run through the command interpreter. cmd.exe /d /s /c npm.cmd …,
spelled out as argv rather than shell: true, so the exact command line is visible in the source and
asserted in tests instead of being assembled by Node's string joining. Same fix in the npm-prefix
lookup that classifies the install source.

install.ps1 speaks the progress protocol, mirroring install.sh: state=waiting while release
assets are not up yet, state=downloading with percent and byte counts (one line per second at
most), state=done, and a single state=failed after the last retry — not between attempts, which
would drop the footer out of its downloading state and back into a failure it is about to recover
from.

doctor survives a native install, reporting the package root only when there is one, and the
launch-path source detection classifies an unresolvable layout as unsupported rather than throwing.
It also now prints the last recorded update success, which is what would have shown the original
problem immediately.

One scope decision worth flagging: the interactive Updated … to X message still prints unchanged
when a native probe could not run. The mismatch case — the actual lie — throws and is reported as a
failure on both foreground paths; the unproven case only loses a line in a flow the user is watching,
and it is recorded in the install state either way.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update. The user-facing docs describe the
    commands, not doctor's runtime lines, and the update behaviour is unchanged when an install
    really works.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed crashes when running diagnostics from native installations.
    • Improved automatic updates for npm, pnpm, and Yarn installations on Windows.
    • Updates are no longer reported as successful when the installed version remains unchanged.
  • New Features

    • Added post-update version verification with clearer failure and unverified status reporting.
    • Diagnostics now show the most recent successful update and its status.
    • Windows installer downloads now display progress, waiting, completion, and failure states.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cfbbb313-2d60-4fa0-9da8-1965c84a5574

📥 Commits

Reviewing files that changed from the base of the PR and between d0f4c81 and 2f69349.

📒 Files selected for processing (4)
  • apps/pythinker-code/src/cli/sub/upgrade.ts
  • apps/pythinker-code/src/cli/update/preflight.ts
  • apps/pythinker-code/src/cli/update/verify-install.ts
  • apps/pythinker-code/test/cli/upgrade.test.ts
📝 Walkthrough

Walkthrough

Changes

Update installation flow

Layer / File(s) Summary
Post-install verification and outcome recording
apps/pythinker-code/src/cli/update/verify-install.ts, apps/pythinker-code/src/cli/update/preflight.ts, apps/pythinker-code/src/cli/update/types.ts, apps/pythinker-code/src/cli/update/install-state.ts, apps/pythinker-code/test/cli/update/verify-install.test.ts, apps/pythinker-code/test/cli/update/preflight.test.ts
Native installations are verified with --version. Mismatches become failures. Probe failures record unverified success details.
Upgrade result propagation
apps/pythinker-code/src/cli/sub/upgrade.ts, apps/pythinker-code/test/cli/upgrade.test.ts
The upgrade flow receives installation verification results and stores unverified success details.
Native installation diagnostics
apps/pythinker-code/src/cli/version.ts, apps/pythinker-code/src/cli/sub/doctor.ts, apps/pythinker-code/src/cli/update/source.ts, apps/pythinker-code/test/cli/doctor.test.ts, apps/pythinker-code/test/cli/update/source.test.ts
Package-root lookup supports native binaries without package metadata. doctor reports available package-root and last-update information.
Windows command execution and installer progress
apps/pythinker-code/src/cli/update/source.ts, apps/pythinker-code/src/cli/update/preflight.ts, apps/pythinker-code/test/cli/update/preflight.test.ts, apps/pythinker-web/public/install.ps1
Windows package-manager shims run through cmd.exe. The installer emits waiting, downloading, done, and failed progress states.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant UpdatePreflight
  participant verifyInstalledVersion
  participant NativeExecutable
  participant InstallState
  UpdatePreflight->>verifyInstalledVersion: verify expected version
  verifyInstalledVersion->>NativeExecutable: run --version
  NativeExecutable-->>verifyInstalledVersion: version output or probe error
  verifyInstalledVersion-->>UpdatePreflight: verification result
  UpdatePreflight->>InstallState: persist outcome
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% 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
Title check ✅ Passed The title uses the valid fix prefix, stays within 72 characters, uses imperative mood, and describes a primary change in the PR.
Description check ✅ Passed The description includes all required sections, explains the problems and changes, and confirms tests, changesets, documentation, and contribution guidance.
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.

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

@pkg-pr-new

pkg-pr-new Bot commented Aug 8, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@pythoughts/pythinker-code@2f69349
npx https://pkg.pr.new/@pythoughts/pythinker-code@2f69349

commit: 2f69349

@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 `@apps/pythinker-code/src/cli/update/preflight.ts`:
- Around line 603-607: Update installUpdate to return the successful
InstallVerification, then have the manual-install success path in
runUpdatePreflight persist verification.unverified alongside lastSuccess.
Preserve the existing failure behavior and add a regression test covering a
successful manual install with an unverified verification result.

In `@apps/pythinker-web/public/install.ps1`:
- Line 498: Update the installation progress flow around Write-MachineProgress
and Print-Done so the archive move continues reporting state=downloading; remove
the premature state=done emission and emit state=done only after Print-Done
succeeds, following checksum validation, extraction, executable replacement, and
PATH configuration.
🪄 Autofix

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: CHILL

Plan: Pro

Run ID: a11cd1f1-b3b7-460f-a6d4-ae38c6eb5f02

📥 Commits

Reviewing files that changed from the base of the PR and between 82951c6 and 832856e.

📒 Files selected for processing (16)
  • .changeset/doctor-native-install.md
  • .changeset/update-verify-before-reporting-success.md
  • .changeset/windows-installer-progress.md
  • .changeset/windows-package-manager-updates.md
  • apps/pythinker-code/src/cli/sub/doctor.ts
  • apps/pythinker-code/src/cli/update/install-state.ts
  • apps/pythinker-code/src/cli/update/preflight.ts
  • apps/pythinker-code/src/cli/update/source.ts
  • apps/pythinker-code/src/cli/update/types.ts
  • apps/pythinker-code/src/cli/update/verify-install.ts
  • apps/pythinker-code/src/cli/version.ts
  • apps/pythinker-code/test/cli/doctor.test.ts
  • apps/pythinker-code/test/cli/update/preflight.test.ts
  • apps/pythinker-code/test/cli/update/source.test.ts
  • apps/pythinker-code/test/cli/update/verify-install.test.ts
  • apps/pythinker-web/public/install.ps1

Comment thread apps/pythinker-code/src/cli/update/preflight.ts
Comment thread apps/pythinker-web/public/install.ps1

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

Caution

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

⚠️ Outside diff range comments (1)
apps/pythinker-code/src/cli/update/preflight.ts (1)

912-918: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Release the lock when verification rejects.

finish awaits verifyInstalledVersion before entering the try/finally that calls lock.release(). If verification rejects, the finalizer exits without releasing the lock and can block updates for up to six hours. Move verification into the protected block or catch all verifier failures.

🤖 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 `@apps/pythinker-code/src/cli/update/preflight.ts` around lines 912 - 918, Move
the verifyInstalledVersion call and related verification handling into the
try/finally protected by lock.release() in finish, or otherwise catch verifier
failures, ensuring every rejection releases the lock while preserving the
existing installed success condition.
🤖 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 `@apps/pythinker-code/src/cli/sub/upgrade.ts`:
- Line 51: Update handleUpgrade to inspect the InstallVerification returned by
UpgradeDeps.installUpdate and route any result with ok false through the
existing failure path using verification.reason, rather than recording success
and returning 0. Preserve the current successful-result behavior; alternatively,
narrow the dependency contract only if failed results are intentionally
impossible.

In `@apps/pythinker-code/test/cli/upgrade.test.ts`:
- Around line 223-231: Update the createDeps fixture in the upgrade test to
provide the existing darwin native manifest so handleUpgrade reaches
installUpdate; retain the unverified result and state-write assertions, and add
an assertion that installUpdate was called to verify the intended execution
path.
- Around line 226-230: Update handleUpgrade to branch on a resolved
InstallVerification result with ok: false, route that mismatch through
lastFailure, and preserve its reason instead of writing lastSuccess or emitting
upgrade_command_succeeded. Add a test in upgrade.test.ts using installUpdate to
return a failed verification and assert the failure state retains the declared
reason.
- Around line 235-239: Update the assertion for lastSuccess.unverified in the
upgrade test to compare against the complete expected fixture string rather than
using expect.stringContaining('ETIMEDOUT'). Preserve the full persisted
manual-install reason, including the executable path and explanatory details, so
the test fails if the recorded message is truncated.

---

Outside diff comments:
In `@apps/pythinker-code/src/cli/update/preflight.ts`:
- Around line 912-918: Move the verifyInstalledVersion call and related
verification handling into the try/finally protected by lock.release() in
finish, or otherwise catch verifier failures, ensuring every rejection releases
the lock while preserving the existing installed success condition.
🪄 Autofix

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: CHILL

Plan: Pro

Run ID: 77d39710-a527-43ea-b374-9cd206f7c5aa

📥 Commits

Reviewing files that changed from the base of the PR and between 832856e and d0f4c81.

📒 Files selected for processing (3)
  • apps/pythinker-code/src/cli/sub/upgrade.ts
  • apps/pythinker-code/src/cli/update/preflight.ts
  • apps/pythinker-code/test/cli/upgrade.test.ts

Comment thread apps/pythinker-code/src/cli/sub/upgrade.ts Outdated
Comment thread apps/pythinker-code/test/cli/upgrade.test.ts
Comment thread apps/pythinker-code/test/cli/upgrade.test.ts Outdated
Comment thread apps/pythinker-code/test/cli/upgrade.test.ts Outdated
@elkaix
elkaix merged commit bceff21 into main Aug 9, 2026
12 checks passed
@elkaix
elkaix deleted the fix/windows-update-truthful-install branch August 9, 2026 00:20
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