From bbb22e0a2ef47f27a6229f8aa7b41f307174b651 Mon Sep 17 00:00:00 2001 From: Abir Abbas Date: Mon, 2 Nov 2026 09:39:52 -0500 Subject: [PATCH 1/2] ci: remove the daily Release downloads workflow It has failed every day because CODEAF_POSTHOG_PROJECT_KEY was never set, and the team agreed the download counts are redundant. Removes the workflow, the script only it ran, that script's tests, and the TELEMETRY.md section that described it. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release-downloads.yml | 33 -- docs/TELEMETRY.md | 16 - scripts/release_downloads.py | 231 ------------ scripts/test_release_downloads.py | 444 ------------------------ 4 files changed, 724 deletions(-) delete mode 100644 .github/workflows/release-downloads.yml delete mode 100644 scripts/release_downloads.py delete mode 100644 scripts/test_release_downloads.py diff --git a/.github/workflows/release-downloads.yml b/.github/workflows/release-downloads.yml deleted file mode 100644 index 39e40c1101..0000000000 --- a/.github/workflows/release-downloads.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Release downloads - -# Reports release download counts to PostHog once a day. The counts come from -# public GitHub release data — the API publishes a cumulative download_count -# per asset — so this needs no code in the binary. One event per binary asset -# (codeaf--[.exe]); sidecars like checksums.txt are not reported. -on: - schedule: - # 06:00 UTC every day. - - cron: '0 6 * * *' - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: release-downloads - cancel-in-progress: false - -jobs: - report: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Report release download counts - env: - GH_TOKEN: ${{ github.token }} - GITHUB_REPOSITORY: ${{ github.repository }} - CODEAF_POSTHOG_PROJECT_KEY: ${{ secrets.CODEAF_POSTHOG_PROJECT_KEY }} - # Repository variable; unset falls back to https://us.i.posthog.com. - POSTHOG_HOST: ${{ vars.POSTHOG_HOST }} - run: | - python3 scripts/release_downloads.py diff --git a/docs/TELEMETRY.md b/docs/TELEMETRY.md index 6312116e08..8fbb44a8da 100644 --- a/docs/TELEMETRY.md +++ b/docs/TELEMETRY.md @@ -151,22 +151,6 @@ judge at all). `codeaf pool status` says how many rows wait to leave for the pool; this page describes both streams, so "what is collected" is answered for everything the binary sends. -## Download counts - -The download numbers this repository reports come from public GitHub release -data: the GitHub API publishes a cumulative download count for every asset on -a release, and a daily scheduled workflow reads those counts and sends one -event per binary asset to PostHog (`codeaf:release_downloads`). No code in -the binary is involved, nothing is collected from the person downloading, and -the only facts in the event are the release tag, the asset's platform, and -the count GitHub already shows on the release page. Sidecar files such as -`checksums.txt` are not counted. The workflow lives in -`.github/workflows/release-downloads.yml`; the script behind it is -`scripts/release_downloads.py`, and running it with `--dry-run` prints the -batch it would send. - -The release-download workflow fails visibly when its production PostHog key or GitHub token is missing; dry-run still works without a PostHog key. - ## Estimates and recovered receipts See [Model usage accounting](model-usage-accounting.md) for the estimator, diff --git a/scripts/release_downloads.py b/scripts/release_downloads.py deleted file mode 100644 index 7e96c2a7f3..0000000000 --- a/scripts/release_downloads.py +++ /dev/null @@ -1,231 +0,0 @@ -#!/usr/bin/env python3 -"""Report release download counts to PostHog. - -The numbers come from public GitHub release data: the GitHub API exposes a -running download count for every asset on a release, and this script reads -that count and sends one PostHog event per binary asset so the download -trend is visible next to the other codeaf telemetry. It runs daily from -.github/workflows/release-downloads.yml, and runs locally too: - - python3 scripts/release_downloads.py --dry-run - -Nothing is inferred and nothing about the requester is known: an asset's -download count is public, the event carries the release tag, the asset's -platform, and that count. Only assets named codeaf--[.exe] are -reported; sidecars like checksums.txt are not releases of the binary. - -An asset is reported once per day even if the run repeats: the event's id is -derived from the tag, the asset and the UTC date, so PostHog's event -deduplication makes a same-day rerun land on the first copy instead of -counting twice. -""" - -import argparse -import datetime -import json -import os -import re -import sys -import urllib.request -import uuid - -POSTHOG_EVENT = "codeaf:release_downloads" -DISTINCT_ID = "codeaf-release-downloads" -DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com" -GITHUB_API = "https://api.github.com" -NAMESPACE = uuid.NAMESPACE_URL -# codeaf--[.exe]. os/arch follow the release.yml build matrix, so -# anything outside this set is a file this script has no opinion about. -ASSET_RE = re.compile( - r"^codeaf-(?Pdarwin|linux|windows)-" - r"(?Pamd64|arm64)(?P\.exe)?$" -) -# Tag grammar, matching cmd/codeaf-release/version.go exactly: stable and -# rc tags are vMAJOR.MINOR.PATCH (rc with an -rc.N counter), and the dev and -# staging channels are date-and-commit tags. Anything else is no channel this -# script can name, so it is reported as "unknown". -STABLE_TAG_RE = re.compile(r"^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$") -RC_TAG_RE = re.compile( - r"^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)-rc\.([1-9][0-9]*)$" -) -DEV_TAG_RE = re.compile(r"^dev-[0-9]{8}-[0-9a-f]{12}$") -STAGING_TAG_RE = re.compile(r"^staging-[0-9]{8}-[0-9a-f]{12}$") -PROPS = ( - "release_tag", - "channel", - "prerelease", - "os", - "arch", - "download_count", - "snapshot_date", -) - - -def release_channel(tag: str) -> str: - """The channel of a tag: stable, rc, dev or staging, else "unknown".""" - if STABLE_TAG_RE.match(tag): - return "stable" - if RC_TAG_RE.match(tag): - return "rc" - if DEV_TAG_RE.match(tag): - return "dev" - if STAGING_TAG_RE.match(tag): - return "staging" - return "unknown" - - -def asset_os_arch(name: str): - """(os, arch) for an asset this script reports, else None.""" - match = ASSET_RE.match(name) - if not match: - return None - return match.group("os"), match.group("arch") - - -def event_uuid(tag: str, asset: str, snapshot_date: str) -> str: - """One id per release, asset and day, so a same-day rerun deduplicates.""" - return str( - uuid.uuid5( - NAMESPACE, f"{DISTINCT_ID}|{tag}|{asset}|{snapshot_date}" - ) - ) - - -def fetch_releases(repository: str, token: str): - """Every release of the repository, following GitHub's pagination.""" - releases = [] - page = 1 - while True: - request = urllib.request.Request( - f"{GITHUB_API}/repos/{repository}/releases" - f"?per_page=100&page={page}", - headers={ - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {token}", - "X-GitHub-Api-Version": "2022-11-28", - "User-Agent": "codeaf-release-downloads", - }, - ) - with urllib.request.urlopen(request, timeout=30) as response: - batch = json.load(response) - if not isinstance(batch, list): - raise ValueError(f"unexpected GitHub API response on page {page}") - releases.extend(batch) - if len(batch) < 100: - return releases - page += 1 - - -def build_events(releases, snapshot_date: str): - """One PostHog event per binary asset, with the properties it carries.""" - events = [] - for release in releases: - if release.get("draft"): - continue - tag = release["tag_name"] - for asset in release.get("assets") or []: - name = asset.get("name") or "" - platform = asset_os_arch(name) - if platform is None: - continue - os_name, arch = platform - events.append( - { - "event": POSTHOG_EVENT, - "uuid": event_uuid(tag, name, snapshot_date), - "distinct_id": DISTINCT_ID, - "properties": { - "release_tag": tag, - "channel": release_channel(tag), - "prerelease": bool(release.get("prerelease")), - "os": os_name, - "arch": arch, - # The cumulative total the GitHub API reports. - "download_count": asset["download_count"], - "snapshot_date": snapshot_date, - # The sender is a CI runner; its location is meaningless. - "$process_person_profile": False, - "$geoip_disable": True, - }, - } - ) - return events - - -def build_payload(events, project_key: str): - return {"api_key": project_key, "batch": events} - - -def send(payload, posthog_host: str) -> None: - body = json.dumps(payload).encode() - request = urllib.request.Request( - f"{posthog_host.rstrip('/')}/batch/", - data=body, - headers={"Content-Type": "application/json"}, - method="POST", - ) - with urllib.request.urlopen(request, timeout=30) as response: - response.read() - - -def main(argv=None) -> int: - parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) - parser.add_argument( - "--dry-run", - action="store_true", - help="print the batch JSON instead of sending it", - ) - parser.add_argument( - "--fixture", - metavar="PATH", - help="read the releases API response from a file instead of the network", - ) - parser.add_argument( - "--repository", - default=os.environ.get("GITHUB_REPOSITORY", "Agent-Field/codeaf"), - help="owner/name of the repository whose releases are read", - ) - args = parser.parse_args(argv) - - project_key = os.environ.get("CODEAF_POSTHOG_PROJECT_KEY", "") - if not project_key and not args.dry_run: - print( - "error: CODEAF_POSTHOG_PROJECT_KEY is not set; nothing sent", - file=sys.stderr, - ) - return 1 - # --dry-run is the local and test path: it must work without a key, so a - # missing key is printed as a placeholder rather than stopping the run. - if not project_key: - project_key = "" - - if args.fixture: - with open(args.fixture) as fixture: - releases = json.load(fixture) - else: - token = os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN", "") - if not token: - print( - "error: GITHUB_TOKEN is not set; nothing sent", - file=sys.stderr, - ) - return 1 - releases = fetch_releases(args.repository, token) - - snapshot_date = datetime.datetime.now(datetime.timezone.utc).strftime( - "%Y-%m-%d" - ) - events = build_events(releases, snapshot_date) - payload = build_payload(events, project_key) - - if args.dry_run: - print(json.dumps(payload, indent=2, sort_keys=True)) - return 0 - - send(payload, os.environ.get("POSTHOG_HOST") or DEFAULT_POSTHOG_HOST) - print(f"sent {len(events)} events to PostHog") - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/scripts/test_release_downloads.py b/scripts/test_release_downloads.py deleted file mode 100644 index ae1df8f9e1..0000000000 --- a/scripts/test_release_downloads.py +++ /dev/null @@ -1,444 +0,0 @@ -#!/usr/bin/env python3 -"""Tests for scripts/release_downloads.py: dry-run mode against a recorded -fixture of the GitHub releases API, plus the identifiers the wire spec spells -out, the workflow that schedules the script, and the docs paragraph. -""" - -import ast -import datetime -import importlib.util -import json -import os -import pathlib -import re -import subprocess -import sys -import unittest -import uuid -from unittest import mock - -HERE = pathlib.Path(__file__).resolve().parent -SCRIPT = HERE / "release_downloads.py" -FIXTURE = HERE / "testdata" / "github_releases.json" -WORKFLOW = HERE.parent / ".github" / "workflows" / "release-downloads.yml" -DOCS = HERE.parent / "docs" - -_spec = importlib.util.spec_from_file_location("release_downloads", SCRIPT) -module = importlib.util.module_from_spec(_spec) -_spec.loader.exec_module(module) - - -class FakeResponse: - """Stand-in for urlopen's return: a context manager whose read() is JSON.""" - - def __init__(self, payload): - self._payload = ( - payload if isinstance(payload, bytes) else json.dumps(payload).encode() - ) - - def __enter__(self): - return self - - def __exit__(self, *exception): - return False - - def read(self): - return self._payload - - -try: - import yaml # type: ignore -except ImportError: # pragma: no cover - depends on the machine - yaml = None - - -EVENT_PROPERTIES = ( - "release_tag", - "channel", - "prerelease", - "os", - "arch", - "download_count", - "snapshot_date", -) - - -def run_script(*args, env_extra=None): - env = dict(os.environ) - env.pop("CODEAF_POSTHOG_PROJECT_KEY", None) - env.update(env_extra or {}) - return subprocess.run( - [sys.executable, str(SCRIPT), *args], - capture_output=True, - text=True, - env=env, - ) - - -def dry_run_payload(env_extra=None): - result = run_script( - "--dry-run", "--fixture", str(FIXTURE), env_extra=env_extra - ) - assert result.returncode == 0, result.stderr - return json.loads(result.stdout) - - -class ReleaseDownloadsTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.payload = dry_run_payload({"CODEAF_POSTHOG_PROJECT_KEY": "phx_test"}) - cls.batch = cls.payload["batch"] - - def test_one_event_per_binary_asset(self): - # 6 assets x 2 full releases plus one asset on each of the dev, staging - # and beta tags; checksums.txt and any other sidecar on the fixture is - # not a binary asset and gets no event. - self.assertEqual(len(self.batch), 15) - tags = {event["properties"]["release_tag"] for event in self.batch} - self.assertEqual( - tags, - { - "v1.2.0", - "v1.3.0-rc.1", - "dev-20260917-09299019bcdc", - "staging-20260916-ed5fe58eb546", - "v1.4.0-beta.1", - }, - ) - - def test_payload_shape(self): - self.assertEqual( - self.payload["api_key"], "phx_test" - ) - for event in self.batch: - self.assertEqual(event["event"], "codeaf:release_downloads") - self.assertEqual(event["distinct_id"], "codeaf-release-downloads") - - def test_event_properties(self): - for event in self.batch: - properties = event["properties"] - self.assertEqual( - sorted(properties), - sorted( - EVENT_PROPERTIES - + ("$process_person_profile", "$geoip_disable") - ), - ) - self.assertIs(properties["$process_person_profile"], False) - # The sender is a CI runner; its location is meaningless. - self.assertIs(properties["$geoip_disable"], True) - self.assertEqual( - properties["snapshot_date"], - datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d"), - ) - self.assertIsInstance(properties["prerelease"], bool) - self.assertIsInstance(properties["download_count"], int) - - def test_channel_and_prerelease(self): - channels = { - event["properties"]["release_tag"]: ( - event["properties"]["channel"], - event["properties"]["prerelease"], - ) - for event in self.batch - } - self.assertEqual(channels["v1.2.0"], ("stable", False)) - self.assertEqual(channels["v1.3.0-rc.1"], ("rc", True)) - self.assertEqual(channels["dev-20260917-09299019bcdc"], ("dev", True)) - self.assertEqual( - channels["staging-20260916-ed5fe58eb546"], ("staging", True) - ) - # Not one of the four tag shapes this repository publishes. - self.assertEqual(channels["v1.4.0-beta.1"], ("unknown", True)) - - def test_platforms_and_counts(self): - counts = { - (event["properties"]["os"], event["properties"]["arch"]): event[ - "properties" - ]["download_count"] - for event in self.batch - if event["properties"]["release_tag"] == "v1.2.0" - } - self.assertEqual( - counts, - { - ("linux", "amd64"): 15010, - ("linux", "arm64"): 11240, - ("darwin", "amd64"): 18930, - ("darwin", "arm64"): 22471, - ("windows", "amd64"): 4109, - ("windows", "arm64"): 2051, - }, - ) - - def test_event_uuid_format_and_uniqueness(self): - uuids = [event["uuid"] for event in self.batch] - self.assertEqual(len(set(uuids)), len(uuids)) - for value in uuids: - self.assertRegex( - value, - r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", - ) - self.assertEqual(uuid.UUID(value).version, 5) - - def test_same_day_rerun_is_deduplicated(self): - # The id is derived from the tag, the asset and the UTC date, so a - # rerun on the same day produces the same ids and PostHog drops the - # repeat instead of counting the downloads twice. - again = dry_run_payload({"CODEAF_POSTHOG_PROJECT_KEY": "phx_test"}) - self.assertEqual( - [event["uuid"] for event in again["batch"]], - [event["uuid"] for event in self.batch], - ) - - def test_uuid_depends_on_snapshot_date(self): - # A different day is a new event, not a repeat of yesterday's. - tag, asset, distinct = "v1.2.0", "codeaf-linux-amd64", "codeaf-release-downloads" - self.assertNotEqual( - uuid.uuid5(uuid.NAMESPACE_URL, f"{distinct}|{tag}|{asset}|2026-02-11"), - uuid.uuid5(uuid.NAMESPACE_URL, f"{distinct}|{tag}|{asset}|2026-02-12"), - ) - - def test_no_checksums_or_asset_names_in_payload(self): - # Sidecars are not binary releases: their names and counts never - # appear in the payload at all. - text = json.dumps(self.payload) - self.assertNotIn("checksums.txt", text) - for event in self.batch: - self.assertNotIn("name", event["properties"]) - - def test_dry_run_without_project_key_prints_the_batch(self): - # --dry-run is the local and test path: no key in the environment is no - # obstacle, the key is printed as a placeholder and the batch still - # comes out, with no notice. - result = run_script("--dry-run", "--fixture", str(FIXTURE)) - self.assertEqual(result.returncode, 0, result.stderr) - payload = json.loads(result.stdout) - self.assertEqual(payload["api_key"], "") - self.assertEqual(len(payload["batch"]), 15) - - def test_sending_without_project_key_fails(self): - # Only the sending path stops on a missing key; it never reaches the - # network or the PostHog host. - result = run_script() - self.assertEqual(result.returncode, 1) - self.assertIn("error", result.stderr) - self.assertIn("CODEAF_POSTHOG_PROJECT_KEY", result.stderr) - self.assertEqual(result.stdout, "") - - def test_sending_without_github_token_fails(self): - result = run_script(env_extra={ - "CODEAF_POSTHOG_PROJECT_KEY": "phx_test", - "GH_TOKEN": "", "GITHUB_TOKEN": "", - }) - self.assertEqual(result.returncode, 1) - self.assertIn("GITHUB_TOKEN", result.stderr) - self.assertEqual(result.stdout, "") - - -class PaginationTest(unittest.TestCase): - def test_all_pages_are_followed(self): - page_one = [ - { - "tag_name": f"v1.{number}.0", - "draft": False, - "prerelease": False, - "assets": [{"name": "codeaf-linux-amd64", "download_count": 1}], - } - for number in range(100) - ] - page_two = [ - { - "tag_name": "v0.1.0", - "draft": False, - "prerelease": False, - "assets": [{"name": "codeaf-linux-arm64", "download_count": 2}], - } - ] - with mock.patch( - "urllib.request.urlopen", - side_effect=[FakeResponse(page_one), FakeResponse(page_two)], - ) as urlopen: - releases = module.fetch_releases("Agent-Field/codeaf", "token") - self.assertEqual(urlopen.call_count, 2) - self.assertIn("page=1", urlopen.call_args_list[0].args[0].full_url) - self.assertIn("page=2", urlopen.call_args_list[1].args[0].full_url) - self.assertEqual(len(releases), 101) - self.assertEqual(releases[0]["tag_name"], "v1.0.0") - self.assertEqual(releases[-1]["tag_name"], "v0.1.0") - - def test_posthog_host_is_overridable(self): - with mock.patch("urllib.request.urlopen") as urlopen: - urlopen.return_value = FakeResponse(b"") - module.send({}, "https://telemetry.example.com") - request = urlopen.call_args.args[0] - self.assertEqual(request.full_url, "https://telemetry.example.com/batch/") - - -class WorkflowTest(unittest.TestCase): - """The workflow that schedules the script, asserted as text so the check - reads the same file GitHub Actions does.""" - - @classmethod - def setUpClass(cls): - cls.text = WORKFLOW.read_text() - - def test_workflow_file_exists(self): - self.assertTrue(WORKFLOW.exists()) - - def test_workflow_is_scheduled_daily_and_dispatchable(self): - self.assertIn("workflow_dispatch", self.text) - self.assertIn("'0 6 * * *'", self.text) - - def test_workflow_permissions_are_contents_read_only(self): - self.assertIn("permissions:", self.text) - self.assertIn("contents: read", self.text) - - def test_workflow_runs_the_script(self): - self.assertIn("python3 scripts/release_downloads.py", self.text) - self.assertIn("runs-on: ubuntu-latest", self.text) - self.assertIn("actions/checkout", self.text) - - def test_workflow_uses_builtin_token_secret_and_host_variable(self): - self.assertIn("GH_TOKEN: ${{ github.token }}", self.text) - self.assertIn( - "CODEAF_POSTHOG_PROJECT_KEY: ${{ secrets.CODEAF_POSTHOG_PROJECT_KEY }}", - self.text, - ) - self.assertIn("POSTHOG_HOST: ${{ vars.POSTHOG_HOST }}", self.text) - - def test_workflow_parses_as_yaml(self): - if yaml is None: - self.skipTest("PyYAML is not installed; the text checks above stand") - document = yaml.safe_load(self.text) - triggers = document[True] - self.assertEqual(triggers["schedule"][0]["cron"], "0 6 * * *") - self.assertIn("workflow_dispatch", triggers) - self.assertEqual(document["permissions"], {"contents": "read"}) - - def test_workflow_and_script_are_the_only_nonstandard_files(self): - # The feature is workflow + script + fixture + test + docs; no Go - # source anywhere carries it. - text = self.text - self.assertNotIn("go run", text) - self.assertNotIn("go build", text) - - -class DocsTest(unittest.TestCase): - def test_download_counts_paragraph_exists(self): - text = (DOCS / "TELEMETRY.md").read_text() - flat = " ".join(text.split()) - self.assertIn("## Download counts", text) - self.assertIn("public GitHub release data", flat) - - -class DocsAndWorkflowMixTest(unittest.TestCase): - def test_docs_paragraph_names_workflow_and_script(self): - text = (DOCS / "TELEMETRY.md").read_text() - self.assertIn("## Download counts", text) - self.assertIn("release-downloads.yml", text) - self.assertIn("scripts/release_downloads.py", text) - self.assertIn("checksums.txt", text) - - -class ScriptSourceTest(unittest.TestCase): - """The script is python3 standard library only, and imports prove it.""" - - def test_script_imports_stdlib_only(self): - tree = ast.parse(SCRIPT.read_text()) - imported = set() - for node in ast.walk(tree): - if isinstance(node, ast.Import): - imported.update(alias.name.split(".")[0] for alias in node.names) - elif isinstance(node, ast.ImportFrom) and node.level == 0 and node.module: - imported.add(node.module.split(".")[0]) - self.assertEqual( - imported, - {"argparse", "datetime", "json", "os", "re", "sys", "urllib", "uuid"}, - ) - # Nothing outside the standard library is imported, so the script - # runs anywhere python3 does, including the workflow runner. - # sys.stdlib_module_names exists from Python 3.10; on older interpreters - # the pinned import set above is the check that stands. - stdlib = getattr(sys, "stdlib_module_names", None) - if stdlib is not None: - self.assertLessEqual(imported, set(stdlib)) - - -class ChannelTest(unittest.TestCase): - """The four tag shapes of cmd/codeaf-release/version.go, and nothing - else: any other tag is reported as "unknown" rather than mislabelled.""" - - def test_stable_tags(self): - self.assertEqual(module.release_channel("v1.2.0"), "stable") - self.assertEqual(module.release_channel("v0.0.1"), "stable") - self.assertEqual(module.release_channel("v10.20.30"), "stable") - - def test_rc_tags(self): - self.assertEqual(module.release_channel("v1.3.0-rc.1"), "rc") - self.assertEqual(module.release_channel("v2.0.0-rc.12"), "rc") - - def test_dev_tags(self): - self.assertEqual( - module.release_channel("dev-20260917-09299019bcdc"), "dev" - ) - self.assertEqual(module.release_channel("dev-20250101-abcdef012345"), "dev") - - def test_staging_tags(self): - self.assertEqual( - module.release_channel("staging-20260916-ed5fe58eb546"), "staging" - ) - self.assertEqual( - module.release_channel("staging-20250102-000000000abc"), "staging" - ) - - def test_unrecognised_tags_are_unknown(self): - for tag in ( - "v1.4.0-beta.1", - "v1.3.0-rc.0", - "v1.3.0-rc", - "v01.2.0", - "1.2.0", - "v1.2", - "dev-20260917-09299019bcdcX", - "staging-20260916-ED5FE58EB546", - "nightly-20260917-09299019bcdc", - "", - ): - self.assertEqual(module.release_channel(tag), "unknown", tag) - - -class SendEndpointTest(unittest.TestCase): - def test_default_host_is_posthog_us(self): - with mock.patch("urllib.request.urlopen") as urlopen: - urlopen.return_value = FakeResponse(b"") - module.send({}, "https://us.i.posthog.com") - self.assertEqual( - urlopen.call_args.args[0].full_url, "https://us.i.posthog.com/batch/" - ) - - -class AssetMatchingTest(unittest.TestCase): - def test_sidecars_and_nonmatching_names_are_skipped(self): - self.assertIsNone(module.asset_os_arch("checksums.txt")) - self.assertIsNone(module.asset_os_arch("codeaf-linux-amd64.tar.gz")) - self.assertIsNone(module.asset_os_arch("codeaf-solaris-sparc")) - self.assertEqual(module.asset_os_arch("codeaf-linux-amd64"), ("linux", "amd64")) - self.assertEqual( - module.asset_os_arch("codeaf-windows-amd64.exe"), ("windows", "amd64") - ) - - -class DraftTest(unittest.TestCase): - def test_draft_releases_produce_no_events(self): - draft = { - "tag_name": "v9.9.9", - "draft": True, - "prerelease": False, - "assets": [{"name": "codeaf-linux-amd64", "download_count": 5}], - } - self.assertEqual(module.build_events([draft], "2026-02-11"), []) - - -if __name__ == "__main__": - unittest.main() From b38e4dbe501e712fd98e0f89447bbaa9ced54787 Mon Sep 17 00:00:00 2001 From: Abir Abbas Date: Mon, 2 Nov 2026 09:43:07 -0500 Subject: [PATCH 2/2] docs(changes): entry for the Release downloads removal Co-Authored-By: Claude Opus 5.5 --- .../unreleased/2193-release-downloads-workflow.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 docs/changes/unreleased/2193-release-downloads-workflow.md diff --git a/docs/changes/unreleased/2193-release-downloads-workflow.md b/docs/changes/unreleased/2193-release-downloads-workflow.md new file mode 100644 index 0000000000..b860dbb096 --- /dev/null +++ b/docs/changes/unreleased/2193-release-downloads-workflow.md @@ -0,0 +1,11 @@ +--- +kind: removed +title: The daily Release downloads workflow and its PostHog script are gone +pr: 2193 +surface: [build, docs] +invalidates: + - "A daily workflow, .github/workflows/release-downloads.yml, sent GitHub release download counts to PostHog as codeaf:release_downloads; that workflow and scripts/release_downloads.py no longer exist and nothing reports download counts." +--- + +It failed on every run because its PostHog project key was never set, and the +counts are already visible on the GitHub release pages.