Skip to content

fix(browseros): product-aware release CLI with bounded listing#1505

Merged
Nikhil (shadowfax92) merged 6 commits into
mainfrom
fix/release-cli-per-product
Jul 2, 2026
Merged

fix(browseros): product-aware release CLI with bounded listing#1505
Nikhil (shadowfax92) merged 6 commits into
mainfrom
fix/release-cli-per-product

Conversation

@shadowfax92

Copy link
Copy Markdown
Contributor

Summary

  • Thread --product through the whole release CLI (default browseros): create_release_context now resolves the product via the registry, and every fetch_all_release_metadata call site (list/appcast/download, joining publish/github) passes ctx.product.id — BrowserClaw releases are no longer invisible.
  • Fix version listing under the productized R2 layout: list_all_versions(release_prefix, env) lists releases/<prefix>/, and list_legacy_versions surfaces pre-product bare releases/<version>/ entries under browseros tagged (legacy) (dedupe prefers productized).
  • Bound browseros release list to the newest 5 versions per product (both products by default, in short sections) with -n/--limit override and --all for everything; release list <version> keeps the product-aware artifact detail view.
  • Convert the flag-soup callback (--list/--appcast/--publish/--download) into real typer subcommands: release list|appcast|publish|download, keeping release github create (+ --product, long-only since -p stays --publish there).
  • Kill the last if product.id == "browseros" branch in get_download_path_mapping — the alias map is built purely from product.artifact_prefix, with a golden test pinning the browseros output byte-identical to the old constant.

Design

Thin subcommands over product-parameterized helpers: the Step-module architecture stays (clean merge surface for the in-flight feed-publisher branch — appcast still only prints snippets, no feed publishing here). Listing splits into two composable R2 helpers sharing one paginated CommonPrefixes walker; merge/limit logic is pure and unit-tested without boto3. Legacy bare releases attach to the default product because they predate the registry; their detail view already works via get_release_json's existing browseros fallback (untouched).

Test plan

  • uv run python -m unittest discover -s bos_build -t . -p "*_test.py" — 384 tests OK (30 new: golden alias maps, prefix/delimiter/pagination listing, legacy shape filtering, merge/dedupe/limit, CLI surface incl. conflict + unknown-product errors)
  • uv run ruff check bos_build — clean
  • uv run browseros release list --help / release --help / build --list / product doctor — all green
  • Live smoke against production R2: bounded sections with … 112 more (use --all) hint, browserclaw section, (legacy) tags, legacy version detail fallback

🤖 Generated with Claude Code

@shadowfax92

Copy link
Copy Markdown
Contributor Author

Greptile (@greptileai) review

@shadowfax92

Copy link
Copy Markdown
Contributor Author

Claude (@claude) review

@github-actions github-actions Bot added the fix label Jul 2, 2026
@claude

claude Bot commented Jul 2, 2026

Copy link
Copy Markdown

Claude finished Nikhil (@shadowfax92)'s task in 2m 20s —— View job


Code Review

  • Gathering context
  • Reading changed files
  • Analyzing code
  • Posting review

Overall this is a well-structured refactor. The subcommand conversion is clean, the golden test for get_download_path_mapping is good regression protection, and the pagination implementation is correct. A few issues worth addressing before merge:


Bugs

1. Hardcoded "BrowserOS" in generate_appcast_item title

common.py:196:

return f"""<item>
  <title>BrowserOS - {version}</title>

This title is hardcoded regardless of the product argument. Any BrowserClaw appcast snippet will incorrectly identify itself as "BrowserOS". The function doesn't receive a product parameter, so it can't be product-aware. Needs either a display_name parameter or to derive the name from the caller.

Same issue in generate_release_notes at common.py:214:

notes = f"""## BrowserOS v{version}

Both functions predate this PR, but since the PR is threading product-awareness through the whole CLI, these two spots now become incorrect for BrowserClaw.

Fix this →


2. Silent partial result on mid-pagination R2 failure

common.py:103-107:

try:
    response = client.list_objects_v2(**kwargs)
except Exception as e:
    log_warning(f"R2 listing failed for {prefix}: {e}")
    break

If the listing fails mid-pagination (e.g., on page 3 of 5), the function silently returns only the versions collected so far. The warning says the listing failed, but the caller can't distinguish "empty" from "partial." Worse, _list_version_details called on a partially-listed version that wasn't collected would silently report "No release metadata found" — a confusing failure for an existing release.

