Skip to content

Conversation

@rmitchellscott
Copy link
Contributor

@rmitchellscott rmitchellscott commented Nov 23, 2025

This PR adds comprehensive support for Paper Pro / Paper Pro Move devices and software versions 3.22-3.23.

Key Changes:

  • Add support for Paper Pro Move devices
  • Add 3.22 and 3.23 software versions
  • Add "Backup version" (version installed to inactive partition) to status command
  • Enable restore functionality for Paper Pro (Move) devices, including handling the 3.20/3.22 partition schema change
  • Add support for downgrades to 3.20 and below using bootloader updates on Paper Pro
  • Integrate additional external software provider for software downloads

I have tested:

  • Paper Pro, 3.20 -> 3.23 (install and restore)
  • Paper Pro, 3.23 -> 3.20 (install and restore)
  • Paper Pro, 3.22 -> 3.23 (install and restore)
  • Paper Pro Move, 3.21 -> 3.23 (install and restore)
  • Paper Pro Move, 3.23 -> 3.21 (restore)
  • reMarkable 2, 3.22 -> 3.23 (install and restore)
  • reMarkable 2, 3.23 -> 3.22 (install and restore)
  • reMarkable 1, 3.22 -> 3.23 (install and restore)
  • reMarkable 1, 3.23 -> 3.22 (install and restore)

Summary by CodeRabbit

  • New Features

    • Adds Paper Pro Move (RMPPM) support in listings/status with Backup version shown and a Move variant block
    • SWU metadata-driven install flow with bootloader-aware upgrade/downgrade handling and automatic bootloader extraction
    • Multi-provider SWU download with provider URL fallback and retries
  • Bug Fixes

    • Improved status/restore reporting and clearer error messaging
  • Documentation

    • Bootloader update instructions, RMPPM example workflows, and extraction guidance
  • Tests

    • New tests for bootloader-boundary downgrade detection and version handling
  • Chores

    • Dependency updates and SWU verification added to test flow

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 23, 2025

Walkthrough

Adds ReMarkable Paper Pro Move (RMPPM) support, multi-provider SWU download and metadata extraction, bootloader-aware upgrade/downgrade handling with bootloader extraction/upload for Paper Pro, consolidated update-image/fuse import handling, and status output now includes backup partition version.

Changes

Cohort / File(s) Summary
Hardware support & device flow
codexctl/device.py
Adds HardwareType.RMPPM; expands hardware parsing/mappings; get_device_status() now returns backup version; adds _read_version_from_path, _get_backup_partition_version, _get_paper_pro_partition_info; updates restore flow for new engine and partitions; adds _update_paper_pro_bootloader(); install_sw_update() accepts optional bootloader_files.
Update manager & downloads
codexctl/updates.py
Adds remarkableppm_versions and external_provider_urls; get_remarkable_versions() returns provider URLs list; get_latest_version()/download_version() handle RMPPM and iterate providers until success; adds is_bootloader_boundary_downgrade(); improves download logging and non-200 handling.
SWU analysis & import handling
codexctl/analysis.py, codexctl/__init__.py
Adds import/usage of HardwareType; refactors dynamic imports to use UpdateImage for SWU metadata extraction and consolidates import error messaging for extraction vs mount paths.
Version metadata
data/version-ids.json
Adds/updates entries for remarkable1, remarkable2, remarkablepp; introduces remarkableppm section; replaces external-provider-url with external-provider-urls array and moves related metadata.
Docs & tests
README.md, tests/test.py
README documents Paper Pro bootloader update flow and extraction examples; tests add coverage for is_bootloader_boundary_downgrade() and adjust version/toltec assertions.
CLI error handling & build
codexctl/__init__.py, Makefile, pyproject.toml, requirements.txt
Wraps main command with SystemError catch; Makefile gains SWU build/test targets and verification; bumps remarkable-update-image and remarkable-update-fuse versions and adds build-backend entry.
CI workflow
.github/workflows/main.yml
Adds "Free up disk space" step that removes many system directories before job steps.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as codexctl CLI
    participant UpdateMgr as UpdateManager
    participant Analysis as SWU Analysis
    participant Provider as External Provider(s)
    participant Device as DeviceManager
    participant FTP as Device FTP

    User->>CLI: install --target rmppm 3.23.0.64
    CLI->>UpdateMgr: get_latest_version(target)
    UpdateMgr-->>CLI: candidate_version_info

    CLI->>UpdateMgr: download_version(candidate)
    loop try providers
        UpdateMgr->>Provider: HTTP GET SWU (provider URL)
        Provider-->>UpdateMgr: SWU file or error
    end
    UpdateMgr-->>CLI: downloaded_swu_file

    CLI->>Analysis: extract_swu_metadata(downloaded_swu_file)
    Analysis-->>CLI: (version, hardware info)

    CLI->>UpdateMgr: is_bootloader_boundary_downgrade(current_version, target_version)
    UpdateMgr-->>CLI: boolean

    alt Bootloader boundary crossed
        CLI->>UpdateMgr: obtain current SWU for current_version (download or reuse)
        UpdateMgr-->>CLI: current_swu_file
        CLI->>Analysis: extract update-bootloader.sh & imx-boot from current_swu_file
        Analysis-->>CLI: bootloader_files (script, imx-boot)
        CLI->>Device: install_sw_update(target_swu_file, bootloader_files)
        Device->>FTP: upload target SWU
        FTP-->>Device: ack
        Device->>FTP: upload bootloader script & imx-boot
        FTP-->>Device: ack
        Device->>Device: run preinst/postinst and apply bootloader update
    else Normal update
        CLI->>Device: install_sw_update(target_swu_file, bootloader_files=None)
        Device->>FTP: upload and install SWU
    end

    Device-->>CLI: installation result
    CLI-->>User: result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

  • Areas needing extra attention:
    • Bootloader boundary detection and version parsing in UpdateManager.is_bootloader_boundary_downgrade()
    • Partition layout, inactive/backup partition discovery, and version reads in DeviceManager (_read_version_from_path, _get_paper_pro_partition_info, _get_backup_partition_version)
    • Bootloader extraction, validation, and upload flow (_update_paper_pro_bootloader and install_sw_update integration)
    • Multi-provider download retry logic, logging, and failure aggregation in download_version()
    • Consistency between data/version-ids.json structure and code expectations for external_provider_urls

Possibly related PRs

Suggested reviewers

  • Jayy001

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 74.29% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: support for Paper Pro Move devices, software versions 3.22 & 3.23, Paper Pro restore functionality, and SWU extraction capabilities.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0e27851 and dd2951f.

