From 1f46aaa87ca4a48c1bef53f4a868ed4edbff8ea8 Mon Sep 17 00:00:00 2001 From: Muqsit Date: Sat, 5 Sep 2026 20:23:49 -0700 Subject: [PATCH 1/2] =?UTF-8?q?chore(release):=20v0.21.1=20=E2=80=94=20fix?= =?UTF-8?q?=20version=20string,=20guard=20it=20with=20a=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 0.21.0 shipped with __version__ still at "0.20.0". Every 0.21.0 install runs the correct code but self-reports the previous version, so a fleet-wide `linear --version` sweep calls every up-to-date box stale — which is exactly how this was found, right after rolling 0.21.0 out to nine machines. __version__ is bumped in the feature commit, not the release commit (see 3811dfd for 0.20.0), which is what made it easy to miss. Adds VersionMatchesChangelogTest so the suite fails when __version__ drifts from the newest CHANGELOG heading. Verified it fails when reverted. Re-pins install.sh + README to v0.21.1 (linear sha256 4055001d…). --- CHANGELOG.md | 11 +++++++++++ README.md | 4 ++-- install.sh | 4 ++-- linear | 2 +- test_linear.py | 21 +++++++++++++++++++++ 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e03fcfd..8e55852 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.21.1] - 2026-09-05 + +### Fixed + +- **`linear --version` reported `0.20.0` on a 0.21.0 install.** `__version__` + is bumped in the feature commit, not the release commit, and 0.21.0 shipped + without that bump — so every 0.21.0 install ran the right code and + self-reported the previous version. That silently breaks any "is this box up + to date?" check, including a fleet-wide `linear --version` sweep. No behavior + change beyond the version string. + ## [0.21.0] - 2026-09-05 ### Changed diff --git a/README.md b/README.md index 20fe67b..cefce59 100644 --- a/README.md +++ b/README.md @@ -30,13 +30,13 @@ Use the same shell contract whether you're typing or a subagent is: query the qu Preferred (pinned tag + SHA-256 verify via `install.sh`): ```bash -curl -sSL https://raw.githubusercontent.com/phnx-labs/linear-cli/v0.21.0/install.sh | sh +curl -sSL https://raw.githubusercontent.com/phnx-labs/linear-cli/v0.21.1/install.sh | sh ``` Manual pin (same tag; no checksum — prefer `install.sh` above): ```bash -curl -o ~/.local/bin/linear https://raw.githubusercontent.com/phnx-labs/linear-cli/v0.21.0/linear +curl -o ~/.local/bin/linear https://raw.githubusercontent.com/phnx-labs/linear-cli/v0.21.1/linear chmod +x ~/.local/bin/linear ``` diff --git a/install.sh b/install.sh index a9f58c6..cac244a 100755 --- a/install.sh +++ b/install.sh @@ -6,9 +6,9 @@ set -eu REPO="phnx-labs/linear-cli" # Pin a release tag (not floating main). Override with LINEAR_CLI_VERSION / # LINEAR_CLI_SHA256 only when you deliberately install a different revision. -VERSION="${LINEAR_CLI_VERSION:-v0.21.0}" +VERSION="${LINEAR_CLI_VERSION:-v0.21.1}" # SHA-256 of the `linear` file at VERSION. Recomputed whenever VERSION bumps. -EXPECTED_SHA256="${LINEAR_CLI_SHA256:-d68fe508cb19460d0f2a517062a7a66646525539a11caedaa4a15ae5b3717cbe}" +EXPECTED_SHA256="${LINEAR_CLI_SHA256:-4055001dcdef84f21b30760a1a1e922dd4d0d7b4b9c936d3a6e5c4c143e7b465}" URL="https://raw.githubusercontent.com/${REPO}/${VERSION}/linear" pick_install_dir() { diff --git a/linear b/linear index 09ad808..32db094 100755 --- a/linear +++ b/linear @@ -66,7 +66,7 @@ _LOCK_CONTENDED_ERRNOS = tuple( if e is not None ) -__version__ = "0.20.0" +__version__ = "0.21.1" # Sentinel for "flag not supplied" — distinct from None, which means an explicit # clear (e.g. `--project none`). Lets update pre-resolve a field once and pass diff --git a/test_linear.py b/test_linear.py index 480e707..8d31f6c 100644 --- a/test_linear.py +++ b/test_linear.py @@ -10,6 +10,7 @@ import io import json import os +import re import sys import tempfile import types @@ -550,6 +551,26 @@ def test_bulk_error_survives_tabs_and_newlines_in_the_assign_value(self): self.assertIn("matched no human", err) +class VersionMatchesChangelogTest(unittest.TestCase): + """`__version__` lives in `linear` and is bumped in the feature commit, not + the release commit, so it is easy to forget — 0.21.0 shipped self-reporting + 0.20.0, which made a fleet-wide `linear --version` sweep call every box + stale when it was in fact up to date.""" + + def test_version_matches_newest_released_changelog_entry(self): + changelog = Path(_HERE, "CHANGELOG.md").read_text() + # Released headings look like "## [0.21.1] - 2026-09-05"; + # "## [Unreleased]" has no version and is skipped by the pattern. + versions = re.findall(r"^## \[(\d+\.\d+\.\d+)\]", changelog, re.MULTILINE) + self.assertTrue(versions, "no released version headings found in CHANGELOG.md") + self.assertEqual( + linear_cli.__version__, versions[0], + f"__version__ is {linear_cli.__version__!r} but the newest CHANGELOG " + f"entry is {versions[0]!r}. Bump __version__ in `linear` when you add " + f"the CHANGELOG entry, not at release time.", + ) + + class BulkTsvFieldTest(unittest.TestCase): """Bulk --from-file output is a tab-separated record. Titles come from the caller's JSONL and from Linear, so neither may carry a raw tab/newline.""" From 9481f8493a1cde17f083376adf3158a69615130c Mon Sep 17 00:00:00 2001 From: Muqsit Date: Sat, 5 Sep 2026 20:29:25 -0700 Subject: [PATCH 2/2] docs(tests): note the stale-.pyc trap for the extensionless `linear` Cost a reviewer a false 'version test is flaky' diagnosis on #46: macOS system python3 caches `linear` as `linearcpython-39.pyc`, and a same-length edit ("0.20.0" -> "0.21.1") is easy to miss as stale. --- test_linear.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test_linear.py b/test_linear.py index 8d31f6c..06732e6 100644 --- a/test_linear.py +++ b/test_linear.py @@ -2,6 +2,12 @@ """Regression tests for the `linear` CLI. The CLI is intentionally dependency-free, so tests use only stdlib unittest. + +Seeing a stale value for something you just edited in `linear`? macOS's system +python3 sets a global sys.pycache_prefix, and because `linear` has no .py +extension the cached name loses its separator (`linearcpython-39.pyc`), so the +usual invalidation is easy to miss when an edit doesn't change the file length. +Re-run with PYTHONDONTWRITEBYTECODE=1 before believing the result. """ import contextlib