Minor fix: return None (or raise) on failure so callers can surface "listing incomplete" instead of silently acting on a partial set. At minimum the warning could mention it's a partial result.


Design Observations

3. _resolve_product called twice for release list --product X

release.py:159-160:

products = [_resolve_product(product)] if product else list(PRODUCTS.values())
release_ctx = create_release_context("", product=product)  # calls _resolve_product again internally

create_release_context calls _resolve_product(product) too. For the error path (nosuch product), this means log_error fires twice. You can see this in test_unknown_product_names_valid_ids — the test only checks the exit code, not that the error appears once. Consider:

resolved = _resolve_product(product) if product else None
products = [resolved] if resolved else list(PRODUCTS.values())
release_ctx = create_release_context("", product=resolved.id if resolved else None)

4. ctx.product is unused in the multi-product listing path

release.py:160:

release_ctx = create_release_context("", product=product)

The context's product field is set to browseros (or the requested product) but ListModule ignores ctx.product during multi-product listing — it uses self.products instead. The context product is only read in _list_version_details. This works correctly but creates a slightly misleading context object. Worth a comment or, if the context pattern allows it, leaving product as the default without the explicit argument here.


Test Gaps

5. _FakeR2Client pagination indexing is fragile

common_test.py:113-115:

def list_objects_v2(self, **kwargs):
    return pages[int(kwargs.get("ContinuationToken", 0))]

This works because the fake returns str(1) as the continuation token and then does int("1") as a list index. It's clever, but breaks if a real continuation token format is ever adopted in tests or if a token value doesn't cast cleanly to an int. A more robust pattern would be a deque popping pages, with no token dependency at all.

6. No test for the multi-product listing output

list_test.py has CollectProductRowsTest and PrintProductSectionTest, but there's no end-to-end test of ListModule._list_all_versions printing both products in sequence. Currently only individual helpers are tested in isolation. Not a blocker — but a single integration-style test covering both sections would catch regressions like the product iteration order or the trailing "Use release list <version>" hint.


Minor

7. version_sort_key is sensitive to inconsistent version field counts

common.py:64-72: Python tuple comparison means (1, 2) < (1, 2, 0), so "1.2" sorts before "1.2.0". This is fine in practice since version formats are consistent, but worth a note if 4-part versions (chromium-style 1.2.3.4) can ever land in releases/<prefix>/.

8. 1.2.3.4 passes the legacy version filter

common_test.py:186-193 explicitly tests that "1.2.3.4" is kept as a valid version name. The regex r"\d+(\.\d+)*" does match it. Confirm this is intentional (4-part versions considered valid legacy entries, not product-prefix-like).


What's working well

  • The subcommand conversion (list|appcast|publish|download) is a clear UX improvement over the old flag soup — the --help test verifying the old --list/--appcast flags are gone is a nice regression guard.
  • merge_product_versions deduplication (productized wins over legacy) is correct and well-covered.
  • The golden test for get_download_path_mapping is excellent — byte-identical comparison against the old constant gives high confidence the refactor didn't silently rename any download paths.
  • The --all / limit=None design is clean and the hint message … N more (use --all) is user-friendly.
  • _list_common_prefixes correctly handles the paginated S3-style continuation token loop.

@shadowfax92 Nikhil (shadowfax92) merged commit bd594ff into main Jul 2, 2026
6 of 7 checks passed
@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR threads --product through the entire release CLI, converts the old flag-soup callback into real typer subcommands (list, appcast, publish, download), fixes version listing under the productized R2 layout with a bounded per-product display, and removes the last hardcoded browseros branch in get_download_path_mapping.

  • Subcommand refactor: release list|appcast|publish|download|github replace the former --list/--appcast/--publish/--download flags; release list gains -n/--limit and --all bounding, and surfacing of pre-product (legacy) versions via a dedicated list_legacy_versions helper.
  • Product-aware R2 listing: list_all_versions(release_prefix, env) now queries releases/<prefix>/ per product; list_legacy_versions filters bare releases/<version>/ entries by shape (digits-and-dots regex) and attaches them only to the default product.
  • get_download_path_mapping cleanup: the old static constant and if product.id == "browseros" branch are removed; the mapping is built purely from product.artifact_prefix, with a golden test pinning BrowserOS output byte-identical to the old constant.

