Skip to content

fix(update): detect a half-replaced install instead of reporting its version - #850

Merged
ffroliva merged 1 commit into
developfrom
bugfix/848-update-detects-broken-install
Sep 16, 2026
Merged

ffroliva merged 1 commit into
developfrom
bugfix/848-update-detects-broken-install

Conversation

@ffroliva

@ffroliva ffroliva commented Sep 16, 2026 •

Copy link
Copy Markdown
Owner

Summary

gflow update could report a version for an install that no longer starts.

A package manager replaces files in place, so an interrupted upgrade leaves a venv whose metadata reads perfectly and whose imports are dead. gflow update only ever re-read the version (importlib.metadata, which reads *.dist-info and never touches a module), so it called that state "exited 1 and gflow-cli is still 0.69.0" and sent the user to a plain uv tool upgrade — which cannot repair it: it reads the same intact metadata, finds it current and changes nothing.

After the manager runs, gflow update now imports gflow-cli in a fresh isolated interpreter before it looks at any version. A failed import is reported as an unusable install (exit 11, ConfigurationError) quoting that interpreter's own last line, and the remediation is a forced reinstall — the command that actually completed in the reported incident.

Verification

Run against a really half-replaced greenlet, not a stand-in — the probe reproduces the issue's own error string verbatim:

healthy probe -> None
broken  probe -> AttributeError: module 'greenlet' has no attribute 'greenlet'
restored probe -> None

Offline gates: 283 passed, 1 skipped on the update-adjacent slice; hygiene, doc-links, website-docs mirror, council-memory, ruff check, ruff format --check, pyright src (0 errors) all green.

No Flow surface is touched, so no e2e test applies and none was written — this is local package management, not a generation path. The load-bearing new logic (the probe) is verified by a real subprocess against a real broken venv, plus 9 unit tests.

Findings that change two of the three suggestions in #848

Suggestion 3 — "detect the no-op" — already exists. update_check.py raises ConfigurationError when the manager exits 0 and the version did not move, and tests/test_update_check.py::test_manager_exit_0_but_version_unchanged_is_configuration_error pins it. The no-op described in the issue ("Modified gflow-cli environment", exit 0, version unchanged) was a raw uv tool upgrade, not gflow update.

Suggestion 1 — "pre-flight the lock" — measured, and there is no signal to act on.

  • chrome.exe cannot hold a Python .pyd; only a Python process can. The issue attributes the lock to "a Playwright-driven Chrome", which is not a mechanism that can produce it.
  • Every gflow process maps 12 of its venv's .pyd files, _greenlet.cp313-win_amd64.pyd among them — structlog imports greenlet unconditionally (python -c "import structlog, sys; print('greenlet' in sys.modules)" prints True). So gflow update is itself always a holder; a pre-flight that refuses while a native module is held would refuse every single time.
  • And the lock is not deterministically fatal. Measured on this machine: uv pip install --reinstall-package greenlet replaced 3.5.4 with 3.5.6 while another process had _greenlet.pyd loaded, exit 0. A mapped .pyd cannot be opened for write cross-process (PermissionError 13), but it can be unlinked and replaced.

Why uv failed with os error 5 in the reported run is therefore still unexplained — not our process, not Chrome, and not reproducible here. That is stated as unresolved rather than guessed at. This PR implements suggestion 2 instead, in its strongest form: the state the install is actually in, detected rather than inferred.

Refs #848

Summary by CodeRabbit

  • Bug Fixes

    • gflow update now verifies that the upgraded package can be imported in a fresh interpreter.
    • Reports unusable installations with configuration error 11 and the relevant import error.
    • Provides a package-manager-specific forced reinstall command, including the detected version when applicable.
    • Unrunnable validation probes are not incorrectly reported as installation failures.
  • Documentation

    • Updated usage documentation and changelog entries to describe post-update validation and recovery guidance.

…version

A package manager replaces files in place, so an interrupted upgrade leaves a
venv whose metadata reads perfectly and whose imports are dead. `gflow update`
only ever re-read the version, so it called that state "still 0.69.0" and sent
the user to a plain `uv tool upgrade` — which cannot repair it: it reads the
same intact metadata, finds it current and changes nothing.

Measured on Windows (#848): an aborted native-dependency swap left every command
dying on `AttributeError: module 'greenlet' has no attribute 'greenlet'`, and
only `uv tool install "gflow-cli==<version>" --force --reinstall` completed.

After the manager runs, import gflow-cli in a fresh isolated interpreter before
looking at any version. `-I` so a `gflow_cli` directory in the caller's CWD
cannot answer for the venv; a probe that could not run at all is never reported
as breakage.

Refs #848
@coderabbitai

coderabbitai Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: e5674265-93b4-4840-8284-7315bb342f14

📥 Commits

Reviewing files that changed from the base of the PR and between 04a3d4f and 595e15c.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • docs/USAGE.md
  • src/gflow_cli/update_check.py
  • tests/test_update_check.py
  • website/docs/USAGE.md

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

gflow update now probes package importability in an isolated interpreter after installation. Failed imports produce configuration error 11 with diagnostics and forced-reinstall guidance. Successful probes continue to version validation.

Changes

Update validation

Layer / File(s) Summary
Import probe and reinstall guidance
src/gflow_cli/update_check.py, tests/test_update_check.py
The update checker probes imports in a fresh interpreter and creates forced-reinstall commands for supported package managers. Tests cover successful, failed, and unrunnable probes.
Update flow and documented behavior
src/gflow_cli/update_check.py, tests/test_update_check.py, docs/USAGE.md, website/docs/USAGE.md, CHANGELOG.md
run_update checks importability before version validation and raises ConfigurationError for failed imports. Tests and documentation cover diagnostics, remediation, and existing version behavior.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Suggested reviewers: kittinan

Sequence Diagram(s)

sequenceDiagram
  participant run_update
  participant package_manager
  participant isolated_interpreter
  participant version_validation
  run_update->>package_manager: install update
  package_manager-->>run_update: installation result
  run_update->>isolated_interpreter: import installed package
  isolated_interpreter-->>run_update: import status and diagnostic
  run_update->>version_validation: validate version when import succeeds
Loading

Merge Risk: ⚪ Minimal · up to 595e1

The update command now detects unusable installations and provides repair guidance without introducing a supported current-head risk. It is ready to merge.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 16 functions across 2 files. (3 skipped: … 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 clearly and concisely describes the main change: detecting half-replaced installations instead of accepting readable version metadata as success.
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.
Full details: Docstring Coverage

Explanation

Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 16 functions across 2 files. (3 skipped: 3 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bugfix/848-update-detects-broken-install

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

@ffroliva
ffroliva marked this pull request as ready for review September 16, 2026 19:28
@ffroliva
ffroliva merged commit 72022f7 into develop Sep 16, 2026
20 checks passed
@ffroliva
ffroliva deleted the bugfix/848-update-detects-broken-install branch September 16, 2026 21:16
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