📒 Files selected for processing (11)
  • .github/workflows/main.yml (2 hunks)
  • Makefile (4 hunks)
  • README.md (4 hunks)
  • codexctl/__init__.py (7 hunks)
  • codexctl/analysis.py (1 hunks)
  • codexctl/device.py (15 hunks)
  • codexctl/updates.py (7 hunks)
  • data/version-ids.json (4 hunks)
  • pyproject.toml (1 hunks)
  • requirements.txt (1 hunks)
  • tests/test.py (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
codexctl/device.py (1)

571-609: Minor: remove unused old_part_label in Paper Pro restore logic

In the RMPP/RMPPM restore branch, old_part_label is computed but never used:

old_part_label = "a" if current_part == 2 else "b"

You can drop this assignment (or include it in the log output if you intended to display the old label) to avoid the unused‑variable warning.

codexctl/updates.py (1)

31-36: UpdateManager: new hardware, provider URLs, and boundary helper are mostly solid; consider a small guard

  • Initialising self.remarkableppm_versions and self.external_provider_urls from the new get_remarkable_versions() tuple lines up with the updated data/version-ids.json structure.
  • Normalising "external-provider-urls" / "external-provider-url" to a list and then iterating over self.external_provider_urls in download_version gives a clean multi‑provider fallback, and lowering non‑200 responses to debug avoids noisy logs when a primary mirror is down.
  • The new HardwareType.RMPPM branch in both get_latest_version and download_version correctly picks up self.remarkableppm_versions and uses the same code path as the other devices.
  • is_bootloader_boundary_downgrade’s list comparison against [3, 22] behaves as intended for versions like 3.23.0.643.20.0.92, while safely returning False on malformed inputs.

One small robustness improvement: if neither "external-provider-urls" nor "external-provider-url" is present (e.g. a locally edited or very old version-ids.json), provider_urls ends up None and download_version will later fail with a TypeError when iterating it. You could fail fast with a clearer error:

-        provider_urls = contents.get("external-provider-urls", contents.get("external-provider-url"))
-        if isinstance(provider_urls, str):
-            provider_urls = [provider_urls]
+        provider_urls = contents.get("external-provider-urls", contents.get("external-provider-url"))
+        if isinstance(provider_urls, str):
+            provider_urls = [provider_urls]
+        if provider_urls is None:
+            raise SystemError("version-ids.json is missing external provider URLs")

That makes configuration problems easier to diagnose.

Also applies to: 38-43, 86-96, 124-142, 174-176, 204-220, 227-238, 243-258, 310-327, 395-419

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0e27851 and 6e558b6.

📒 Files selected for processing (5)
  • codexctl/__init__.py (5 hunks)
  • codexctl/cpio.py (1 hunks)
  • codexctl/device.py (14 hunks)
  • codexctl/updates.py (7 hunks)
  • data/version-ids.json (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T16:37:10.452Z
Learnt from: Eeems
Repo: Jayy001/codexctl PR: 140
File: codexctl/__init__.py:54-55
Timestamp: 2025-09-10T16:37:10.452Z
Learning: In codexctl/__init__.py, the UpdateManager methods (get_latest_version, get_toltec_version, download_version) all expect HardwareType enum parameters, not string parameters.

Applied to files:

  • codexctl/device.py
🧬 Code graph analysis (2)
codexctl/__init__.py (4)
codexctl/device.py (5)
  • HardwareType (20-85)
  • get_device_status (418-475)
  • restore_previous_version (553-632)
  • new_download_hw (52-61)
  • install_sw_update (661-773)
codexctl/cpio.py (1)
  • extract_cpio_files (54-150)
codexctl/analysis.py (1)
  • get_update_image (9-33)
codexctl/updates.py (2)
  • is_bootloader_boundary_downgrade (396-419)
  • download_version (174-258)
codexctl/updates.py (1)
codexctl/device.py (2)
  • HardwareType (20-85)
  • new_download_hw (52-61)
🪛 Ruff (0.14.5)
codexctl/cpio.py

23-23: Avoid specifying long messages outside the exception class

(TRY003)


29-29: Avoid specifying long messages outside the exception class

(TRY003)


77-77: Avoid specifying long messages outside the exception class

(TRY003)

codexctl/__init__.py

129-131: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


129-131: Avoid specifying long messages outside the exception class

(TRY003)


143-145: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


143-145: Avoid specifying long messages outside the exception class

(TRY003)


334-334: Avoid specifying long messages outside the exception class

(TRY003)


359-359: Avoid specifying long messages outside the exception class

(TRY003)

codexctl/updates.py

417-417: Consider moving this statement to an else block

(TRY300)

codexctl/device.py

37-37: Avoid specifying long messages outside the exception class

(TRY003)


49-49: Avoid specifying long messages outside the exception class

(TRY003)


85-85: Avoid specifying long messages outside the exception class

(TRY003)


308-308: Do not catch blind exception: Exception

(BLE001)


345-345: Probable insecure usage of temporary file or directory: "/tmp/mount_p"

(S108)


359-359: Do not catch blind exception: Exception

(BLE001)


366-366: Consider moving this statement to an else block

(TRY300)


368-368: Do not catch blind exception: Exception

(BLE001)


382-382: Avoid specifying long messages outside the exception class

(TRY003)


392-392: Do not catch blind exception: Exception

(BLE001)


403-403: Do not catch blind exception: Exception

(BLE001)


412-412: Do not catch blind exception: Exception

(BLE001)


576-576: Local variable old_part_label is assigned to but never used

Remove assignment to unused variable old_part_label

(F841)


581-581: Do not catch blind exception: Exception

(BLE001)


587-587: Do not catch blind exception: Exception

(BLE001)


795-795: Avoid specifying long messages outside the exception class

(TRY003)


799-799: Probable insecure usage of temporary file or directory: "/tmp/update-bootloader.sh"

(S108)


800-800: Probable insecure usage of temporary file or directory: "/tmp/imx-boot"

(S108)


828-828: Avoid specifying long messages outside the exception class

(TRY003)


837-837: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (6)
data/version-ids.json (1)

3-10: Version metadata structure matches UpdateManager expectations

The new 3.22/3.23 entries, remarkableppm section, and the top-level last-updated and external-provider-urls keys all line up with how UpdateManager.get_remarkable_versions() consumes this file; I don’t see structural issues here.

Also applies to: 193-205, 395-406, 452-466

codexctl/__init__.py (2)

70-87: Paper Pro Move listing integration looks correct

Wiring self.updater.remarkableppm_versions into the list command and gating it on HardwareType.RMPPM is consistent with the other hardware types; no issues here.

Based on learnings, this continues to keep UpdateManager calls using HardwareType enums rather than raw strings.


241-244: Status/restore output now correctly includes backup version

Using backup = remarkable.get_device_status()[4] and printing “Backup version” in status, plus echoing the backup version after restore, matches the new 5‑tuple signature of get_device_status and gives users clear feedback about the inactive partition.

Also applies to: 247-250

codexctl/device.py (3)

20-38: RMPPM hardware type and mappings are coherent

The new HardwareType.RMPPM variant, its parse aliases, and the old_download_hw/new_download_hw/swupdate_hw/toltec_type mappings are internally consistent and match how UpdateManager and SWUpdate expect to address the “chiappa” (Paper Pro Move) hardware.

Also applies to: 40-50, 52-62, 64-74, 76-86


289-319: Version/backup helpers integrate cleanly into device status

Refactoring version reading into _read_version_from_path, introducing _get_backup_partition_version to mount the inactive partition read‑only, and then returning (beta, old_update_engine, xochitl_version, version_id, backup_version) from get_device_status() gives a clear, reusable status surface and matches how __init__.py now renders status and restore. The fallback to "unknown" on failure is reasonable for a CLI tool.

Also applies to: 320-371, 418-475


661-714: Bootloader-aware SWU install flow on Paper Pro looks sound

Accepting optional bootloader_files in install_sw_update and, when present, calling _update_paper_pro_bootloader() after a successful SWUpdate run is a clean way to sequence bootloader updates for boundary downgrades. _update_paper_pro_bootloader itself correctly:

  • Uploads the script and imx-boot image to /tmp on the device,
  • Runs both preinst and postinst phases with exit‑code checks, and
  • Cleans up both remote and local temporary files in a finally block.

This should integrate well with the new bootloader extraction path in __init__.py.

Also applies to: 775-848

Copy link
Collaborator

@Eeems Eeems left a comment

Choose a reason for hiding this comment

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

Could you add some automated tests for UpdateManager.is_bootloader_boundary_downgrade

@rmitchellscott
Copy link
Contributor Author

rmitchellscott commented Nov 23, 2025

Could you add some automated tests for UpdateManager.is_bootloader_boundary_downgrade

I added unit tests for this.

@rmitchellscott rmitchellscott requested a review from Eeems November 23, 2025 20:37
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
codexctl/device.py (1)

418-475: get_device_status remote changes are good; local branch still has a decode bug

Using _read_version_from_path for the remote path and appending backup_version from _get_backup_partition_version() is a clean refactor and enables the new status output.

However, the local path still does:

with open("/usr/share/remarkable/update.conf") as file:
    xochitl_version = re.search(
        "(?<=REMARKABLE_RELEASE_VERSION=).*",
        file.read().decode("utf-8").strip("\n"),
    ).group()

Here file.read() already returns a str, so calling .decode("utf-8") will raise an AttributeError. This looks pre‑existing, but it means get_device_status() will fail when running on‑device with a local update.conf.

Minimal fix (without changing semantics) would be:

-                with open("/usr/share/remarkable/update.conf") as file:
-                    xochitl_version = re.search(
-                        "(?<=REMARKABLE_RELEASE_VERSION=).*",
-                        file.read().decode("utf-8").strip("\n"),
-                    ).group()
+                with open("/usr/share/remarkable/update.conf", encoding="utf-8") as file:
+                    xochitl_version = re.search(
+                        "(?<=REMARKABLE_RELEASE_VERSION=).*",
+                        file.read().strip("\n"),
+                    ).group()

(and similarly drop .decode("utf-8") in the /etc/os-release branch).

🧹 Nitpick comments (11)
README.md (1)

60-64: Add languages to new fenced blocks for linting and readability

The new examples are helpful; to satisfy markdownlint (MD040) and get syntax highlighting, please annotate the new code fences with a language, e.g.:

--  (Paper Pro Move) version 3.23.0.64 to a folder named `out`
-```
+–  (Paper Pro Move) version 3.23.0.64 to a folder named `out`
+```bash
 codexctl download 3.23.0.64 --hardware rmppm -o out
 codexctl install ./out/remarkable-production-image-3.23.0.64-chiappa-public.swu

@@
-- Extract a 3.11+ SWU firmware file to see its contents
- +– Extract a 3.11+ SWU firmware file to see its contents +bash
codexctl extract remarkable-production-image-3.22.0.64-ferrari-public.swu -o extracted




Also applies to: 87-90

</blockquote></details>
<details>
<summary>codexctl/__init__.py (4)</summary><blockquote>

`109-133`: **SWU vs .signed extraction routing is correct; improve dependency error handling**

The CPIO magic detection now correctly recognises both `"070701"` and `"070702"` SWU archives and routes them to `extract_swu_files`, while legacy `.signed` images still go through `get_update_image` and are written as a flat image, which matches the intended split.

To make the SWU path failure mode as user-friendly as the legacy one, consider wrapping the `extract_swu_files` import in a guarded block similar to the `.signed` path and chaining the original ImportError:

```diff
-                if magic in (b'070701', b'070702'):
-                    logger.info("Detected CPIO format (3.11+ SWU file)")
-                    from .analysis import extract_swu_files
+                if magic in (b'070701', b'070702'):
+                    logger.info("Detected CPIO format (3.11+ SWU file)")
+                    try:
+                        from .analysis import extract_swu_files
+                    except ImportError as err:
+                        raise ImportError(
+                            "remarkable_update_image is required for extracting SWU files. Please install it!"
+                        ) from err

This mirrors the old-format path and avoids confusing tracebacks if remarkable-update-image (which backs the SWU helpers) is missing.


134-140: Tighten ImportError re‑raise in mount path

The consolidated import for get_update_image and UpdateFS is good, but you currently drop the original ImportError context and static analysis flags this.

Minor tweak to preserve the cause and align with B904:

-            try:
-                from .analysis import get_update_image
-                from remarkable_update_fuse import UpdateFS
-            except ImportError:
-                raise ImportError(
-                    "remarkable_update_image and remarkable_update_fuse are required for mounting. Please install them!"
-                )
+            try:
+                from .analysis import get_update_image
+                from remarkable_update_fuse import UpdateFS
+            except ImportError as err:
+                raise ImportError(
+                    "remarkable_update_image and remarkable_update_fuse are required for mounting. Please install them!"
+                ) from err

235-245: Status/restore output correctly incorporates backup version

Using the new 5‑tuple get_device_status() return to print the backup version and then referencing index 4 when reporting the restored version is consistent and should be helpful to users.

If you want to avoid a second SFTP round‑trip on restore, you could capture the earlier backup value and reuse it instead of re‑calling get_device_status(), but that’s purely an optimisation.


261-299: SWU metadata and version extraction flow is sound, with minor UX nit

The update_file handling correctly:

  • Detects SWU files via CPIO magic.
  • Extracts (version_number, swu_hardware) via get_swu_metadata.
  • Enforces a strict hardware match before proceeding.
  • Falls back to filename parsing (and finally an interactive prompt) when metadata isn’t available.

One small UX improvement: if the user mistypes the version at the interactive prompt, the code raises SystemError("Invalid version!") after a single attempt. Wrapping the prompt in a simple loop with a maximum number of retries would make recovery smoother without changing semantics.

codexctl/device.py (4)

289-320: _read_version_from_path cleanly unifies version detection for current and backup partitions

The helper correctly:

  • Prefers /usr/share/remarkable/update.conf when available.
  • Falls back to /etc/os-release and flips old_update_engine to False when needed.
  • Accepts an optional base_path prefix so it works for both live and mounted backup partitions.

Given previous feedback about over‑broad exception handling, your restriction to (IOError, OSError) here is reasonable. If you want to tighten it further later, you could explicitly check for the presence of each file instead of relying on the exception, but it’s not strictly necessary for this PR.


321-372: Backup partition version helper is robust but relies on caller handling “unknown” correctly

_get_backup_partition_version does a good job of:

  • Supporting both swupdate‑based (RMPP/RMPPM) and rootdev‑based (RM1/RM2) schemes.
  • Mounting the inactive partition read‑only under /tmp/mount_pX.
  • Best‑effort reading via _read_version_from_path, and gracefully returning "unknown" when mounting or version parsing fails.

The sentinel "unknown" is a reasonable choice, but it requires callers (notably restore_previous_version) to treat it specially instead of assuming it’s a parseable semver; see comment on Lines 571‑609.


660-713: install_sw_update: bootloader hook integration is correct; consider guarding hardware type

Extending install_sw_update to accept optional bootloader_files and, when present, invoke _update_paper_pro_bootloader after a successful swupdate is the right place to hook the Paper Pro downgrade bootloader flow into the existing install path.

Two minor considerations:

  • The method is now semantically “Paper Pro aware” but doesn’t currently guard against being called with bootloader_files on non‑Paper‑Pro hardware. Today only the RMPP downgrade path passes this argument, but adding a defensive check would make misuse harder:

  •        if bootloader_files:
    
  •        if bootloader_files:
    
  •            if self.hardware is not HardwareType.RMPP:
    
  •                raise SystemError("Bootloader updates via swupdate are only supported on Paper Pro devices")
    
    
    
  • On non‑SSH (local) installs you ignore bootloader_files, which is fine as long as the only caller that passes them is the remote RMPP downgrade flow (current state).

Nothing blocking, but worth tightening if you expect other call sites to grow.


774-848: Paper Pro bootloader update helper is well‑structured; minor hardening possible

_update_paper_pro_bootloader is nicely self‑contained:

  • Uses SFTP to upload both update-bootloader.sh and imx-boot into /tmp/ on the device.
  • Runs preinst and postinst phases, checking exit codes and surfacing stderr content via SystemError.
  • Cleans up both local temporary files and remote /tmp artifacts in a finally block.

A couple of minor hardening options (not mandatory for this PR):

  • If you want to be extra defensive about partial cleanup failures, you could wrap the local os.unlink calls in small try/except OSError blocks to avoid masking a more useful SystemError from earlier in the method.
  • The fixed remote paths under /tmp are probably acceptable on a single‑user device, but if you anticipate concurrent codexctl runs, incorporating the PID into the filename (e.g. /tmp/imx-boot-<pid>) would reduce any chance of clobbering.

Overall, this helper looks solid.

codexctl/updates.py (2)

30-36: Version metadata + external provider URL handling looks correct; consider enforcing non‑empty list

The updated get_remarkable_versions neatly:

  • Extends the return shape to include remarkableppm versions and a list of external_provider_urls.
  • Supports both "external-provider-urls" (list) and legacy "external-provider-url" (string) keys, normalising the latter to a one‑element list.
  • Fails early with a clear SystemError when provider URLs are missing.

One small robustness improvement: right now an empty list of URLs passes the provider_urls is None check and will later cause download_version to log “Failed to download … from all sources” without hinting that the config is incomplete. You could treat an empty list as misconfiguration as well:

-        if provider_urls is None:
+        if not provider_urls:
             raise SystemError(
                 f"version-ids.json at {file_location} is missing external provider URLs. "
                 "Please delete the file and try again, or open an issue on the repo."
             )

Also applies to: 38-43, 86-101


400-424: Bootloader boundary detection helper encodes the 3.22 rule correctly

is_bootloader_boundary_downgrade cleanly captures “current ≥ 3.22 and target < 3.22” by:

  • Parsing both version strings into integer lists.
  • Comparing against [3, 22] using list comparison.
  • Returning False on any parsing error or odd format.

Given the current reMarkable versioning scheme, this should behave as intended for 3.x releases and safely ignore malformed inputs.

If you later introduce 4.x versions, you might want to align this helper with whatever you decide for uses_new_update_engine, but for 3.20–3.23 it’s fine.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e558b6 and b8c9a44.

📒 Files selected for processing (9)
  • .github/workflows/main.yml (1 hunks)
  • Makefile (1 hunks)
  • README.md (4 hunks)
  • codexctl/__init__.py (7 hunks)
  • codexctl/analysis.py (2 hunks)
  • codexctl/device.py (14 hunks)
  • codexctl/updates.py (7 hunks)
  • requirements.txt (1 hunks)
  • tests/test_unit.py (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T16:37:10.452Z
Learnt from: Eeems
Repo: Jayy001/codexctl PR: 140
File: codexctl/__init__.py:54-55
Timestamp: 2025-09-10T16:37:10.452Z
Learning: In codexctl/__init__.py, the UpdateManager methods (get_latest_version, get_toltec_version, download_version) all expect HardwareType enum parameters, not string parameters.

Applied to files:

  • codexctl/device.py
  • codexctl/updates.py
🧬 Code graph analysis (4)
codexctl/__init__.py (3)
codexctl/device.py (4)
  • HardwareType (20-85)
  • get_device_status (418-475)
  • new_download_hw (52-61)
  • install_sw_update (660-772)
codexctl/analysis.py (3)
  • extract_swu_files (83-138)
  • get_update_image (13-37)
  • get_swu_metadata (40-80)
codexctl/updates.py (3)
  • UpdateManager (17-424)
  • is_bootloader_boundary_downgrade (401-424)
  • download_version (179-263)
codexctl/analysis.py (1)
codexctl/device.py (1)
  • HardwareType (20-85)
tests/test_unit.py (1)
codexctl/updates.py (1)
  • is_bootloader_boundary_downgrade (401-424)
codexctl/updates.py (1)
codexctl/device.py (2)
  • HardwareType (20-85)
  • new_download_hw (52-61)
🪛 markdownlint-cli2 (0.18.1)
README.md

61-61: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


88-88: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 Ruff (0.14.5)
codexctl/__init__.py

124-126: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


124-126: Avoid specifying long messages outside the exception class

(TRY003)


138-140: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


138-140: Avoid specifying long messages outside the exception class

(TRY003)


276-281: Avoid specifying long messages outside the exception class

(TRY003)


294-294: Avoid specifying long messages outside the exception class

(TRY003)


354-354: Avoid specifying long messages outside the exception class

(TRY003)


379-379: Avoid specifying long messages outside the exception class

(TRY003)

codexctl/analysis.py

58-58: Avoid specifying long messages outside the exception class

(TRY003)


65-65: Avoid specifying long messages outside the exception class

(TRY003)


76-76: Avoid specifying long messages outside the exception class

(TRY003)

codexctl/device.py

37-37: Avoid specifying long messages outside the exception class

(TRY003)


49-49: Avoid specifying long messages outside the exception class

(TRY003)


85-85: Avoid specifying long messages outside the exception class

(TRY003)


307-307: Avoid specifying long messages outside the exception class

(TRY003)


315-315: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


315-315: Avoid specifying long messages outside the exception class

(TRY003)


346-346: Probable insecure usage of temporary file or directory: "/tmp/mount_p"

(S108)


360-360: Do not catch blind exception: Exception

(BLE001)


367-367: Consider moving this statement to an else block

(TRY300)


369-369: Do not catch blind exception: Exception

(BLE001)


383-383: Avoid specifying long messages outside the exception class

(TRY003)


394-394: Avoid specifying long messages outside the exception class

(TRY003)


581-581: Avoid specifying long messages outside the exception class

(TRY003)


587-587: Avoid specifying long messages outside the exception class

(TRY003)


794-794: Avoid specifying long messages outside the exception class

(TRY003)


798-798: Probable insecure usage of temporary file or directory: "/tmp/update-bootloader.sh"

(S108)


799-799: Probable insecure usage of temporary file or directory: "/tmp/imx-boot"

(S108)


827-827: Avoid specifying long messages outside the exception class

(TRY003)


836-836: Avoid specifying long messages outside the exception class

(TRY003)

codexctl/updates.py

90-93: Avoid specifying long messages outside the exception class

(TRY003)


422-422: Consider moving this statement to an else block

(TRY300)

🔇 Additional comments (11)
tests/test_unit.py (1)

1-65: Good focused coverage for bootloader boundary logic

The lightweight harness and these cases exercise the boundary semantics well (including malformed/empty versions). This should give quick signal if the 3.22‑boundary downgrade logic ever regresses.

Makefile (1)

52-57: Separating unit and integration tests in make test is a good call

Running tests/test_unit.py first with set -e gives a fast failure path before the more expensive integration flow in tests/test.py. The added echoes make the phases clear in CI logs.

.github/workflows/main.yml (1)

36-52: CI workflow unit-tests job properly configured

The dedicated unit-tests job with needs: [unit-tests] on the remote matrix provides a clean fast-fail stage before the build matrix runs. The entry point tests/test_unit.py exists and is tracked. Keep unit tests constrained to pure unit coverage so they remain fast and don't drift into integration test territory over time.

requirements.txt (1)

3-3: Consistency confirmed; change is safe

The verification shows requirements.remote.txt includes all packages from requirements.txt via -r requirements.txt. This means remarkable-update-image==1.2 is automatically available in both environments without duplication. The unconditional pin change is consistent across all requirement files.

codexctl/__init__.py (3)

70-87: Paper Pro Move listing wiring looks correct

The additional remarkableppm_versions block and the HardwareType.RMPPM check integrate cleanly with the existing listing logic; behavior for other hardware remains unchanged.


424-425: Passing bootloader_files into install_sw_update only for new‑engine installs is correct

Conditioning remarkable.install_sw_update(update_file, bootloader_files=bootloader_files_for_install) on device_version_uses_new_engine and keeping the old‐engine path on install_ohma_update maintains the previous behavior for RM1/RM2 while enabling the new Paper Pro downgrade flow.


627-631: Top‑level SystemError handling gives cleaner CLI failures

Wrapping man.call_func in a try/except SystemError and printing a concise message to stderr before exiting with status 1 is a nice UX improvement. It avoids tracebacks for expected operational failures while still letting unexpected exceptions surface.

codexctl/device.py (1)

20-37: RMPPM hardware integration into HardwareType and mappings looks consistent

Adding RMPPM to the enum, extending parse with chiappa/rmppm aliases, and wiring it through old_download_hw, new_download_hw, swupdate_hw, and toltec_type keeps behavior parallel to the existing RM1/RM2/RMPP cases (including correctly disallowing old engine/toltec for Pro/Move).

Also applies to: 39-86

codexctl/updates.py (3)

129-147: RMPPM integration into get_latest_version mirrors other hardware types

Adding the HardwareType.RMPPM branch and using self.remarkableppm_versions is consistent with the existing RM1/RM2/RMPP logic; __max_version(versions.keys()) remains the common path.


179-263: RMPPM download support and multi‑provider fallback behavior look good

The changes in download_version:

  • Add a HardwareType.RMPPM case wired to self.remarkableppm_versions, matching the new JSON layout.
  • Keep old .signed downloads (<={3,11,2,5}) intact, including use of hardware_type.old_download_hw.
  • For newer versions, construct a consistent local file_name and iterate over self.external_provider_urls, attempting each provider in sequence and returning on the first successful __download_version_file call, with clear debug logging on each attempt and a final error if all fail.

This provides a straightforward way to add or swap providers via version-ids.json without code changes, while preserving checksum validation and size sanity checks.


329-332: Non‑200 responses are now logged at debug level, aiding provider diagnostics

The additional debug log for non‑200 HTTP responses in __download_version_file will help diagnose misconfigured or failing external providers without spamming normal users.

@Eeems
Copy link
Collaborator

Eeems commented Nov 23, 2025

Instead of adding a brand new test job and brand new test code, could you extend the existing test code which is already integrated into the workflow.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
codexctl/device.py (1)

665-717: Bootloader update integration looks solid.

The install_sw_update extension correctly:

  • Accepts optional bootloader_files parameter
  • Applies bootloader update after successful swupdate (lines 711-717)
  • Continues with the existing reboot and update-service disabling flow

Minor type annotation inconsistency: Line 665 uses dict[str, bytes] while line 9 imports Dict from typing. For consistency throughout the file, consider:

-    def install_sw_update(self, version_file: str, bootloader_files: dict[str, bytes] | None = None) -> None:
+    def install_sw_update(self, version_file: str, bootloader_files: Dict[str, bytes] | None = None) -> None:

(Note: dict[...] is valid in Python 3.9+, but using Dict matches the existing import style.)

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b8c9a44 and 570b588.

📒 Files selected for processing (3)
  • codexctl/__init__.py (7 hunks)
  • codexctl/analysis.py (2 hunks)
  • codexctl/device.py (14 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T16:37:10.452Z
Learnt from: Eeems
Repo: Jayy001/codexctl PR: 140
File: codexctl/__init__.py:54-55
Timestamp: 2025-09-10T16:37:10.452Z
Learning: In codexctl/__init__.py, the UpdateManager methods (get_latest_version, get_toltec_version, download_version) all expect HardwareType enum parameters, not string parameters.

Applied to files:

  • codexctl/device.py
🧬 Code graph analysis (2)
codexctl/__init__.py (3)
codexctl/device.py (2)
  • HardwareType (20-85)
  • new_download_hw (52-61)
codexctl/analysis.py (3)
  • extract_swu_files (83-142)
  • get_update_image (13-37)
  • get_swu_metadata (40-80)
codexctl/updates.py (3)
  • UpdateManager (17-424)
  • is_bootloader_boundary_downgrade (401-424)
  • download_version (179-263)
codexctl/analysis.py (1)
codexctl/device.py (1)
  • HardwareType (20-85)
🪛 Ruff (0.14.5)
codexctl/device.py

37-37: Avoid specifying long messages outside the exception class

(TRY003)


49-49: Avoid specifying long messages outside the exception class

(TRY003)


85-85: Avoid specifying long messages outside the exception class

(TRY003)


307-307: Avoid specifying long messages outside the exception class

(TRY003)


315-315: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


315-315: Avoid specifying long messages outside the exception class

(TRY003)


346-346: Probable insecure usage of temporary file or directory: "/tmp/mount_p"

(S108)


360-360: Do not catch blind exception: Exception

(BLE001)


367-367: Consider moving this statement to an else block

(TRY300)


369-369: Do not catch blind exception: Exception

(BLE001)


383-383: Avoid specifying long messages outside the exception class

(TRY003)


394-394: Avoid specifying long messages outside the exception class

(TRY003)


581-581: Avoid specifying long messages outside the exception class

(TRY003)


584-586: Avoid specifying long messages outside the exception class

(TRY003)


592-592: Avoid specifying long messages outside the exception class

(TRY003)


799-799: Avoid specifying long messages outside the exception class

(TRY003)


803-803: Probable insecure usage of temporary file or directory: "/tmp/update-bootloader.sh"

(S108)


804-804: Probable insecure usage of temporary file or directory: "/tmp/imx-boot"

(S108)


832-832: Avoid specifying long messages outside the exception class

(TRY003)


841-841: Avoid specifying long messages outside the exception class

(TRY003)

codexctl/__init__.py

124-126: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


124-126: Avoid specifying long messages outside the exception class

(TRY003)


138-140: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


138-140: Avoid specifying long messages outside the exception class

(TRY003)


276-281: Avoid specifying long messages outside the exception class

(TRY003)


294-294: Avoid specifying long messages outside the exception class

(TRY003)


354-354: Avoid specifying long messages outside the exception class

(TRY003)


371-374: Avoid specifying long messages outside the exception class

(TRY003)


384-384: Avoid specifying long messages outside the exception class

(TRY003)

codexctl/analysis.py

58-58: Avoid specifying long messages outside the exception class

(TRY003)


65-65: Avoid specifying long messages outside the exception class

(TRY003)


76-76: Avoid specifying long messages outside the exception class

(TRY003)


118-118: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (12)
codexctl/analysis.py (2)

40-80: LGTM! SWU metadata extraction is well-structured.

The function correctly:

  • Uses bytes keys for Archive access (b"sw-description")
  • Validates the presence of required metadata
  • Maps hardware identifiers to HardwareType enum values
  • Ensures cleanup via finally block

83-142: Excellent work addressing the past review concerns!

The function now correctly:

  • Type safety: Line 136 encodes string filenames to bytes before calling archive.get(), fixing the key type mismatch
  • Path traversal protection: Lines 117-118 resolve the file path and verify it stays within output_path using is_relative_to()
  • Flexible extraction modes (to disk or in-memory dict)
  • Proper cleanup via finally block
codexctl/__init__.py (4)

73-83: LGTM! RMPPM support integrated consistently.

The Paper Pro Move version listing follows the same pattern as existing hardware variants.


109-133: Good fix for CPIO magic detection!

Line 113 now checks for both b'070701' and b'070702', correctly routing all valid CPIO-based SWU files to the new extraction path. This addresses the past review concern about misclassifying "070701" archives.


261-298: Robust SWU metadata extraction with appropriate fallbacks.

The logic correctly:

  • Detects SWU format via magic bytes (both variants)
  • Extracts version and hardware from SWU metadata
  • Validates hardware compatibility before installation (lines 275-281)
  • Falls back to filename parsing if metadata extraction fails

329-389: Excellent! Past critical issue resolved.

The bootloader boundary downgrade flow now correctly handles download failures. Lines 370-374 raise SystemError if the current version SWU cannot be downloaded, preventing the unsafe scenario where the OS downgrade would proceed without the required bootloader update.

Additional safeguards:

  • Lines 383-384: Validates that exactly 2 bootloader files were extracted
  • Lines 386-388: Logs file sizes for verification
  • Clear user warnings and confirmation prompt (lines 337-354)
codexctl/device.py (6)

24-85: LGTM! RMPPM hardware type integrated consistently.

The new Paper Pro Move variant is correctly:

  • Added to the HardwareType enum (line 24)
  • Parsed from multiple aliases: "ppm", "rmppm", "chiappa", "remarkable chiappa" (lines 28-29)
  • Mapped across all properties: old_download_hw, new_download_hw, swupdate_hw, toltec_type
  • Included in error messages (line 37)

289-319: Well-structured helper for version detection.

The _read_version_from_path function provides a clean abstraction for reading versions from either the current partition or a mounted backup, with appropriate fallback from update.conf to os-release.


321-371: Robust backup partition version detection.

The function gracefully handles failures by returning "unknown" rather than raising exceptions, which is appropriate for a helper that may encounter unreadable partitions. The exception handling at lines 360 and 369 is intentional for robustness.

Note: The S108 static analysis warning about /tmp/mount_p{inactive_part} is a false positive—this is a temporary mount point, not a file with sensitive data.


418-475: Clean extension of device status with backup version.

The function now returns a 5-tuple including the backup partition version, utilizing the new helper methods for cleaner code organization.


571-613: Excellent! Past review concerns fully addressed.

The restore flow now correctly:

  • Raises SystemError for all failure modes (lines 581, 584-586, 592) instead of ValueError, ensuring consistent error handling
  • Explicitly checks for "unknown" backup version (lines 583-586) with a clear error message
  • Handles both legacy sysfs (root_part) and new MMC bootpart methods based on version boundaries
  • Applies both methods when transitioning across the 3.22 boundary

779-852: Well-structured bootloader update implementation.

The _update_paper_pro_bootloader method correctly:

  • Uses local temp files for staging (lines 806-812)
  • Uploads bootloader script and image to device (lines 816-819)
  • Runs both preinst and postinst phases (lines 826-841)
  • Validates exit status after each phase
  • Ensures cleanup in finally block (lines 845-852)

Note: The S108 static analysis warnings about /tmp paths (lines 803-804) are false positives—these are paths on the remote device, not the local system.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
codexctl/device.py (1)

289-319: Refactor exception handling to avoid blind fallback.

The current implementation catches any IOError/OSError when reading update.conf and blindly tries os-release. This conflates "file doesn't exist" with "file is corrupted" or other I/O errors.

Based on learnings, Eeems previously requested checking for files explicitly rather than using try/catch for flow control.

Additionally, the ValueError exceptions raised on lines 307 and 315 won't be caught by the top-level SystemError handler in __init__.py, potentially showing raw tracebacks to users.

Consider this refactor:

 def _read_version_from_path(self, ftp, base_path: str = "") -> tuple[str, bool]:
     """Reads version from a given path (current partition or mounted backup)
     
     Args:
         ftp: SFTP client connection
         base_path: Base path prefix (empty for current partition, /tmp/mount_pX for backup)
     
     Returns:
         tuple: (version_string, old_update_engine_boolean)
     """
-    old_update_engine = True
-
-    try:
-        update_conf_path = f"{base_path}/usr/share/remarkable/update.conf" if base_path else "/usr/share/remarkable/update.conf"
-        with ftp.file(update_conf_path) as file:
-            contents = file.read().decode("utf-8").strip("\n")
-            match = re.search("(?<=REMARKABLE_RELEASE_VERSION=).*", contents)
-            if not match:
-                raise ValueError(f"REMARKABLE_RELEASE_VERSION not found in {update_conf_path}")
-            version = match.group()
-    except (IOError, OSError):
-        os_release_path = f"{base_path}/etc/os-release" if base_path else "/etc/os-release"
-        with ftp.file(os_release_path) as file:
-            contents = file.read().decode("utf-8")
-            match = re.search("(?<=IMG_VERSION=).*", contents)
-            if not match:
-                raise ValueError(f"IMG_VERSION not found in {os_release_path}")
-            version = match.group().strip('"')
-            old_update_engine = False
-
-    return version, old_update_engine
+    update_conf_path = f"{base_path}/usr/share/remarkable/update.conf" if base_path else "/usr/share/remarkable/update.conf"
+    os_release_path = f"{base_path}/etc/os-release" if base_path else "/etc/os-release"
+    
+    # Try old update engine format first
+    try:
+        ftp.stat(update_conf_path)
+        with ftp.file(update_conf_path) as file:
+            contents = file.read().decode("utf-8").strip("\n")
+            match = re.search("(?<=REMARKABLE_RELEASE_VERSION=).*", contents)
+            if not match:
+                raise SystemError(f"REMARKABLE_RELEASE_VERSION not found in {update_conf_path}")
+            return match.group(), True
+    except (IOError, OSError):
+        pass  # File doesn't exist, try new format
+    
+    # Try new update engine format
+    try:
+        with ftp.file(os_release_path) as file:
+            contents = file.read().decode("utf-8")
+            match = re.search("(?<=IMG_VERSION=).*", contents)
+            if not match:
+                raise SystemError(f"IMG_VERSION not found in {os_release_path}")
+            return match.group().strip('"'), False
+    except (IOError, OSError) as e:
+        raise SystemError(f"Cannot read version from {base_path or 'current partition'}: {e}") from e

This checks file existence explicitly with stat() and uses SystemError for consistency with the error handler.

🧹 Nitpick comments (1)
codexctl/device.py (1)

321-371: Consider narrowing exception handling.

The method catches broad Exception on lines 360 and 369, which can mask unexpected errors (static analysis warns about BLE001). While returning "unknown" is safe for the current caller, more specific exception types would improve debuggability.

Consider catching only the exceptions you expect:

         try:
             version, _ = self._read_version_from_path(ftp, mount_point)
-        except Exception as e:
+        except (IOError, OSError, SystemError) as e:
             self.logger.debug(f"Failed to read version from backup partition: {e}")
             version = "unknown"
     except Exception as e:
         self.logger.debug(f"Error getting backup partition version: {e}")
         return "unknown"
+    except (IOError, OSError, SystemError, paramiko.SSHException) as e:
+        self.logger.debug(f"Error getting backup partition version: {e}")
+        return "unknown"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 570b588 and 2ce4631.

📒 Files selected for processing (3)
  • Makefile (1 hunks)
  • codexctl/device.py (15 hunks)
  • tests/test.py (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • Makefile
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T16:37:10.452Z
Learnt from: Eeems
Repo: Jayy001/codexctl PR: 140
File: codexctl/__init__.py:54-55
Timestamp: 2025-09-10T16:37:10.452Z
Learning: In codexctl/__init__.py, the UpdateManager methods (get_latest_version, get_toltec_version, download_version) all expect HardwareType enum parameters, not string parameters.

Applied to files:

  • codexctl/device.py
🪛 Ruff (0.14.5)
codexctl/device.py

37-37: Avoid specifying long messages outside the exception class

(TRY003)


49-49: Avoid specifying long messages outside the exception class

(TRY003)


85-85: Avoid specifying long messages outside the exception class

(TRY003)


307-307: Avoid specifying long messages outside the exception class

(TRY003)


315-315: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


315-315: Avoid specifying long messages outside the exception class

(TRY003)


346-346: Probable insecure usage of temporary file or directory: "/tmp/mount_p"

(S108)


360-360: Do not catch blind exception: Exception

(BLE001)


367-367: Consider moving this statement to an else block

(TRY300)


369-369: Do not catch blind exception: Exception

(BLE001)


383-383: Avoid specifying long messages outside the exception class

(TRY003)


394-394: Avoid specifying long messages outside the exception class

(TRY003)


581-581: Avoid specifying long messages outside the exception class

(TRY003)


584-586: Avoid specifying long messages outside the exception class

(TRY003)


592-592: Avoid specifying long messages outside the exception class

(TRY003)


799-799: Avoid specifying long messages outside the exception class

(TRY003)


803-803: Probable insecure usage of temporary file or directory: "/tmp/update-bootloader.sh"

(S108)


804-804: Probable insecure usage of temporary file or directory: "/tmp/imx-boot"

(S108)


832-832: Avoid specifying long messages outside the exception class

(TRY003)


841-841: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (7)
tests/test.py (2)

201-202: LGTM! Version updates align with PR objectives.

The updated expected versions (3.23.0.64) correctly reflect the new firmware support added in this PR.


218-257: Tests correctly validate bootloader boundary logic.

The method UpdateManager.is_bootloader_boundary_downgrade (codexctl/updates.py:401) exists with the expected signature. All 8 test cases correctly reflect the documented behavior: returns True only when current_version >= 3.22 AND target_version < 3.22. Coverage includes boundary crossing downgrades, non-boundary transitions, upgrades, and edge cases (empty/non-numeric versions).

codexctl/device.py (5)

24-85: LGTM: RMPPM hardware variant integration is consistent.

The RMPPM (Paper Pro Move) enum value and property implementations follow the established patterns for RM1, RM2, and RMPP. The mappings are correct: "chiappa" for both new_download_hw and swupdate_hw, and appropriate ValueError for unsupported old_download_hw and toltec_type.


373-416: LGTM: Partition detection logic is sound.

The version-aware partition detection correctly handles both the legacy sysfs (root_part) and new MMC bootpart schemes. Line 394 now raises SystemError instead of ValueError (past review concern resolved), ensuring clean error handling at the top level.


418-475: LGTM: Clean refactor with new backup version support.

The method now delegates to _read_version_from_path and returns the backup partition version as a fifth return value. The refactoring maintains existing behavior while adding the new functionality.


571-613: LGTM: Robust partition switching for Paper Pro devices.

The restore logic correctly handles the 3.20/3.22 partition scheme transition by conditionally applying both legacy sysfs and new MMC bootpart methods based on current and target versions. The explicit check for "unknown" backup version on lines 583-586 addresses the past review concern, ensuring clear error messages rather than raw tracebacks.


665-717: LGTM: Bootloader update integration is clean.

The optional bootloader_files parameter is correctly typed and conditionally applied after successful swupdate. The flow ensures bootloader updates only occur when needed (e.g., for Paper Pro downgrades to <3.20).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
codexctl/device.py (3)

9-9: Consider type hint consistency.

The imports Optional and Dict from typing aren't used in the visible changes (lines 667, 673 use dict[str, bytes] | None instead). If they're not used elsewhere in the file, they could be removed. Alternatively, for consistency with older Python type hint styles, you could use Optional[Dict[str, bytes]] instead of dict[str, bytes] | None.


323-373: Good defensive programming for backup partition detection.

The method correctly handles various failure modes by returning "unknown" rather than crashing, which is then explicitly checked by callers. The cleanup logic properly unmounts and removes the temporary mount point.

Minor style suggestion: line 369's return version could be moved to an else block for clarity per TRY300, though the current implementation is functionally correct.


803-807: Improve exception handling specificity and chaining.

Line 806 catches bare Exception which can hide unexpected errors, and line 807 raises without chaining the original exception, losing debug context.

Apply this diff to catch specific exceptions and preserve the traceback:

-        ftp_client = None
-        try:
-            ftp_client = self.client.open_sftp()
-        except Exception:
-            raise SystemError("Failed to open SFTP connection for bootloader update")
+        ftp_client = None
+        try:
+            ftp_client = self.client.open_sftp()
+        except (paramiko.SSHException, OSError) as e:
+            raise SystemError("Failed to open SFTP connection for bootloader update") from e

This catches likely SFTP failures specifically and chains the exception for better debugging.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2ce4631 and 9d136b4.

📒 Files selected for processing (1)
  • codexctl/device.py (15 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T16:37:10.452Z
Learnt from: Eeems
Repo: Jayy001/codexctl PR: 140
File: codexctl/__init__.py:54-55
Timestamp: 2025-09-10T16:37:10.452Z
Learning: In codexctl/__init__.py, the UpdateManager methods (get_latest_version, get_toltec_version, download_version) all expect HardwareType enum parameters, not string parameters.

Applied to files:

  • codexctl/device.py
🪛 Ruff (0.14.5)
codexctl/device.py

37-37: Avoid specifying long messages outside the exception class

(TRY003)


49-49: Avoid specifying long messages outside the exception class

(TRY003)


85-85: Avoid specifying long messages outside the exception class

(TRY003)


308-308: Avoid specifying long messages outside the exception class

(TRY003)


318-318: Avoid specifying long messages outside the exception class

(TRY003)


321-321: Avoid specifying long messages outside the exception class

(TRY003)


348-348: Probable insecure usage of temporary file or directory: "/tmp/mount_p"

(S108)


369-369: Consider moving this statement to an else block

(TRY300)


385-385: Avoid specifying long messages outside the exception class

(TRY003)


396-396: Avoid specifying long messages outside the exception class

(TRY003)


583-583: Avoid specifying long messages outside the exception class

(TRY003)


586-588: Avoid specifying long messages outside the exception class

(TRY003)


594-594: Avoid specifying long messages outside the exception class

(TRY003)


801-801: Avoid specifying long messages outside the exception class

(TRY003)


806-806: Do not catch blind exception: Exception

(BLE001)


807-807: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


807-807: Avoid specifying long messages outside the exception class

(TRY003)


809-809: Probable insecure usage of temporary file or directory: "/tmp/update-bootloader.sh"

(S108)


810-810: Probable insecure usage of temporary file or directory: "/tmp/imx-boot"

(S108)


838-838: Avoid specifying long messages outside the exception class

(TRY003)


847-847: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (7)
codexctl/device.py (7)

24-85: LGTM: Comprehensive RMPPM hardware type integration.

The Paper Pro Move (RMPPM) support is consistently integrated across all HardwareType properties with appropriate identifiers ("chiappa") and error messages. The parse method correctly handles multiple name variants.


289-322: Good refactoring: Version reading logic extracted.

The _read_version_from_path helper cleanly handles both old (update.conf) and new (os-release) version formats with appropriate fallback logic. The error handling correctly raises SystemError for unrecoverable failures, ensuring consistent error handling across the codebase.


375-418: LGTM: Correct handling of Paper Pro partition scheme transition.

The method properly handles the 3.20/3.22 partition scheme change by attempting the new MMC boot_part method first (for >= 3.22), then falling back to the legacy root_part sysfs interface. The version parsing and error handling are appropriate.


420-477: LGTM: Backup version added to device status.

The method now returns backup partition version as a fifth value, utilizing the new _read_version_from_path and _get_backup_partition_version helpers. Note this is a breaking API change (5 return values instead of 4), but appears intentional per the PR objectives.


573-616: LGTM: Robust restore logic for Paper Pro partition scheme transition.

The restore logic correctly handles the 3.20/3.22 partition scheme change by:

  • Validating backup version is readable (lines 585-588)
  • Detecting version boundaries for both current and target versions
  • Conditionally applying legacy sysfs method (< 3.22) and/or new MMC method (>= 3.22)

All version parsing errors appropriately raise SystemError for consistent error handling (confirmed from past review feedback).


667-719: LGTM: Clean integration of bootloader update pathway.

The optional bootloader_files parameter allows seamless bootloader updates for Paper Pro downgrades without breaking existing callers. The update sequence (swupdate → bootloader → reboot) is correct.


781-859: Bootloader update implementation looks solid.

The method correctly:

  • Uses secure temporary files locally (tempfile.NamedTemporaryFile)
  • Uploads script and image to the device
  • Runs both preinst and postinst phases with proper error checking
  • Cleans up both local and remote files in the finally block
  • Guards the ftp_client.close() call to prevent NameError (past review concern addressed)

Co-authored-by: Nathaniel van Diepen <Eeems@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
codexctl/analysis.py (1)

9-9: Replace deprecated typing imports with modern syntax.

The imports Tuple, Optional, and Dict from typing are deprecated. Line 40 already uses the modern tuple[...] syntax, but line 87 still uses Optional[Dict[str, bytes]]. Based on learnings, the maintainer has requested using tuple, dict, and | None instead.

Apply this diff to use modern type hints consistently:

-from typing import Tuple, Optional, Dict
+from typing import TYPE_CHECKING

Then update line 87:

-) -> Optional[Dict[str, bytes]]:
+) -> dict[str, bytes] | None:

Based on learnings, the maintainer has previously requested this change.

🧹 Nitpick comments (1)
codexctl/analysis.py (1)

40-80: Implementation looks correct; consider extracting exception messages as optional refactor.

The function correctly:

  • Uses bytes keys for archive access (b"sw-description")
  • Ensures archive cleanup in the finally block
  • Validates presence of version and hardware metadata
  • Maps hardware identifiers to HardwareType enum values

The static analysis hints flag long exception messages at lines 58, 65, and 76. While these are functional, you could optionally extract them into constants or custom exception classes for consistency with TRY003.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d136b4 and 02bbbb4.

📒 Files selected for processing (2)
  • README.md (4 hunks)
  • codexctl/analysis.py (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T16:37:10.452Z
Learnt from: Eeems
Repo: Jayy001/codexctl PR: 140
File: codexctl/__init__.py:54-55
Timestamp: 2025-09-10T16:37:10.452Z
Learning: In codexctl/__init__.py, the UpdateManager methods (get_latest_version, get_toltec_version, download_version) all expect HardwareType enum parameters, not string parameters.

Applied to files:

  • codexctl/analysis.py
🧬 Code graph analysis (1)
codexctl/analysis.py (1)
codexctl/device.py (1)
  • HardwareType (20-85)
🪛 markdownlint-cli2 (0.18.1)
README.md

61-61: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


88-88: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 Ruff (0.14.6)
codexctl/analysis.py

58-58: Avoid specifying long messages outside the exception class

(TRY003)


65-65: Avoid specifying long messages outside the exception class

(TRY003)


76-76: Avoid specifying long messages outside the exception class

(TRY003)


118-118: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (4)
README.md (2)

10-12: Good addition documenting the new bootloader update workflow.

The new section clearly explains the automatic bootloader handling for Paper Pro downgrades, which aligns well with the PR's downgrade support feature.


77-81: Good enhancement showing the complete restore workflow.

The updated example now demonstrates the full status → restore sequence, making it clearer how to use the restore functionality on devices that support it (Paper Pro/Move and other variants).

codexctl/analysis.py (2)

110-124: Path traversal protection correctly implemented.

The code now properly prevents path traversal attacks:

  • Line 107 resolves the output directory to an absolute path
  • Line 115 resolves each entry's target path including symlinks
  • Line 117 validates the resolved path stays within the output directory
  • Line 118 raises ValueError for any traversal attempts

This addresses the previous security concern flagged in past reviews.


127-140: Type consistency fix correctly implemented.

The code now properly handles the bytes/str key mismatch:

  • Line 136 encodes caller-provided string filenames to bytes (filename.encode('utf-8')) before calling archive.get()
  • This ensures lookups succeed since archive keys are bytes (as confirmed by the b"TRAILER!!!" checks)

This addresses the previous correctness issue where filtered extraction would silently fail to find entries.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
README.md (2)

60-64: Add bash language specification to the code block.

The fenced code block is missing a language identifier, which violates markdown linting rules. Specify bash for proper syntax highlighting.

- Downloading rmppm version 3.23.0.64 to a folder named `out` and then installing it
-```
+Downloading rmppm version 3.23.0.64 to a folder named `out` and then installing it
+```bash
 codexctl download 3.23.0.64 --hardware rmppm -o out
 codexctl install ./out/remarkable-production-image-3.23.0.64-chiappa-public.swu
-```
+```

87-90: Add bash language specification to the code block.

The fenced code block is missing a language identifier, which violates markdown linting rules. Specify bash for proper syntax highlighting.

- Extract the contents of an upgrade file to a folder named `extracted`
-```
+Extract the contents of an upgrade file to a folder named `extracted`
+```bash
 codexctl extract remarkable-production-image-3.22.0.64-ferrari-public.swu -o extracted
-```
+```
🧹 Nitpick comments (3)
codexctl/__init__.py (1)

124-130: Chain the exception for better debugging.

The raise ... from None pattern should be used when intentionally discarding the original exception context. However, for debugging purposes, consider preserving the original exception.

             except ImportError:
-                raise ImportError(
+                raise ImportError(
                     "remarkable_update_image and remarkable_update_fuse are required for mounting. Please install them!"
-                )
+                ) from None

This silences the static analysis warning while making the intent explicit.

codexctl/device.py (2)

345-356: Consider extracting duplicate partition detection logic.

The partition detection code (getting active device, calculating inactive partition) is duplicated between the RMPP/RMPPM branch (lines 346-350) and the RM1/RM2 branch (lines 352-356). The logic is identical except for the command used (swupdate -g vs rootdev).

This is a minor duplication that could be consolidated in the future, but it's acceptable for now given the different command sources.


810-814: Good fix for potential NameError, but exception chaining is missing.

The ftp_client = None initialization before the try block addresses the past review concern. However, the bare except Exception should chain the exception for debugging.

         ftp_client = None
         try:
             ftp_client = self.client.open_sftp()
-        except Exception:
-            raise SystemError("Failed to open SFTP connection for bootloader update")
+        except Exception as e:
+            raise SystemError("Failed to open SFTP connection for bootloader update") from e

This preserves the original exception context for debugging while still presenting a clean error message.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02bbbb4 and 9d1cffb.

📒 Files selected for processing (6)
  • README.md (4 hunks)
  • codexctl/__init__.py (7 hunks)
  • codexctl/analysis.py (2 hunks)
  • codexctl/device.py (14 hunks)
  • codexctl/updates.py (7 hunks)
  • tests/test.py (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T16:37:10.452Z
Learnt from: Eeems
Repo: Jayy001/codexctl PR: 140
File: codexctl/__init__.py:54-55
Timestamp: 2025-09-10T16:37:10.452Z
Learning: In codexctl/__init__.py, the UpdateManager methods (get_latest_version, get_toltec_version, download_version) all expect HardwareType enum parameters, not string parameters.

Applied to files:

  • codexctl/device.py
  • codexctl/analysis.py
🧬 Code graph analysis (4)
codexctl/updates.py (2)
codexctl/device.py (2)
  • HardwareType (19-89)
  • new_download_hw (56-65)
codexctl/__init__.py (1)
  • version_lookup (248-249)
tests/test.py (1)
codexctl/updates.py (2)
  • UpdateManager (17-435)
  • is_bootloader_boundary_downgrade (401-435)
codexctl/__init__.py (3)
codexctl/device.py (4)
  • HardwareType (19-89)
  • get_device_status (422-479)
  • new_download_hw (56-65)
  • install_sw_update (674-786)
codexctl/analysis.py (1)
  • get_swu_metadata (37-62)
codexctl/updates.py (1)
  • is_bootloader_boundary_downgrade (401-435)
codexctl/analysis.py (1)
codexctl/device.py (1)
  • HardwareType (19-89)
🪛 markdownlint-cli2 (0.18.1)
README.md

61-61: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


88-88: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 Ruff (0.14.7)
codexctl/updates.py

90-93: Avoid specifying long messages outside the exception class

(TRY003)


419-419: Avoid specifying long messages outside the exception class

(TRY003)


421-421: Avoid specifying long messages outside the exception class

(TRY003)


427-427: Avoid specifying long messages outside the exception class

(TRY003)


430-430: Avoid specifying long messages outside the exception class

(TRY003)

codexctl/__init__.py

112-114: Avoid specifying long messages outside the exception class

(TRY003)


128-130: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


128-130: Avoid specifying long messages outside the exception class

(TRY003)


266-271: Avoid specifying long messages outside the exception class

(TRY003)


284-284: Avoid specifying long messages outside the exception class

(TRY003)


344-344: Avoid specifying long messages outside the exception class

(TRY003)


361-364: Avoid specifying long messages outside the exception class

(TRY003)


375-375: Avoid specifying long messages outside the exception class

(TRY003)

codexctl/device.py

41-41: Avoid specifying long messages outside the exception class

(TRY003)


51-51: Avoid specifying long messages outside the exception class

(TRY003)


53-53: Avoid specifying long messages outside the exception class

(TRY003)


87-87: Avoid specifying long messages outside the exception class

(TRY003)


89-89: Avoid specifying long messages outside the exception class

(TRY003)


309-309: Consider moving this statement to an else block

(TRY300)


319-319: Avoid specifying long messages outside the exception class

(TRY003)


327-327: Avoid specifying long messages outside the exception class

(TRY003)


329-329: Avoid specifying long messages outside the exception class

(TRY003)


341-341: Avoid specifying long messages outside the exception class

(TRY003)


358-358: Probable insecure usage of temporary file or directory: "/tmp/mount_p"

(S108)


368-368: Avoid specifying long messages outside the exception class

(TRY003)


387-387: Avoid specifying long messages outside the exception class

(TRY003)


398-398: Avoid specifying long messages outside the exception class

(TRY003)


585-585: Avoid specifying long messages outside the exception class

(TRY003)


588-590: Avoid specifying long messages outside the exception class

(TRY003)


596-596: Avoid specifying long messages outside the exception class

(TRY003)


808-808: Avoid specifying long messages outside the exception class

(TRY003)


813-813: Do not catch blind exception: Exception

(BLE001)


814-814: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


814-814: Avoid specifying long messages outside the exception class

(TRY003)


816-816: Probable insecure usage of temporary file or directory: "/tmp/update-bootloader.sh"

(S108)


817-817: Probable insecure usage of temporary file or directory: "/tmp/imx-boot"

(S108)


845-845: Avoid specifying long messages outside the exception class

(TRY003)


854-854: Avoid specifying long messages outside the exception class

(TRY003)

codexctl/analysis.py

60-60: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (12)
README.md (2)

10-12: Clear and informative addition to documentation.

The new "Paper Pro bootloader updates" section effectively explains the automatic bootloader handling during downgrades across the 3.20/3.22 boundary, aligning with the PR objectives for bootloader-aware upgrade flows.


77-81: Example workflow appropriately updated.

The restore workflow example is clear and consistent with existing documentation patterns.

codexctl/analysis.py (1)

37-62: LGTM! Clean metadata extraction implementation.

The function correctly extracts version and hardware type from SWU files using the remarkable_update_image library. The hardware mapping aligns with the HardwareType.swupdate_hw property values defined in codexctl/device.py.

tests/test.py (1)

213-249: Good test coverage for RMPPM and bootloader boundary downgrade logic.

The tests comprehensively cover:

  • RMPPM toltec error path (matching RMPP behavior)
  • Boundary-crossing downgrades (3.22+ → <3.22)
  • Non-boundary cases and upgrade direction
  • Input validation (empty string, non-numeric versions)

This addresses the prior review feedback about adding unit tests for is_bootloader_boundary_downgrade.

codexctl/__init__.py (2)

623-627: Good addition of top-level SystemError handler.

This catches SystemError exceptions and provides a clean user-facing error message instead of a raw traceback, improving the CLI experience.


321-322: Bootloader boundary check should include RMPPM (Paper Pro Move).

The bootloader boundary downgrade logic only checks for HardwareType.RMPP, but based on the PR description, RMPPM devices also use the new update engine and may require similar bootloader handling for 3.22 boundary crossings.

Consider extending the condition to include both Paper Pro variants:

-                if (device_version_uses_new_engine and
-                    remarkable.hardware == HardwareType.RMPP):
+                if (device_version_uses_new_engine and
+                    remarkable.hardware in (HardwareType.RMPP, HardwareType.RMPPM)):

If RMPPM devices do not require this handling, clarify with a comment explaining why only RMPP is checked.

codexctl/updates.py (3)

86-100: Good backward-compatible handling of provider URLs.

The code gracefully handles both string (legacy) and list (new) formats for external provider URLs, with appropriate validation. This ensures smooth migration for users with older version-ids.json files.


248-263: Multi-provider download fallback is well-implemented.

The loop iterates through available providers, logging failures and attempting the next source. This improves reliability when a single provider is unavailable.


400-435: Robust bootloader boundary detection with proper validation.

The implementation correctly:

  • Validates empty/whitespace inputs
  • Handles non-numeric version components with clear error messages
  • Requires at least 2 version components
  • Uses list comparison for semantic version ordering

One edge case to note: [3, 22] comparison works correctly because Python compares lists element-by-element.

codexctl/device.py (3)

293-329: Well-structured version reading helper.

The _read_version_from_path method cleanly abstracts version detection from either update.conf (old engine) or os-release (new engine), with proper error messages when neither source is available.


858-866: Cleanup in finally block looks correct.

The finally block properly:

  • Removes remote temporary files
  • Removes local temporary files
  • Guards ftp_client.close() with a None check

477-479: Verify _get_backup_partition_version behavior without SSH client.

get_device_status calls _get_backup_partition_version() unconditionally at line 477, but this method may raise SystemError if self.client is None (line 341). When running locally (non-remote), self.client is None, which could cause the status command to fail.

Consider handling the local (non-remote) case in _get_backup_partition_version, or guard the call:

-        backup_version = self._get_backup_partition_version()
+        if self.client:
+            backup_version = self._get_backup_partition_version()
+        else:
+            # Local partition mounting would require different implementation
+            backup_version = "unknown"

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
codexctl/device.py (1)

783-861: Solid bootloader update implementation with proper cleanup.

The method correctly handles the two-phase bootloader update (preinst/postinst), includes comprehensive cleanup in the finally block, and properly guards against the NameError that was flagged in previous reviews.

One minor refinement: the exception handling on line 808 catches all exceptions blindly. Consider being more specific:

-        except Exception:
-            raise SystemError("Failed to open SFTP connection for bootloader update")
+        except (IOError, OSError, paramiko.SSHException) as e:
+            raise SystemError("Failed to open SFTP connection for bootloader update") from e

This improves exception chaining and makes debugging easier while being more precise about what failures are expected.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d1cffb and 469ca81.

📒 Files selected for processing (1)
  • codexctl/device.py (14 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T16:37:10.452Z
Learnt from: Eeems
Repo: Jayy001/codexctl PR: 140
File: codexctl/__init__.py:54-55
Timestamp: 2025-09-10T16:37:10.452Z
Learning: In codexctl/__init__.py, the UpdateManager methods (get_latest_version, get_toltec_version, download_version) all expect HardwareType enum parameters, not string parameters.

Applied to files:

  • codexctl/device.py
🧬 Code graph analysis (1)
codexctl/device.py (1)
tests/test.py (1)
  • write (33-34)
🪛 Ruff (0.14.7)
codexctl/device.py

41-41: Avoid specifying long messages outside the exception class

(TRY003)


51-51: Avoid specifying long messages outside the exception class

(TRY003)


53-53: Avoid specifying long messages outside the exception class

(TRY003)


87-87: Avoid specifying long messages outside the exception class

(TRY003)


89-89: Avoid specifying long messages outside the exception class

(TRY003)


309-309: Consider moving this statement to an else block

(TRY300)


319-319: Avoid specifying long messages outside the exception class

(TRY003)


327-327: Avoid specifying long messages outside the exception class

(TRY003)


329-329: Avoid specifying long messages outside the exception class

(TRY003)


341-341: Avoid specifying long messages outside the exception class

(TRY003)


358-358: Probable insecure usage of temporary file or directory: "/tmp/mount_p"

(S108)


368-368: Avoid specifying long messages outside the exception class

(TRY003)


387-387: Avoid specifying long messages outside the exception class

(TRY003)


398-398: Avoid specifying long messages outside the exception class

(TRY003)


585-585: Avoid specifying long messages outside the exception class

(TRY003)


591-591: Avoid specifying long messages outside the exception class

(TRY003)


803-803: Avoid specifying long messages outside the exception class

(TRY003)


808-808: Do not catch blind exception: Exception

(BLE001)


809-809: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


809-809: Avoid specifying long messages outside the exception class

(TRY003)


811-811: Probable insecure usage of temporary file or directory: "/tmp/update-bootloader.sh"

(S108)


812-812: Probable insecure usage of temporary file or directory: "/tmp/imx-boot"

(S108)


840-840: Avoid specifying long messages outside the exception class

(TRY003)


849-849: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (9)
codexctl/device.py (9)

23-23: LGTM!

Clean addition of the Paper Pro Move hardware variant to the enum.


27-41: LGTM!

The refactor to use a match statement is cleaner and more idiomatic. The RMPPM case mapping and updated error message are well-structured.


51-53: LGTM!

RMPPM is correctly integrated into all hardware-specific properties with appropriate values and error messages for unsupported features.

Also applies to: 64-65, 76-77, 87-89


293-329: LGTM!

Well-structured method that cleanly handles version detection from both old and new partition layouts. The nested file_exists helper and fallback logic are solid.


331-375: LGTM!

Robust implementation for reading the backup partition version. The logic correctly handles different device types, mounts read-only, and ensures cleanup in the finally block. Error handling with SystemError is consistent with the codebase.


377-420: LGTM!

Solid implementation that correctly detects the partition scheme based on version and handles both new (MMC bootpart) and legacy (sysfs) partition detection methods with appropriate fallbacks.


422-479: LGTM!

The signature change to include backup partition version is well-integrated. The method correctly uses the new helper methods for remote execution and maintains equivalent logic for local execution.


575-617: LGTM!

Excellent implementation of the partition-switching logic for Paper Pro devices. The code correctly handles all transition scenarios between old (<3.22) and new (>=3.22) partition schemes by applying the appropriate combination of legacy sysfs and new MMC bootpart methods.


669-721: LGTM!

Clean integration of the bootloader update pathway. The optional parameter uses modern type hints, and the bootloader update is correctly sequenced after the main software update succeeds.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 469ca81 and 66840e4.

📒 Files selected for processing (5)
  • Makefile (4 hunks)
  • codexctl/__init__.py (7 hunks)
  • codexctl/analysis.py (2 hunks)
  • pyproject.toml (1 hunks)
  • requirements.txt (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T16:37:10.452Z
Learnt from: Eeems
Repo: Jayy001/codexctl PR: 140
File: codexctl/__init__.py:54-55
Timestamp: 2025-09-10T16:37:10.452Z
Learning: In codexctl/__init__.py, the UpdateManager methods (get_latest_version, get_toltec_version, download_version) all expect HardwareType enum parameters, not string parameters.

Applied to files:

  • codexctl/__init__.py
  • codexctl/analysis.py
🧬 Code graph analysis (2)
codexctl/__init__.py (3)
codexctl/device.py (4)
  • HardwareType (19-89)
  • get_device_status (422-479)
  • new_download_hw (56-65)
  • install_sw_update (669-781)
codexctl/analysis.py (2)
  • get_update_image (10-34)
  • get_swu_metadata (37-65)
codexctl/updates.py (3)
  • UpdateManager (17-435)
  • is_bootloader_boundary_downgrade (401-435)
  • download_version (179-263)
codexctl/analysis.py (1)
codexctl/device.py (1)
  • HardwareType (19-89)
🪛 checkmake (0.2.2)
Makefile

[warning] 63-63: Target body for "test" exceeds allowed length of 5 (37).

(maxbodylength)

🪛 Ruff (0.14.7)
codexctl/__init__.py

112-114: Avoid specifying long messages outside the exception class

(TRY003)


128-130: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


128-130: Avoid specifying long messages outside the exception class

(TRY003)


266-271: Avoid specifying long messages outside the exception class

(TRY003)


284-284: Avoid specifying long messages outside the exception class

(TRY003)


344-344: Avoid specifying long messages outside the exception class

(TRY003)


361-364: Avoid specifying long messages outside the exception class

(TRY003)


375-375: Avoid specifying long messages outside the exception class

(TRY003)

codexctl/analysis.py

60-60: Avoid specifying long messages outside the exception class

(TRY003)


63-63: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (10)
pyproject.toml (1)

15-16: Verify dependency versions support the new SWU extraction and bootloader features.

The dependency version bumps from 1.1.6→1.3 and 1.2.4→1.3 are aligned with the PR scope (SWU extraction, bootloader-aware updates), but the version spec (1.3) is permissive (allows any 1.3.x). For production device firmware management, consider whether stricter pinning (e.g., ^1.3.0) is needed to ensure deterministic behavior.

Confirm that:

  1. Tests validate integration with these library versions
  2. The new capabilities (SWU metadata extraction, bootloader handling) are exercised
  3. No breaking changes or unexpected behavior from the library updates
Makefile (2)

7-62: LGTM! Well-structured SWU test infrastructure.

The SWU test variables and download target follow the same proven pattern as the existing firmware test setup, providing consistent checksum validation and version tracking.


88-128: LGTM! Comprehensive SWU test coverage.

The test flows properly validate extraction, filesystem listing, and file reading for SWU files, with appropriate error handling and output validation.

codexctl/analysis.py (1)

37-65: LGTM! Clean SWU metadata extraction using remarkable-update-image.

The function correctly uses the remarkable-update-image library API to extract version and hardware type. The hardware mapping is consistent with the HardwareType.swupdate_hw property, and the validation ensures both hardware support and version presence.

codexctl/__init__.py (6)

73-83: LGTM! Consistent RMPPM support in list command.

The Paper Pro Move version listing follows the established pattern for other hardware variants, correctly integrating with the UpdateManager's remarkableppm_versions.


109-130: LGTM! Simplified extraction with proper error handling.

The extraction flow now cleanly delegates format detection to the UpdateImage library, while mount properly requires both necessary dependencies. The error messages clearly communicate missing dependencies.


226-234: LGTM! Backup version reporting integrated correctly.

The status and restore commands properly handle the expanded device status tuple, displaying the backup partition version as intended.


251-289: LGTM! Robust version extraction with hardware validation.

The SWU metadata extraction provides early hardware compatibility checking, preventing firmware mismatch installations. The graceful fallback to regex extraction and user prompts ensures compatibility with non-SWU files.


623-627: LGTM! Clean error handling for user-facing failures.

The SystemError catch wrapper provides a better user experience by converting internal SystemError exceptions into clean error messages on stderr with proper exit codes.


319-379: Verify remarkable-update-image version constraint to prevent runtime failures on bootloader extraction.

The bootloader downgrade implementation correctly handles the critical logic flow (boundary detection, user confirmation, error handling on download failure). However, lines 370-371 depend on the archive attribute from unreleased remarkable-update-image PR #16. If deployed with an older library version, this will fail at runtime with an AttributeError.

The code snippet shows no error handling around the archive access and no visible version constraint in dependencies. Before merging, confirm:

  • remarkable-update-image version is pinned to a release that includes PR #16, OR
  • a try-except block wraps lines 370-371 with a clear error message indicating the required library version

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
.github/workflows/main.yml (1)

118-132: Remove unnecessary wc -l pipe from disk cleanup step.

The | wc -l at the end counts the number of verbose output lines but doesn't contribute to the cleanup or provide actionable information. Remove it to reduce log noise.

Apply this diff:

  - name: Free up disk space
    shell: bash
    run: |
      sudo rm -vrf \
      /usr/lib/jvm \
      /usr/share/dotnet \
      /usr/share/swift \
      /usr/local/.ghcup \
      /usr/local/julia* \
      /usr/local/lib/android \
      /usr/local/share/chromium \
      /opt/microsoft /opt/google \
      /opt/az \
-     /usr/local/share/powershell \
-     | wc -l
+     /usr/local/share/powershell
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66840e4 and 5eba442.

📒 Files selected for processing (1)
  • .github/workflows/main.yml (2 hunks)
🔇 Additional comments (1)
.github/workflows/main.yml (1)

118-132: Disk cleanup placement and directory selection look sensible.

The step is well-positioned before the Docker build (which requires significant disk space for Nuitka ARM compilation), and the chosen directories (JVM, dotnet, Swift, Julia, Android, Chromium, cloud provider tooling) are standard CI environment bloat. This should free adequate space for the build pipeline.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
codexctl/updates.py (2)

86-101: LGTM! Robust parsing with backward compatibility.

The provider URL parsing correctly handles both legacy single-string and new list formats, with appropriate validation to catch missing configuration early.

Note: Static analysis flags the error message on lines 90-93 as potentially verbose (TRY003), but it's helpful for troubleshooting configuration issues.


400-435: LGTM! Thorough validation and correct boundary detection logic.

The bootloader boundary detection correctly identifies downgrades crossing 3.22 (from ≥3.22 to <3.22) using lexicographic list comparison. Input validation is comprehensive with clear error messages for debugging.

Note: Static analysis flags error messages on lines 419-421, 427, and 430 as potentially verbose (TRY003). These are helpful for diagnosing version format issues, so deferring changes is reasonable given the "Chill" review mode.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5eba442 and 7b8b39f.

📒 Files selected for processing (1)
  • codexctl/updates.py (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
codexctl/updates.py (1)
codexctl/device.py (2)
  • HardwareType (19-89)
  • new_download_hw (56-65)
🪛 Ruff (0.14.7)
codexctl/updates.py

90-93: Avoid specifying long messages outside the exception class

(TRY003)


419-419: Avoid specifying long messages outside the exception class

(TRY003)


421-421: Avoid specifying long messages outside the exception class

(TRY003)


427-427: Avoid specifying long messages outside the exception class

(TRY003)


430-430: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (5)
codexctl/updates.py (5)

30-36: LGTM! New attributes initialized correctly.

The addition of remarkableppm_versions and external_provider_urls attributes aligns with the expanded functionality for Paper Pro Move support and multi-provider downloads.


145-146: LGTM! RMPPM support added correctly.

The Paper Pro Move hardware type is handled consistently with other device types.


216-217: LGTM! RMPPM download support implemented.

Correctly maps HardwareType.RMPPM to remarkableppm_versions for version lookups.


248-263: LGTM! Well-implemented multi-provider fallback.

The download logic correctly iterates through provider URLs with appropriate fallback behavior. Returning on first success and logging at debug level for intermediate failures provides good observability without noise.


331-331: LGTM! Appropriate logging level for multi-provider context.

Changing from error to debug level makes sense when multiple providers are attempted—intermediate failures are expected, and only final failure (logged at line 262) represents an actual error condition.

@rmitchellscott rmitchellscott requested a review from Eeems December 3, 2025 20:00
Copy link
Collaborator

@Eeems Eeems left a comment

Choose a reason for hiding this comment

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

I had some time to go through this from top to bottom again and found some more things and nits.

Co-authored-by: Nathaniel van Diepen <Eeems@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
README.md (2)

61-64: Add bash language identifier to code block.

The code block is missing a language specifier, which violates markdown linting rules (MD040).

Apply this diff:

 - Downloading rmppm version 3.23.0.64 to a folder named `out` and then installing it
-```
+```bash
 codexctl download 3.23.0.64 --hardware rmppm -o out
 codexctl install ./out/remarkable-production-image-3.23.0.64-chiappa-public.swu

---

`88-90`: **Add bash language identifier to code block.**

The code block is missing a language specifier, which violates markdown linting rules (MD040).




Apply this diff:

```diff
 - Extract the filesystem image of an upgrade file as a file named `extracted`
-```
+```bash
 codexctl extract remarkable-production-image-3.22.0.64-ferrari-public.swu -o extracted

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>codexctl/__init__.py (1)</summary><blockquote>

`124-130`: **Consider mentioning both required libraries in the mount error message.**

The mount path requires both `remarkable_update_image` (for `get_update_image`) and `remarkable_update_fuse` (for `UpdateFS`), but the error message only mentions the latter.



Apply this diff to make the error more informative:

```diff
             try:
                 from .analysis import get_update_image
                 from remarkable_update_fuse import UpdateFS
             except ImportError:
                 raise ImportError(
-                    "remarkable_update_fuse is required for mounting. Please install it!"
+                    "remarkable_update_image and remarkable_update_fuse are required for mounting. Please install them!"
                 )
codexctl/device.py (1)

798-798: Consider moving tempfile import to module level.

The tempfile import at line 798 is inside the method. Moving it to the top of the file with other imports improves consistency and follows Python conventions.

Based on learnings, imports should generally be at module level unless they're optional dependencies that need feature detection.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b8b39f and 3941f39.

📒 Files selected for processing (5)
  • README.md (4 hunks)
  • codexctl/__init__.py (7 hunks)
  • codexctl/analysis.py (1 hunks)
  • codexctl/device.py (14 hunks)
  • codexctl/updates.py (7 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T16:37:10.452Z
Learnt from: Eeems
Repo: Jayy001/codexctl PR: 140
File: codexctl/__init__.py:54-55
Timestamp: 2025-09-10T16:37:10.452Z
Learning: In codexctl/__init__.py, the UpdateManager methods (get_latest_version, get_toltec_version, download_version) all expect HardwareType enum parameters, not string parameters.

Applied to files:

  • codexctl/analysis.py
  • codexctl/__init__.py
  • codexctl/device.py
🧬 Code graph analysis (3)
codexctl/analysis.py (1)
codexctl/device.py (1)
  • HardwareType (19-89)
codexctl/__init__.py (2)
codexctl/device.py (4)
  • HardwareType (19-89)
  • get_device_status (422-479)
  • new_download_hw (56-65)
  • install_sw_update (669-781)
codexctl/updates.py (1)
  • is_bootloader_boundary_downgrade (402-436)
codexctl/updates.py (2)
codexctl/device.py (2)
  • HardwareType (19-89)
  • new_download_hw (56-65)
codexctl/__init__.py (1)
  • version_lookup (248-249)
🪛 markdownlint-cli2 (0.18.1)
README.md

61-61: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


88-88: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 Ruff (0.14.7)
codexctl/__init__.py

112-114: Avoid specifying long messages outside the exception class

(TRY003)


128-130: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


128-130: Avoid specifying long messages outside the exception class

(TRY003)


262-262: Avoid specifying long messages outside the exception class

(TRY003)


272-272: Avoid specifying long messages outside the exception class

(TRY003)


278-283: Avoid specifying long messages outside the exception class

(TRY003)


296-296: Avoid specifying long messages outside the exception class

(TRY003)


356-356: Avoid specifying long messages outside the exception class

(TRY003)


373-376: Avoid specifying long messages outside the exception class

(TRY003)


387-387: Avoid specifying long messages outside the exception class

(TRY003)

codexctl/updates.py

88-91: Avoid specifying long messages outside the exception class

(TRY003)


420-420: Avoid specifying long messages outside the exception class

(TRY003)


422-422: Avoid specifying long messages outside the exception class

(TRY003)


428-428: Avoid specifying long messages outside the exception class

(TRY003)


431-431: Avoid specifying long messages outside the exception class

(TRY003)

codexctl/device.py

41-41: Avoid specifying long messages outside the exception class

(TRY003)


51-51: Avoid specifying long messages outside the exception class

(TRY003)


53-53: Avoid specifying long messages outside the exception class

(TRY003)


87-87: Avoid specifying long messages outside the exception class

(TRY003)


89-89: Avoid specifying long messages outside the exception class

(TRY003)


309-309: Consider moving this statement to an else block

(TRY300)


319-319: Avoid specifying long messages outside the exception class

(TRY003)


327-327: Avoid specifying long messages outside the exception class

(TRY003)


329-329: Avoid specifying long messages outside the exception class

(TRY003)


341-341: Avoid specifying long messages outside the exception class

(TRY003)


358-358: Probable insecure usage of temporary file or directory: "/tmp/mount_p"

(S108)


368-368: Avoid specifying long messages outside the exception class

(TRY003)


387-387: Avoid specifying long messages outside the exception class

(TRY003)


398-398: Avoid specifying long messages outside the exception class

(TRY003)


583-583: Avoid specifying long messages outside the exception class

(TRY003)


589-589: Avoid specifying long messages outside the exception class

(TRY003)


803-803: Avoid specifying long messages outside the exception class

(TRY003)


808-808: Do not catch blind exception: Exception

(BLE001)


809-809: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


809-809: Avoid specifying long messages outside the exception class

(TRY003)


811-811: Probable insecure usage of temporary file or directory: "/tmp/update-bootloader.sh"

(S108)


812-812: Probable insecure usage of temporary file or directory: "/tmp/imx-boot"

(S108)


840-840: Avoid specifying long messages outside the exception class

(TRY003)


849-849: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (19)
codexctl/analysis.py (1)

7-7: LGTM! Import aligns with RMPPM support.

The HardwareType import is correctly added and necessary for the broader hardware variant support introduced in this PR.

codexctl/__init__.py (5)

73-83: LGTM! RMPPM version list support is well-integrated.

The remarkableppm_versions handling follows the established pattern for other hardware types and integrates cleanly into the list output.


226-234: LGTM! Backup version reporting is correctly integrated.

The status and restore flows properly use the expanded 5-tuple from get_device_status and display the backup partition version clearly.


331-392: LGTM! Bootloader-aware downgrade handling is well-implemented.

The boundary detection, user confirmation, SWU download with fallback, and bootloader file extraction are all correctly implemented. The use of UpdateImage.archive[b'...'] is valid with remarkable-update-image v1.3, which is specified in the project dependencies.


433-433: LGTM! Bootloader files correctly passed to install flow.

The bootloader_files parameter is properly passed to install_sw_update, matching the updated signature in device.py.


635-639: LGTM! Top-level error handling improves user experience.

The SystemError handler provides clean, user-friendly error messages instead of raw stack traces, which is good practice for a CLI tool.

codexctl/updates.py (5)

30-36: LGTM! Initialization correctly handles RMPPM and multi-provider URLs.

The unpacking of remarkableppm_versions and external_provider_urls (as a list) correctly matches the updated return signature of get_remarkable_versions.


86-101: LGTM! Provider URL validation is defensive and well-structured.

The logic correctly handles both old (external-provider-url) and new (external-provider-urls) formats, validates presence before use, and normalizes to a list. The safe .get() calls with empty dict defaults are good defensive programming.


146-147: LGTM! RMPPM hardware type handling is consistent.

The RMPPM cases in both get_latest_version and download_version correctly select remarkableppm_versions and follow the established pattern for other hardware types.

Also applies to: 217-218


241-264: LGTM! Multi-provider download logic enhances resilience.

The iteration through multiple provider URLs with appropriate debug logging provides good fallback behavior. Downgrading the non-200 log to debug level (line 332) is the right choice for multi-source downloads to avoid excessive error noise.

Also applies to: 332-332


401-436: LGTM! Bootloader boundary detection is robust and well-tested.

The is_bootloader_boundary_downgrade method has thorough input validation, clear error messages, correct comparison logic for the 3.22 boundary, and proper error chaining. The test coverage in tests/test.py validates the boundary cases.

codexctl/device.py (8)

23-41: LGTM! RMPPM hardware type and match-based parsing are well-implemented.

The new RMPPM enum value and the refactored match statement provide clear, idiomatic parsing with comprehensive alias support and a helpful error message.


51-53: LGTM! RMPPM property mappings are consistent and correct.

All hardware property mappings for RMPPM follow the established pattern, use correct identifiers (chiappa for swupdate), and provide clear error messages for unsupported features.

Also applies to: 64-65, 76-77, 87-89


293-329: LGTM! Version reading abstraction is well-designed.

The _read_version_from_path helper cleanly handles both old (update.conf) and new (os-release) version file formats with appropriate fallback logic and error handling.


331-375: LGTM! Backup partition version detection is robust.

The partition detection correctly handles both Paper Pro (using swupdate -g) and older devices (using rootdev), mounts read-only for safety, and guarantees cleanup via the finally block. The /tmp usage flagged by static analysis is standard practice for temporary mounts in Linux environments.


377-420: LGTM! Partition detection handles 3.20/3.22 boundary correctly.

The _get_paper_pro_partition_info method correctly handles the partition scheme changes between firmware versions, with defensive fallbacks and appropriate error handling for both old and new boot mechanisms.


422-479: LGTM! Status reporting correctly includes backup version.

The updated get_device_status properly uses the new version-reading helper and returns the 5-tuple including backup partition version, matching the signature expected by callers in __init__.py.


575-617: LGTM! Restore logic handles partition scheme transitions correctly.

The enhanced restore_previous_version for RMPP/RMPPM correctly handles all transition cases across the 3.22 bootloader boundary by conditionally applying both legacy (sysfs) and new (MMC bootpart) methods as needed.


669-721: LGTM! Bootloader update workflow is well-implemented.

The bootloader update flow correctly uploads both the script and image, executes preinst and postinst stages, validates exit statuses, and ensures cleanup in the finally block. The error handling is appropriate for a critical firmware update operation.

Also applies to: 783-861

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
codexctl/device.py (1)

784-860: Bootloader update implementation is solid, with one exception handling refinement.

The method correctly:

  • Initializes ftp_client = None before the try block (addressing past review concern)
  • Uploads bootloader script and image via SFTP
  • Executes both preinst and postinst phases with exit status validation
  • Cleans up temporary files on both device and local system in the finally block
  • Guards ftp_client.close() to avoid NameError (addressing past review concern)

The logic and error handling are robust overall.

Consider making the exception handling at line 807 more specific to avoid catching unexpected errors:

-        except Exception:
-            raise SystemError("Failed to open SFTP connection for bootloader update")
+        except (paramiko.SSHException, OSError) as e:
+            raise SystemError("Failed to open SFTP connection for bootloader update") from e

This narrows the catch to expected connection/SFTP errors and preserves the exception chain for debugging.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3941f39 and dd2951f.

📒 Files selected for processing (2)
  • codexctl/__init__.py (7 hunks)
  • codexctl/device.py (15 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T16:37:10.452Z
Learnt from: Eeems
Repo: Jayy001/codexctl PR: 140
File: codexctl/__init__.py:54-55
Timestamp: 2025-09-10T16:37:10.452Z
Learning: In codexctl/__init__.py, the UpdateManager methods (get_latest_version, get_toltec_version, download_version) all expect HardwareType enum parameters, not string parameters.

Applied to files:

  • codexctl/device.py
  • codexctl/__init__.py
🪛 Ruff (0.14.7)
codexctl/device.py

42-42: Avoid specifying long messages outside the exception class

(TRY003)


52-52: Avoid specifying long messages outside the exception class

(TRY003)


54-54: Avoid specifying long messages outside the exception class

(TRY003)


88-88: Avoid specifying long messages outside the exception class

(TRY003)


90-90: Avoid specifying long messages outside the exception class

(TRY003)


310-310: Consider moving this statement to an else block

(TRY300)


320-320: Avoid specifying long messages outside the exception class

(TRY003)


328-328: Avoid specifying long messages outside the exception class

(TRY003)


330-330: Avoid specifying long messages outside the exception class

(TRY003)


342-342: Avoid specifying long messages outside the exception class

(TRY003)


359-359: Probable insecure usage of temporary file or directory: "/tmp/mount_p"

(S108)


369-369: Avoid specifying long messages outside the exception class

(TRY003)


388-388: Avoid specifying long messages outside the exception class

(TRY003)


399-399: Avoid specifying long messages outside the exception class

(TRY003)


584-584: Avoid specifying long messages outside the exception class

(TRY003)


590-590: Avoid specifying long messages outside the exception class

(TRY003)


802-802: Avoid specifying long messages outside the exception class

(TRY003)


807-807: Do not catch blind exception: Exception

(BLE001)


808-808: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


808-808: Avoid specifying long messages outside the exception class

(TRY003)


810-810: Probable insecure usage of temporary file or directory: "/tmp/update-bootloader.sh"

(S108)


811-811: Probable insecure usage of temporary file or directory: "/tmp/imx-boot"

(S108)


839-839: Avoid specifying long messages outside the exception class

(TRY003)


848-848: Avoid specifying long messages outside the exception class

(TRY003)

codexctl/__init__.py

112-114: Avoid specifying long messages outside the exception class

(TRY003)


128-130: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


128-130: Avoid specifying long messages outside the exception class

(TRY003)


213-215: Avoid specifying long messages outside the exception class

(TRY003)


269-269: Avoid specifying long messages outside the exception class

(TRY003)


279-279: Avoid specifying long messages outside the exception class

(TRY003)


285-290: Avoid specifying long messages outside the exception class

(TRY003)


303-303: Avoid specifying long messages outside the exception class

(TRY003)


363-363: Avoid specifying long messages outside the exception class

(TRY003)


380-383: Avoid specifying long messages outside the exception class

(TRY003)


394-394: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (12)
codexctl/__init__.py (6)

73-89: LGTM: Paper Pro Move support in list command.

The RMPPM version listing correctly follows the existing pattern for other hardware types and integrates cleanly with the conditional output blocks.


109-130: LGTM: Extraction and mount flows now use remarkable-update-image library.

The extraction path correctly uses UpdateImage and read() for direct extraction, and the mount path consolidates the import error handling for both required libraries. This aligns with the maintainer's feedback from previous reviews.


210-215: LGTM: Install path correctly imports UpdateImage.

The import is appropriately placed and will support the SWU metadata extraction logic later in the install flow.


233-241: LGTM: Status and restore now include backup partition version.

The changes correctly unpack the extended return value from get_device_status and display the backup version in both status output and restore confirmation.


258-307: LGTM: SWU metadata extraction with hardware validation.

The metadata extraction correctly uses remarkable-update-image to read version and hardware type from CPIO-based SWU files, validates hardware compatibility, and falls back to manual version input when metadata is unavailable. The hardware mismatch check prevents accidental installation of incompatible firmware.

Minor note: Line 263 re-imports UpdateImage, which was already imported at line 210. This is harmless but could be consolidated if you prefer.


338-398: LGTM: Bootloader boundary downgrade handling is comprehensive and safe.

The implementation correctly:

  • Detects 3.22+ → <3.22 downgrades for Paper Pro devices
  • Prompts users with clear warnings before proceeding
  • Reuses existing SWU files or downloads them automatically
  • Raises a hard error if the current version SWU cannot be obtained (addressing past review concern)
  • Extracts bootloader files using the remarkable-update-image 1.3 API
  • Validates that both required files were successfully extracted

The user confirmation prompt and validation checks make this a safe implementation of a complex and risky operation.

codexctl/device.py (6)

1-24: LGTM: Required imports and RMPPM hardware type added.

The new imports support the version reading helpers and bootloader update functionality, and the RMPPM enum value correctly extends the hardware type enumeration.


28-90: LGTM: RMPPM parsing and properties correctly implemented.

The hardware type parsing correctly handles RMPPM device identifiers, and all property methods appropriately return the corresponding values or raise clear errors for unsupported features (old update engine, toltec).


294-421: LGTM: Version reading and partition info helpers are well-structured.

The new helper methods correctly:

  • Read version information from both current and backup partitions
  • Handle both old (update.conf) and new (os-release) version file formats
  • Mount/unmount backup partitions safely with proper cleanup in finally blocks
  • Detect Paper Pro partition schemes based on version numbers
  • Raise clear SystemError exceptions for failure cases (addressing past review concerns)

The /tmp usage flagged by static analysis is acceptable here since these are controlled, temporary mount points on reMarkable devices with proper cleanup.


423-480: LGTM: Status method correctly returns backup partition version.

The refactored get_device_status now uses the new helper methods and correctly returns the backup partition version as the fifth tuple element, integrating cleanly with the calling code in __init__.py.


558-618: LGTM: Paper Pro restore correctly handles partition scheme transitions.

The RMPP/RMPPM-specific restore logic properly:

  • Validates both current and backup version formats with early exit pattern (addressing past review feedback)
  • Constructs appropriate restore commands based on version boundaries
  • Uses legacy sysfs method for versions < 3.22
  • Uses MMC bootpart method when either current or target is >= 3.22
  • Handles all transition cases (old→old, old→new, new→old, new→new)

The conditional logic ensures the device can boot successfully regardless of which partition scheme change is involved.


670-722: LGTM: Bootloader files parameter integrated into install flow.

The updated signature correctly accepts optional bootloader files and triggers the bootloader update after successful software installation. The conditional execution ensures the bootloader update only runs when explicitly provided.

@rmitchellscott rmitchellscott requested a review from Eeems December 5, 2025 02:01
@Eeems Eeems changed the title feat: Support for 3.22 & 3.23, Paper Pro Move, Paper Pro restores, swu extraction. feat: Support for 3.22 & 3.23, Paper Pro Move, Paper Pro restores Dec 5, 2025
@Eeems
Copy link
Collaborator

Eeems commented Dec 5, 2025

@coderabbitai summary

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 5, 2025

✅ Actions performed

Summary regeneration triggered.

@Eeems Eeems merged commit 3b77cbf into Jayy001:main Dec 5, 2025
8 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Dec 12, 2025
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.

2 participants