Confidence Score: 3/5

The CLI restructuring and listing logic are solid, but the appcast and GitHub release-note commands now silently emit wrong product names for BrowserClaw.

The product threading through fetch_all_release_metadata is correct everywhere, and the listing, download, and publish paths work properly. However, generate_appcast_item and generate_release_notes both hardcode 'BrowserOS' in their output — now that --product browserclaw is a reachable CLI path for both appcast and github create, operators using those commands for BrowserClaw will get XML with the wrong title/link and GitHub release notes with the wrong header.

packages/browseros/bos_build/release/common.py — generate_appcast_item (line ~194) and generate_release_notes (line ~214) need a product parameter before BrowserClaw appcast/release-notes are usable.

Important Files Changed

Filename Overview
packages/browseros/bos_build/cli/release.py Flag-soup callbacks replaced with proper typer subcommands; product threading and conflict detection are correct, minor double _resolve_product call when --product is given with list.
packages/browseros/bos_build/release/common.py R2 listing refactored into composable helpers; hardcoded 'BrowserOS' string in generate_appcast_item title/link and generate_release_notes header are now active bugs for the newly-accessible BrowserClaw code paths.
packages/browseros/bos_build/release/list.py New merge/limit/collect helpers are clean; legacy-version deduplication logic is correct and well-tested.
packages/browseros/bos_build/release/appcast.py Now passes ctx.product.id to fetch_all_release_metadata (correct), but generate_appcast_item still hardcodes 'BrowserOS' in the emitted XML, making BrowserClaw appcast output wrong.
packages/browseros/bos_build/release/download.py One-line change to pass ctx.product.id — correct, minimal, no issues.
packages/browseros/bos_build/cli/release_test.py New test file with solid CLI surface coverage: conflict detection, limit/product filtering, product context propagation, and unknown-product error messaging.
packages/browseros/bos_build/release/common_test.py Expanded with golden constant tests, R2 listing pagination tests, and legacy shape filtering — all well-structured.
packages/browseros/bos_build/release/list_test.py New test file covering merge/dedupe, limit/slice, legacy-product scoping, and detail-view product propagation — good coverage of new helpers.
packages/browseros/bos_build/release/init.py Exports list_legacy_versions to public API — straightforward additive change.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    CLI["browseros release"] --> LIST["list [VERSION]"]
    CLI --> APPCAST["appcast --version"]
    CLI --> PUBLISH["publish --version"]
    CLI --> DOWNLOAD["download --version"]
    CLI --> GITHUB["github create --version"]

    LIST -- "VERSION given" --> CTX1["create_release_context(version, product)"]
    LIST -- "no VERSION" --> RESOLVE["_resolve_product(product)"]
    RESOLVE --> PRODUCTS_ALL["PRODUCTS.values() or single product"]
    PRODUCTS_ALL --> LIST_MODULE["ListModule(products, limit)"]
    LIST_MODULE --> COLLECT["collect_product_rows(product, env)"]
    COLLECT --> ALL_VER["list_all_versions(release_prefix, env)\nreleases/prefix/"]
    COLLECT -- "default product only" --> LEG["list_legacy_versions(env)\nreleases/ filter digits-and-dots"]
    ALL_VER & LEG --> MERGE["merge_product_versions()\ndedupe + sort desc"]
    MERGE --> LIMIT["apply_list_limit(rows, limit)"]
    LIMIT --> PRINT["_print_product_section()"]

    CTX1 --> DETAIL["ListModule._list_version_details()\nfetch_all_release_metadata(version, env, product.id)"]

    APPCAST --> CTX2["create_release_context(version, product)"]
    CTX2 --> APPM["AppcastModule\nfetch_all_release_metadata(version, env, product.id)"]
    APPM --> GEN["generate_appcast_item()\n hardcodes BrowserOS title/link"]

    GITHUB --> CTX3["create_release_context(version, repo, product)"]
    CTX3 --> GHM["GithubModule\ngenerate_release_notes()\n hardcodes BrowserOS header"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    CLI["browseros release"] --> LIST["list [VERSION]"]
    CLI --> APPCAST["appcast --version"]
    CLI --> PUBLISH["publish --version"]
    CLI --> DOWNLOAD["download --version"]
    CLI --> GITHUB["github create --version"]

    LIST -- "VERSION given" --> CTX1["create_release_context(version, product)"]
    LIST -- "no VERSION" --> RESOLVE["_resolve_product(product)"]
    RESOLVE --> PRODUCTS_ALL["PRODUCTS.values() or single product"]
    PRODUCTS_ALL --> LIST_MODULE["ListModule(products, limit)"]
    LIST_MODULE --> COLLECT["collect_product_rows(product, env)"]
    COLLECT --> ALL_VER["list_all_versions(release_prefix, env)\nreleases/prefix/"]
    COLLECT -- "default product only" --> LEG["list_legacy_versions(env)\nreleases/ filter digits-and-dots"]
    ALL_VER & LEG --> MERGE["merge_product_versions()\ndedupe + sort desc"]
    MERGE --> LIMIT["apply_list_limit(rows, limit)"]
    LIMIT --> PRINT["_print_product_section()"]

    CTX1 --> DETAIL["ListModule._list_version_details()\nfetch_all_release_metadata(version, env, product.id)"]

    APPCAST --> CTX2["create_release_context(version, product)"]
    CTX2 --> APPM["AppcastModule\nfetch_all_release_metadata(version, env, product.id)"]
    APPM --> GEN["generate_appcast_item()\n hardcodes BrowserOS title/link"]

    GITHUB --> CTX3["create_release_context(version, repo, product)"]
    CTX3 --> GHM["GithubModule\ngenerate_release_notes()\n hardcodes BrowserOS header"]
Loading

Comments Outside Diff (2)

  1. packages/browseros/bos_build/release/common.py, line 194-203 (link)

    P1 Hardcoded "BrowserOS" in appcast XML title and link

    generate_appcast_item produces <title>BrowserOS - {version}</title> and <link>https://browseros.com</link> regardless of the product. Now that AppcastModule.execute passes ctx.product.id to fetch the correct metadata, running release appcast --product browserclaw --version X will fetch BrowserClaw artifacts but emit XML that still identifies the product as "BrowserOS" — the feed consumers (Sparkle, WinSparkle) would see the wrong app name and homepage link. The function signature needs a product: ProductDescriptor parameter so the title and link can be derived from it.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: packages/browseros/bos_build/release/common.py
    Line: 194-203
    
    Comment:
    **Hardcoded "BrowserOS" in appcast XML title and link**
    
    `generate_appcast_item` produces `<title>BrowserOS - {version}</title>` and `<link>https://browseros.com</link>` regardless of the product. Now that `AppcastModule.execute` passes `ctx.product.id` to fetch the correct metadata, running `release appcast --product browserclaw --version X` will fetch BrowserClaw artifacts but emit XML that still identifies the product as "BrowserOS" — the feed consumers (Sparkle, WinSparkle) would see the wrong app name and homepage link. The function signature needs a `product: ProductDescriptor` parameter so the title and link can be derived from it.
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. packages/browseros/bos_build/release/common.py, line 214-227 (link)

    P1 Hardcoded "BrowserOS" in GitHub release notes header

    generate_release_notes opens with ## BrowserOS v{version} unconditionally. The GithubModule now accepts --product (threaded in via create_release_context), so release github create --product browserclaw --version X will create a GitHub release with notes titled "## BrowserOS v{version}" instead of "## BrowserClaw v{version}". Like generate_appcast_item, this function needs a product parameter so the header can use product.display_name.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: packages/browseros/bos_build/release/common.py
    Line: 214-227
    
    Comment:
    **Hardcoded "BrowserOS" in GitHub release notes header**
    
    `generate_release_notes` opens with `## BrowserOS v{version}` unconditionally. The `GithubModule` now accepts `--product` (threaded in via `create_release_context`), so `release github create --product browserclaw --version X` will create a GitHub release with notes titled "## BrowserOS v{version}" instead of "## BrowserClaw v{version}". Like `generate_appcast_item`, this function needs a `product` parameter so the header can use `product.display_name`.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
packages/browseros/bos_build/release/common.py:194-203
**Hardcoded "BrowserOS" in appcast XML title and link**

`generate_appcast_item` produces `<title>BrowserOS - {version}</title>` and `<link>https://browseros.com</link>` regardless of the product. Now that `AppcastModule.execute` passes `ctx.product.id` to fetch the correct metadata, running `release appcast --product browserclaw --version X` will fetch BrowserClaw artifacts but emit XML that still identifies the product as "BrowserOS" — the feed consumers (Sparkle, WinSparkle) would see the wrong app name and homepage link. The function signature needs a `product: ProductDescriptor` parameter so the title and link can be derived from it.

### Issue 2 of 3
packages/browseros/bos_build/release/common.py:214-227
**Hardcoded "BrowserOS" in GitHub release notes header**

`generate_release_notes` opens with `## BrowserOS v{version}` unconditionally. The `GithubModule` now accepts `--product` (threaded in via `create_release_context`), so `release github create --product browserclaw --version X` will create a GitHub release with notes titled "## BrowserOS v{version}" instead of "## BrowserClaw v{version}". Like `generate_appcast_item`, this function needs a `product` parameter so the header can use `product.display_name`.

### Issue 3 of 3
packages/browseros/bos_build/cli/release.py:159-165
**`_resolve_product` is called twice when `--product` is supplied**

When `product` is not `None`, `_resolve_product(product)` fires on line 159 and then again inside `create_release_context("", product=product)` on line 160. The second call is harmless (fast dict lookup), but the duplication is easy to remove by building `products` from the already-resolved descriptor rather than from the `product` string again.

Reviews (1): Last reviewed commit: "fix(browseros): address review findings ..." | Re-trigger Greptile

@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces a flag-soup callback (--list/--appcast/--publish/--download) with proper Typer subcommands (release list|appcast|publish|download), threads --product through the entire release CLI so BrowserClaw releases are no longer invisible, and fixes the R2 version-listing layout by splitting it into productized and legacy bare-prefix walkers.

  • Product-aware release flow: create_release_context now resolves the product via the registry; fetch_all_release_metadata calls in appcast, download, list, publish, and github all pass ctx.product.id.
  • Bounded listing: release list defaults to the newest 5 versions per product with -n/--limit and --all overrides; legacy releases/<version>/ entries surface under BrowserOS tagged (legacy) with dedupe preferring the productized path.
  • Dead constant removed: DOWNLOAD_PATH_MAPPING is replaced by a pure derivation from product.artifact_prefix, with a golden test pinning byte-identical output for BrowserOS.

Confidence Score: 4/5

Safe to merge — all call sites that fetch release metadata now pass the product ID, the removed constant is verified byte-identical via a golden test, and 30 new tests cover listing logic, pagination, merge/dedupe, and the CLI surface.

The core change is well-tested and logically sound. The only issues are cosmetic: an inaccurate comment in the exception handler, a double _resolve_product call in the listing path, an or-based None check that conflates empty-list with unset, and no warning when --all silently discards a -n value. None of these affect production behavior today.

No files require special attention; the minor style observations are concentrated in cli/release.py, release/list.py, and release/common.py.

Important Files Changed

Filename Overview
packages/browseros/bos_build/cli/release.py Flag-soup converted to real subcommands; --product threaded through all commands; minor redundancy: _resolve_product called twice when product filter is used in list path
packages/browseros/bos_build/release/common.py DOWNLOAD_PATH_MAPPING constant replaced with pure derivation from artifact_prefix; listing refactored into composable helpers; misleading inline comment in exception handler
packages/browseros/bos_build/release/list.py ListModule gains products/limit constructor args; merge/limit helpers are correct; self.products or fallback silently treats [] as None; --all silently wins over -n
packages/browseros/bos_build/cli/release_test.py New test file covering subcommand surface, conflict errors, unknown product, and --all/limit behavior
packages/browseros/bos_build/release/list_test.py New test file covering merge, limit, legacy attachment, print sections, and detail view dispatch
packages/browseros/bos_build/release/common_test.py Extended with golden-constant tests, _FakeR2Client pagination harness, and list_legacy_versions filtering
packages/browseros/bos_build/release/appcast.py One-line change: passes ctx.product.id to fetch_all_release_metadata
packages/browseros/bos_build/release/download.py One-line change: passes ctx.product.id to fetch_all_release_metadata
packages/browseros/bos_build/release/init.py Exports list_legacy_versions; minimal correct change

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[browseros release cmd] --> B{subcommand?}
    B --> C[list]
    B --> D[appcast]
    B --> E[publish]
    B --> F[download]
    B --> G[github create]
    C --> H{version arg?}
    H -- yes --> I[create_release_context version + product]
    H -- no --> J[resolve products list]
    J --> K[create_release_context empty version]
    I --> L[ListModule no products arg]
    K --> M[ListModule products + limit]
    L --> N[_list_version_details]
    M --> O[_list_all_versions]
    O --> P[list_all_versions]
    O --> Q{default product?}
    Q -- yes --> R[list_legacy_versions]
    Q -- no --> S[skip legacy]
    P & R --> T[merge_product_versions]
    T --> U[apply_list_limit]
    D & E & F & G --> V[create_release_context version + product]
    V --> W[Module.execute fetch_all_release_metadata ctx.product.id]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[browseros release cmd] --> B{subcommand?}
    B --> C[list]
    B --> D[appcast]
    B --> E[publish]
    B --> F[download]
    B --> G[github create]
    C --> H{version arg?}
    H -- yes --> I[create_release_context version + product]
    H -- no --> J[resolve products list]
    J --> K[create_release_context empty version]
    I --> L[ListModule no products arg]
    K --> M[ListModule products + limit]
    L --> N[_list_version_details]
    M --> O[_list_all_versions]
    O --> P[list_all_versions]
    O --> Q{default product?}
    Q -- yes --> R[list_legacy_versions]
    Q -- no --> S[skip legacy]
    P & R --> T[merge_product_versions]
    T --> U[apply_list_limit]
    D & E & F & G --> V[create_release_context version + product]
    V --> W[Module.execute fetch_all_release_metadata ctx.product.id]
Loading
Prompt To Fix All With AI
Fix the following 4 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 4
packages/browseros/bos_build/release/common.py:104-106
The comment "A partial listing renders as '(no releases found)'" is inaccurate. If names were already collected before the exception fires on a continuation page, those entries are returned — not an empty list. The comment would only hold if the first page itself raises.

```suggestion
        except Exception as e:
            # Partial listing: entries collected so far are returned; caller
            # may see fewer versions than exist in R2.
            log_warning(f"R2 listing failed for {prefix}: {e}")
```

### Issue 2 of 4
packages/browseros/bos_build/cli/release.py:189-195
**Redundant `_resolve_product` call and unused `ctx.product` in listing path**

When `product` is not `None`, `_resolve_product(product)` is called once to build the `products` list, and then a second time inside `create_release_context("", product=product)`. The resulting `release_ctx.product` is never accessed in the non-version listing path — `_list_all_versions` uses `self.products` from the `ListModule` argument, while `ctx.env` is the only field actually consumed from the context.

### Issue 3 of 4
packages/browseros/bos_build/release/list.py:98
`self.products or list(PRODUCTS.values())` treats an explicit empty list the same as `None` (unset). If a caller ever passes `ListModule(products=[])` intending to list no products, it silently falls back to all products. Using `if self.products is not None` is more explicit and guards the default correctly.

```suggestion
        products = self.products if self.products is not None else list(PRODUCTS.values())
```

### Issue 4 of 4
packages/browseros/bos_build/cli/release.py:153-154
**`-n` is silently ignored when `--all` is combined with it**

`limit=None if show_all else limit` means `browseros release list --all -n 3` behaves identically to `--all` alone, with `-n 3` having no effect and no warning. Consider emitting a warning or making the combination an error, similar to how conflicting `version_arg`/`--version` is handled.

Reviews (2): Last reviewed commit: "fix(browseros): address review findings ..." | Re-trigger Greptile

"""List all available versions"""
versions = list_all_versions(ctx.env)
"""Print a bounded newest-first section per product"""
products = self.products or list(PRODUCTS.values())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 self.products or list(PRODUCTS.values()) treats an explicit empty list the same as None (unset). If a caller ever passes ListModule(products=[]) intending to list no products, it silently falls back to all products. Using if self.products is not None is more explicit and guards the default correctly.

Suggested change
products = self.products or list(PRODUCTS.values())
products = self.products if self.products is not None else list(PRODUCTS.values())
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/browseros/bos_build/release/list.py
Line: 98

Comment:
`self.products or list(PRODUCTS.values())` treats an explicit empty list the same as `None` (unset). If a caller ever passes `ListModule(products=[])` intending to list no products, it silently falls back to all products. Using `if self.products is not None` is more explicit and guards the default correctly.

```suggestion
        products = self.products if self.products is not None else list(PRODUCTS.values())
```

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant