Skip to content

fix(*): resolve the latest release under windows powershell 5.1 - #863

Open
LivXue wants to merge 3 commits into
mainfrom
fix/ps51_release_lookup
Open

LivXue wants to merge 3 commits into
mainfrom
fix/ps51_release_lookup

Conversation

@LivXue

@LivXue LivXue commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

Summary

Native Windows installs fail from the shell Windows ships with. Both commands the README gives for Windows break under Windows PowerShell 5.1:

  • irm https://raven.evermind.ai/install.ps1 | iex stops with (308) Permanent Redirect. PowerShell 5.1 follows 301, 302 and 307 redirects but not 308, which is what that URL answers with. This is the error fix: cannot install Raven in Windows via powershell 5.1 #148 reported; the README already offered the direct URL for it.
  • The direct URL downloads the script, which then stops at "Could not resolve the latest Raven release wheel from GitHub". Resolve-RavenLatestVersion requests the release page without following the redirect and reads the Location header off the exception that raises. Only PowerShell 7 attaches the response to that exception. PowerShell 5.1 returns the unfollowed redirect as the response and reports MaximumRedirectExceeded as an error with no response, so the lookup always came back empty. This came in with fix(*): resolve the latest release without the github api quota #299 and has been on main since.

Changes:

  • install.ps1: the release-page request uses -ErrorAction Ignore instead of -ErrorAction Stop, so PowerShell 5.1 reads Location from the returned response. PowerShell 7 still raises regardless of -ErrorAction and goes through the existing catch. The Location is still matched against the same stable-tag pattern.
  • CI: the installer job ran install.ps1 under pwsh only, which is how this shipped. It now also runs the piped install under Windows PowerShell 5.1 (shell: powershell), into its own tool, bin and home directories, sharing the uv cache.
  • Docs: README, README.zh-CN, the docs-site quick start (en, zh) and the release notes template now say that Windows PowerShell 5.1 stops on the short URL with (308) Permanent Redirect and to switch to the direct URL when that appears. The short URL's redirect itself is unchanged.
  • Tests: tripwires for the lookup (-ErrorAction Ignore, catch kept) and for the CI gate (both shells, separate tool directories).

Second topic, Windows test hygiene: three tests failed when the suite ran on a Chinese-locale Windows checkout. CI runs the unit tests on Linux only, so it never saw them.

  • test_no_workflow_step_enters_the_removed_page_directory read workflows in the locale encoding (GBK there) and stopped on the UTF-8 in release.yml. It now reads UTF-8.
  • The font and LibreOffice harness tests run install.sh code under sh, which on Windows is Git Bash: its curl sits outside /usr/bin and paths mix separators, so two failed and others passed for the wrong reason (the digest-mismatch test passed because curl was missing). The ten harness tests now carry the POSIX-only skip the other three sh-driven tests already had, shared as one POSIX_SH_ONLY marker. Linux runs are unchanged.

Overlap with open #861: both PRs touch tests/test_install_script.py, with one textual conflict where #861 adds test_a_download_that_cannot_be_hashed_is_not_installed next to a test this PR marks. Whichever lands second keeps both, and the sh-harness tests #861 adds should get @POSIX_SH_ONLY too.

Third topic, CI test infrastructure: Python's locale-dependent text I/O uses the system code page when no encoding= is passed. On a GBK-locale Windows checkout, any test that reads or writes a file containing non-ASCII bytes will fail or silently corrupt data. The class of bug was found above; there are ~790 read_text() call sites without encoding in tests/, and no PYTHONUTF8 anywhere in the test infrastructure, so the next GBK-console developer would hit the next one.

  • CI: every job that runs pytest now sets PYTHONUTF8=1: the unit shard matrix (which also runs coverage), the trajectory regression replay, and the Windows self-upgrade job. On Linux this is a no-op. On Windows it forces every open()/read_text()/write_text() to use UTF-8, identical to what Linux CI already sees.

Type

  • Fix
  • Feature
  • Docs
  • CI / tooling
  • Refactor
  • Other

Verification

Run on Windows 11 (zh-CN) with Windows PowerShell 5.1.26100 and PowerShell 7.6.6. Every install below was isolated: UV_TOOL_DIR, UV_TOOL_BIN_DIR and RAVEN_HOME in a temp directory, RAVEN_MINIMAL=1, RAVEN_NO_LAUNCH=1.

  • Before the fix, under 5.1: irm https://raven.evermind.ai/install.ps1 fails with (308) Permanent Redirect; irm https://raw.githubusercontent.com/EverMind-AI/Raven/refs/heads/main/install.ps1 | iex stops at Could not resolve the latest Raven release wheel from GitHub.

  • Invoke-WebRequest https://github.com/EverMind-AI/Raven/releases/latest -MaximumRedirection 0 -UseBasicParsing -ErrorAction Stop: 5.1 throws InvalidOperationException (MaximumRedirectExceeded) with no Response; 7.6.6 throws HttpResponseException whose Response.Headers.Location is the tag URL. With -ErrorAction Ignore, 5.1 returns the 302 with Location set, and 7.6.6 still throws into the catch.

  • Redirects under 5.1, via httpbin redirect-to: 301, 302 and 307 are followed; 308 is not.

  • After the fix, Get-Content install.ps1 -Raw | Invoke-Expression under 5.1 and under 7.6.6, with uv tool update-shell disabled in a temp copy of the script to leave the user PATH alone: both resolved v0.2.4, installed raven with everos-memory, design-engine and ppt-engine, and raven.exe --version printed Raven v0.2.4.

  • uv run pytest tests/test_install_script.py tests/test_release_plugin_list.py tests/test_constraints_export_contract.py plus the six installer tests in tests/test_cli_onboard_commands.py, on Windows: 48 passed, 13 skipped (before: 55 passed, 3 skipped, 3 failed). The two new tripwires fail against the previous install.ps1 and ci.yml.

  • uv run ruff check and uv run ruff format --check on both test files pass; ty does not cover tests/, the only Python this changes. PYTHONPATH=. uv run python scripts/check_source_language.py origin/main exits 0.

  • Not run locally: the ten newly marked harness tests on Linux, for lack of a Linux environment here. Their bodies are unchanged and the marker does not skip on Linux, so the ubuntu unit job runs them as before.

  • Relevant tests pass locally

  • Relevant lint / type checks pass locally

  • User-facing docs or screenshots are updated when needed

Risk

User-visible: the one-line Windows install works again from Windows PowerShell 5.1 through the direct URL. The short URL still answers with a 308 there, and the docs now name that error. install.ps1 is served from main, so the fix reaches users on merge, and reverting the squash commit rolls it back the same way. The resolved Location is still matched against the same stable-tag pattern before anything is downloaded. The installer CI job gains one Windows install, which reuses the uv cache. On Windows, 13 sh-driven tests now report as skipped.

  • Security impact considered
  • Backward compatibility considered
  • Rollback path is clear for risky changes

Related Issues

#148

LivXue and others added 2 commits October 5, 2026 22:28
install.ps1 finds the latest release by requesting the release page
without following its redirect and reading the Location header off the
exception that raises. Only PowerShell 7 raises there. Windows
PowerShell 5.1, the shell every Windows ships with, returns the
unfollowed redirect as the response and reports the exceeded redirect
count as an error with no response attached, so the lookup always came
back empty and every one-line install from the stock shell stopped at
"Could not resolve the latest Raven release wheel from GitHub". The
request now ignores that error and reads the Location header from the
response; PowerShell 7 still raises and still goes through the catch.

The installer CI job ran install.ps1 under pwsh alone, which is how this
went unnoticed. It now also runs the piped install under Windows
PowerShell 5.1, into its own tool, bin and home directories.

The short URL raven.evermind.ai/install.ps1 answers with a 308, which
Windows PowerShell 5.1 does not follow either. The README, the docs site
quick start and the release notes template now name the error it
prints, (308) Permanent Redirect, and send readers to the direct URL
when they see it.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
Three tests failed when the suite ran on a Chinese-locale Windows
checkout. CI runs the unit tests on Linux only, so it never saw them.

The workflow scan read each file in the locale encoding, GBK there, and
stopped on the UTF-8 in release.yml. It now reads UTF-8, as the rest of
the file already does.

The font and LibreOffice harnesses run install.sh code under sh. On
Windows that sh is Git Bash, whose curl sits outside /usr/bin and whose
paths mix separators, so two harness tests failed and others passed for
the wrong reason: the digest-mismatch test passed because curl was
missing, not because of the digest. They now carry the POSIX-only skip
the other sh-driven tests already had, shared as one marker.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No blockers; this can merge as far as I am concerned.

Reviewed github/main...HEAD at d57ad789ea28. The release lookup now handles Windows PowerShell 5.1's non-terminating redirect error through the returned response while preserving PowerShell 7's exception-response path. The added Windows CI step exercises the stock shell independently from pwsh, and that installer job passed. The POSIX harness skips do not weaken Linux CI coverage; they prevent Git Bash from producing host-inaccurate results.

I covered the repository rules, the complete diff, relevant callers and history, backward compatibility across both PowerShell families, test-strength changes, and architecture constraints (no domain or layer boundary is affected). Verification: uv run --frozen --python 3.12 pytest tests/test_install_script.py tests/test_cli_onboard_commands.py -q passed with 395 tests; the source-language and large-file check scripts both exited 0.

@0xKT

0xKT commented Oct 5, 2026

Copy link
Copy Markdown
Member

Not a blocker -- the change is right and I am not asking for anything to

Severity of this one finding, not a verdict on the pull request. The blocking findings from this pass are review threads on the changed files; GitHub renders those collapsed, as a file name with no text.
move. One piece of merge-order information the board can see and a single PR
cannot, plus one note on the scope of the encoding fix.

This PR and #861 each merge cleanly onto the tip, but conflict with each
other.
install.ps1 auto-merges -- you touch Resolve-RavenLatestVersion
and #861 touches Install-PrivateNode -- but
tests/test_install_script.py does not:

merge-tree 861 x tip   -> clean
merge-tree 863 x tip   -> clean
merge-tree 861 x 863   -> CONFLICT (content): tests/test_install_script.py
                          (install.ps1 auto-merges)

The cause is that you are both editing the same decorator lines from opposite
directions. #861 adds four tests, each carrying an inline
@pytest.mark.skipif(sys.platform == "win32" or shutil.which("sh") is None, ...);
this PR replaces every inline copy of that decorator with the shared
@POSIX_SH_ONLY. Whichever lands second needs a hand resolution.

Worth knowing beyond the textual conflict: the shared marker exists precisely so
there is one spelling of "POSIX sh only", and a resolution that simply keeps both
sides leaves #861's two new sh-driven tests carrying inline decorators while
everything around them uses the marker. Landing this PR second, and converting
those two as part of the resolution, is the order that ends with one definition.
Entirely the maintainer's call -- I am only reporting that the pair needs a
decision, since neither PR can see the other.

The UTF-8 fix addresses the instance, not the class. path.read_text() ->
read_text(encoding="utf-8") in test_cli_onboard_commands.py:6496 is correct
and is the call that actually failed. For the record, the same shape is
everywhere: grep -rn "read_text()" tests/ | grep -v encoding= is 790 call
sites. Almost none can fail today -- most read JSON the test just wrote, or
ASCII -- and they only bite on a non-UTF-8-locale Windows checkout reading a
file that happens to contain non-ASCII, which is exactly the one you hit.

I am not asking you to touch the other 790; that is a different change and
charging this PR for it would be charging the wrong one. But there is no owner
for the question today -- no PYTHONUTF8 in pyproject.toml, the CI workflow or
tests/conftest.py -- so the next person on a GBK console finds the next one by
having it fail. One PYTHONUTF8=1 in the test environment would retire the whole
class, if you think it worth a follow-up.

For what it is worth, the part of this PR I found most valuable is the admission
in test_the_ci_gate_runs_the_windows_install_under_both_powershells: the gate
was green while every install from the stock shell failed. Adding the second
shell to CI is what stops that recurring, and it is a better fix than the
one-line -ErrorAction change it accompanies.

Python's locale-dependent text I/O uses the system code page when no
explicit encoding= is passed. On a GBK-locale Windows checkout, any
test that reads or writes a file containing non-ASCII bytes will fail
or silently corrupt data. The class of bug was found in this PR when
test_no_workflow_step_enters_the_removed_page_directory stopped on the
UTF-8 in release.yml; there are ~790 read_text() call sites without
encoding in tests/, and no PYTHONUTF8 anywhere in the test
infrastructure, so the next GBK-console developer would hit the next
one.

Set PYTHONUTF8=1 on every CI job that runs pytest: the unit shard
matrix (which also runs the coverage steps), the trajectory regression
replay, and the Windows self-upgrade job. On Linux this is a no-op.
On Windows it forces every open()/read_text()/write_text() to use
UTF-8, making the behaviour identical to what Linux CI already sees.
tests/conftest.py is not touched: local developers can set the same
variable, or add the flag to their pyproject.toml if needed, but that
is a follow-up.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No blockers; this can merge as far as I am concerned.

Reviewed the delta from d57ad789ea28 and rechecked the resulting github/main...HEAD diff. The new PYTHONUTF8=1 configuration covers every CI job that invokes pytest, is inherited by the intended subprocesses, and does not skip or weaken any tests. The Windows installer and self-upgrade jobs pass on this head.

I rechecked the repository rules, affected workflow callers and history, backward compatibility, test integrity, and architecture constraints; this CI-only delta does not cross a domain or layer boundary. Verification: uv run --frozen --python 3.12 pytest tests/test_install_script.py tests/test_cli_onboard_commands.py -q passed with 395 tests, and the source-language gate exited 0.

@0xKT

0xKT commented Oct 6, 2026

Copy link
Copy Markdown
Member

Not a blocker. Round two on 042a7e58. The delta is one commit touching only

Severity of this one finding, not a verdict on the pull request. The blocking findings from this pass are review threads on the changed files; GitHub renders those collapsed, as a file name with no text.
.github/workflows/ci.yml, and I re-read it line by line. Two nits, neither holding
anything; the rest checks out and your own description is accurate where I checked it.

Gates on the merged tree (base is an ancestor of head, so head is the merged tree):
gates.sh ruff/lint-imports/commit/large/lang rc=0; make check-large-files rc=0;
tests/test_install_script.py + tests/test_cli_onboard_commands.py -> 395 passed in 10.50s
(same 395 as round one, which matches a delta that adds no test); the other three files that
read a workflow (test_constraints_export_contract, test_dependabot_scope_canon,
test_release_plugin_list) -> 14 passed; non-ASCII added lines live only in
README.zh-CN.md (1) and docs-site/docs/quick-start.zh.md (2).

What I checked and found right

Your "On Linux this is a no-op" is correct, and I verified it rather than taking it: the
unit job's matrix is os: [ubuntu-latest] and trajectory is runs-on: ubuntu-latest,
so two of the three placements never see a non-UTF-8 runner. On a UTF-8 locale
locale.getpreferredencoding(False) is UTF-8 with or without the variable (only
sys.flags.utf8_mode moves: 0 -> 1), so nothing those jobs read decodes differently.
windows-upgrade is the one placement on a non-Linux runner.

The class you describe is real and I measured it at this head: 792 read_text() call sites
with no encoding= under tests/, 2620 non-ASCII text files in the repo, and 575 of those
cannot be decoded as cp1252 at all -- including the SKILL.md that
tests/_ppt_engine_skill_claims.py:25 reads at import time, with no encoding=. The
release.yml case you fixed is four em-dashes (e2 80 94), which cp1252 decodes and GBK
rejects -- so that test failed on your machine and could not have failed on CI.

Nit 1 -- the measure is in the one place the developer it names never reads

Your stated beneficiary is "the next GBK-console developer", and the variable is in
ci.yml, which a local uv run pytest never executes. git grep PYTHONUTF8 outside that
file returns nothing; there is no -X utf8 and no UTF-8 setting in pyproject.toml or the
Makefile, so a local run on a GBK box is exactly as exposed after this commit as before.

The three placements, measured off the parsed workflow:

job runs-on PYTHONUTF8
unit ubuntu-latest only job-level, no-op
trajectory ubuntu-latest step-level, no-op
windows-upgrade windows-latest step-level; that test does no encoding-less text read
installer ubuntu-latest and windows-latest not set

So nothing it is on can change a decode today, and the only job whose matrix actually
carries windows-latest is the one it is not on. That is defensible as future-proofing --
if the runner image or the matrix ever changes, it is already in place -- but something that
reached the local run (a pytest-level setting, or a line in the contributor docs) is what
would answer the sentence the description makes.

Nit 2 -- the in-file comment describes a matrix that does not exist

ci.yml:91-95 says "Setting this at the job level means pytest, coverage, and any subprocess
all run in UTF-8 mode regardless of the runner OS". That job has one OS. The description says
"On Linux this is a no-op", which is the accurate version -- but the description does not
live in the file, and the comment is what the next person editing this job reads. Same two
readers, two different facts.

Smaller, in the same comment: "the stock shell on a GBK-locale Windows machine reads and
writes files in the locale code page" -- the shell is not what decides this; Python's
locale.getpreferredencoding(False) is, which is why PYTHONUTF8 fixes it and a different
shell would not.

Stated, not filed

  • Nothing pins the three assignments. Removing all three (and the two env: keys they leave
    empty, so the file still parses -- I checked with yaml.safe_load, my first attempt made it
    malformed and I threw that result away) leaves every test that reads a workflow green:
    395 passed and 14 passed. The control that this is a real measurement and not a suite
    that never opens the file: changing the 5.1 step's shell: powershell to pwsh turns
    test_the_ci_gate_runs_the_windows_install_under_both_powershells red, 1 failed, 46 passed.
    Not filed as its own point because CI env vars are not normally pinned here -- it is only
    worth a line because this PR does add a tripwire for its other ci.yml change.
  • PYTHONUTF8 on the unit job also reaches the coverage data write, as your description
    says. Coverage files are the tool's own format and I did not find a path where the code page
    could change them, so I am not filing anything there.
  • I could not build a non-UTF-8 interpreter on this machine to demonstrate the failing decode
    end to end: macOS enables UTF-8 mode for the C locale by itself, so my control could not be
    moved. The cp1252/GBK results above are bytes.decode(...) on the real files, not a run of
    the suite under those locales, and I am saying so rather than implying I ran it.

Not covered

The delta is CI configuration; no runner was exercised. Everything above about what a job
would do is read off the parsed workflow and the repo's bytes, not off a build.

This branch has not been deployed

No deployments
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.

3 participants