Skip to content

Conversation

@tuanwannafly
Copy link
Contributor

@tuanwannafly tuanwannafly commented Jan 5, 2026

Summary

This PR adds a lightweight Hybrid GPU (Optimus) manager feature focused on reducing latency and stuttering caused by global GPU switching.

It introduces GPU state detection and heuristic battery impact estimation without requiring global mode changes or session restarts.

Related Issue

Closes #454

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

AI Disclosure

  • AI/IDE/Agents used

Used ChatGPT as a reasoning assistant to help design the solution, validate edge cases, and improve clarity of the implementation and testing approach.

Testing

  • Ran cortex gpu-battery to verify GPU state detection and battery impact estimates.
  • Verified per-application GPU assignment logic using NVIDIA PRIME offload environment variables.
  • Confirmed no global GPU switching, logout, or session restart is required.
  • Tested on WSL; design avoids reliance on a graphical desktop environment.
  • Added unit tests covering:
    • GPU mode detection
    • Battery impact estimation structure
    • Safe per-application GPU detection behavior
  • All tests pass locally via pytest.

Checklist

  • Tests pass locally
  • Code follows style guide
  • Documentation updated (CLI help)

Demo Video

Demo Videos (9-part reviewer walkthrough)

I’m attaching 9 short videos, each mapped 1:1 to the script sections below, to demonstrate full Hybrid GPU Manager scope: state, battery estimation, per-app assignment, env injection, and mode switching (dry-run + execute path).

  1. Video 1 — Version + Environment
vid1.mp4
  • Shows cortex binary is available and prints versions for Cortex, Python, and Pytest.
  1. Video 2 — Collect GPU Tests
vid2.mp4
  • Runs pytest --collect-only filtered by gpu to prove relevant tests exist and are discoverable.
  1. Video 3 — Unit Tests (GPU Manager)
vid3.mp4
  • Executes pytest -vv tests/test_gpu_manager.py -rA to validate:
    • backend detection (prime-select / system76-power),
    • switch plan creation,
    • per-app config roundtrip,
    • env merge behavior.
  1. Video 4 — Unit Tests (Hybrid Detection + Battery)
vid4.mp4
  • Executes pytest -vv tests/test_hybrid_gpu_manager.py -rA to validate:
    • safe GPU mode detection,
    • battery estimate structure,
    • safe NVIDIA detection behavior.
  1. Video 5 — GPU Status
vid5.mp4
  • Runs cortex gpu status showing:
    • current GPU mode,
    • detected switching backend,
    • number of per-app assignments.
  1. Video 6 — Battery Impact Estimation
vid6.mp4
  • Runs cortex gpu-battery (heuristics + measured fields when available),
  • Also runs cortex gpu-battery --measured-only to demonstrate safe degradation when measurements are unavailable.
  1. Video 7 — Per-app Assignment (Persistence Evidence)
vid7.mp4
  • Uses a temporary CORTEX_CONFIG_DIR,
  • Sets preference blender -> nvidia,
  • Reads it back (get, list),
  • Prints persisted config.yaml,
  • Removes the preference and verifies it’s gone.
  1. Video 8 — GPU Run (Environment Injection)
vid8.mp4
  • Demonstrates cortex gpu run applying env vars correctly:
    • --integrated override,
    • --nvidia override,
    • --app blender resolves to the saved preference,
  • Confirms via printing __NV_PRIME_RENDER_OFFLOAD and __GLX_VENDOR_LIBRARY_NAME.
  1. Video 9 — Switching (Dry-run + Execute Path with Fake Backend)
vid9.mp4
  • Shows dry-run switching commands for integrated/hybrid/nvidia,
  • Injects a fake sudo and prime-select into PATH,
  • Runs --execute --yes to prove the execute path calls expected backend commands without requiring real system mutation in the demo environment.

Bonus in the same script — Negative CLI Validation

Uploading vid10.mp4…

  • Confirms invalid modes are rejected:
    • cortex gpu set turbo ...
    • cortex gpu app set blender turbo ...

Summary by CodeRabbit

  • New Features

    • CLI: added gpu and gpu-battery commands to view GPU/battery status, switch GPU modes (dry-run or execute), run commands with GPU selection, and manage per‑app GPU preferences.
  • New Utilities

    • Runtime GPU mode detection, NVIDIA power and battery measurements, battery‑impact estimation, per‑app GPU env provisioning, and mode‑switch planning/execution with backend detection.
  • Documentation

    • Comprehensive Hybrid GPU Manager guide with usage, configuration, CLI examples, and troubleshooting.
  • Tests

    • New unit tests covering detection, battery estimates, planning, per‑app preferences, and run/env behavior.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 5, 2026

Warning

Rate limit exceeded

@tuanwannafly has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 46 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 7d2d30a and ca8ac4a.

📒 Files selected for processing (1)
  • cortex/cli.py
📝 Walkthrough

Walkthrough

Adds a Hybrid GPU Manager: hardware GPU detection and battery estimation, per‑app GPU preferences and env injection, GPU mode‑switch planning/execution with backend detection, CLI commands (gpu-battery, gpu with subcommands), documentation, and unit tests.

Changes

Cohort / File(s) Summary
CLI: gpu commands
cortex/cli.py
Adds gpu-battery and gpu top-level commands; implements CortexCLI.gpu_battery() and CortexCLI.gpu() handlers; wires subcommands (status, set, run, app) and flags (--measured-only, --dry-run, --yes, --app, --nvidia, --integrated).
Hardware detection & estimator
cortex/hardware_detection.py
New helpers _run(), get_nvidia_power_draw_watts(), detect_nvidia_gpu(), detect_gpu_mode(), Linux/Windows battery readers, and estimate_gpu_battery_impact() combining heuristics and optional measurements.
GPU manager core
cortex/gpu_manager.py
New module defining GPUSwitchBackend, GPUSwitchPlan, GPUAppMode, per‑app prefs persisted in ~/.cortex/config.yaml (YAML/JSON fallback), APIs set/get/remove/list, get_per_app_gpu_env, detect_gpu_switch_backend, plan_gpu_mode_switch, apply_gpu_mode_switch, and run_command_with_env.
Tests: gpu manager & detection
tests/test_gpu_manager.py, tests/test_hybrid_gpu_manager.py
New unit tests covering backend detection, plan generation (prime-select/system76-power), run_command_with_env, per‑app prefs lifecycle and env injection, validation, detect_gpu_mode, detect_nvidia_gpu, and estimator output; includes monkeypatched scenarios.
Docs
docs/HYBRID_GPU_MANAGER.md
New documentation describing module purpose, CLI usage, config schema, API reference, backend notes, testing guidance, and limitations.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CLI as CortexCLI
    participant HD as hardware_detection
    participant GM as gpu_manager
    participant Sys as System
    participant Config as Config(~/.cortex/config.yaml)

    User->>CLI: run `cortex gpu status` / `cortex gpu-battery` / `cortex gpu run`
    CLI->>HD: detect_gpu_mode() or estimate_gpu_battery_impact()
    HD->>Sys: run lspci / nvidia-smi / read battery files / PowerShell
    Sys-->>HD: sensor & battery outputs
    HD-->>CLI: mode / estimates

    CLI->>GM: detect_gpu_switch_backend() / plan_gpu_mode_switch(target)
    GM->>Sys: probe for prime-select / system76-power
    GM->>Config: read/write per-app prefs
    GM-->>CLI: plan (commands, backend, restart) / env for run

    CLI->>Sys: run_command_with_env(cmd, extra_env) or apply_gpu_mode_switch(plan, execute?)
    Sys-->>CLI: command exit status
    CLI->>User: print status / results / exit code
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

enhancement

Suggested reviewers

  • mikejmorgan-ai
  • Anshgrover23

Poem

🐰 I hopped through processes and calls,
I sniffed for GPUs behind the walls,
I counted watts and mapped each mode,
I tucked app wishes in a config code,
Now switches hum and battery tales unfold.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.16% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Hybrid GPU Manager: battery impact estimation and GPU state detection v1' directly summarizes the main features added: battery estimation and GPU state detection for a Hybrid GPU Manager.
Description check ✅ Passed The description follows the template well, includes a clear summary, related issue (#454), AI disclosure, testing details, and a comprehensive checklist. All required sections are present and adequately filled.
Linked Issues check ✅ Passed The PR fully implements all four objectives from issue #454: GPU state detection [gpu_manager.py, hardware_detection.py], per-app GPU assignment [gpu_manager.py], easy switching with battery impact [cli.py, gpu_manager.py], and battery estimates [hardware_detection.py].
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #454's Hybrid GPU Manager objectives. No unrelated modifications or refactoring outside the GPU management scope detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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: 4

Fix all issues with AI Agents 🤖
In @cortex/cli.py:
- Around line 1951-1953: A blank line after the elif args.command ==
"gpu-battery": return cli.gpu_battery() block contains trailing whitespace which
triggers the linter; remove the trailing spaces so the line is truly empty (or
collapse the blank line entirely) to eliminate the whitespace character and
satisfy the pipeline, locating the spot by the args.command == "gpu-battery"
check and the cli.gpu_battery() call.

In @cortex/hardware_detection.py:
- Around line 756-777: Remove the trailing whitespace on the blank line
following the _run function definition: open the file, locate the _run function
(the try/except block that returns "" on exceptions) and delete any spaces/tabs
at the end of the blank line after the except block so the line is truly empty
(ensure the file ends with a single newline and no trailing whitespace).
- Around line 813-847: The file is missing a trailing newline which causes
lint/pipeline failures; add a final newline character at the end of
cortex/hardware_detection.py so the file ends with a newline. Also (optional)
expand the estimate_gpu_battery_impact docstring to describe the return dict
structure (keys "mode", "current", "estimates"), the heuristic basis for the
power/impact values, and caveats that these are non-root, non-realtime estimates
to improve maintainability and clarity.

In @tests/test_hybrid_gpu_manager.py:
- Around line 39-41: The file is missing a trailing newline which breaks
linters; open the test file containing
test_detect_gpu_mode_does_not_crash_and_returns_value (which calls
detect_gpu_mode()) and add a single newline character at the end of the file so
the file ends with a newline (ensure the editor saves the file with a trailing
newline).
🧹 Nitpick comments (2)
cortex/cli.py (1)

935-958: Consider adding error handling for robustness.

The method assumes estimate_gpu_battery_impact() always returns a well-formed dictionary. If the underlying implementation changes or fails, this could raise KeyError or AttributeError. Consider wrapping the call in a try-except block to handle potential errors gracefully.

🔎 Proposed error handling
 def gpu_battery(self) -> int:
     """Estimate battery impact based on GPU usage"""
+    try:
         data = estimate_gpu_battery_impact()
 
         cx_print(f"GPU Mode: {data['mode']}", "info")
         print()
 
         cx_print("Estimated power draw:", "info")
         print(f"- Integrated GPU only: {data['estimates']['integrated']['power']}")
         print(f"- Hybrid (idle dGPU): {data['estimates']['hybrid_idle']['power']}")
         print(f"- NVIDIA active: {data['estimates']['nvidia_active']['power']}")
         print()
 
         cx_print("Estimated battery impact:", "info")
         print(f"- Hybrid idle: {data['estimates']['hybrid_idle']['impact']}")
         print(f"- NVIDIA active: {data['estimates']['nvidia_active']['impact']}")
         print()
 
         cx_print(
             "Note: Estimates are heuristic and vary by hardware and workload.",
             "warning",
         )
 
         return 0
+    except (KeyError, TypeError) as e:
+        self._print_error(f"Failed to estimate GPU battery impact: {e}")
+        return 1
cortex/hardware_detection.py (1)

800-810: Consider enhancing the docstring.

The function logic is sound for best-effort GPU mode detection. However, per the coding guidelines requiring docstrings for all public APIs, consider expanding the docstring to document:

  • Return values and their meanings
  • Detection methodology
  • Limitations/caveats
🔎 Proposed docstring enhancement
 def detect_gpu_mode() -> str:
     """
-    Best-effort GPU mode detection
+    Detect the current GPU mode on the system.
+
+    Returns one of:
+    - "Integrated": Only integrated GPU detected or no PCI devices found
+    - "NVIDIA": NVIDIA discrete GPU detected and available
+    - "Hybrid": PCI devices found but no NVIDIA GPU active (may be AMD or powered-down NVIDIA)
+
+    Note: This is a best-effort heuristic and may not reflect the actual hardware
+    configuration in all cases (e.g., if NVIDIA GPU is present but powered down).
+
+    Returns:
+        str: One of "Integrated", "NVIDIA", or "Hybrid"
     """
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 29c51cc and 098a7de.

📒 Files selected for processing (3)
  • cortex/cli.py
  • cortex/hardware_detection.py
  • tests/test_hybrid_gpu_manager.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/hardware_detection.py
  • cortex/cli.py
  • tests/test_hybrid_gpu_manager.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Maintain >80% test coverage for pull requests

Files:

  • tests/test_hybrid_gpu_manager.py
🧬 Code graph analysis (3)
cortex/hardware_detection.py (1)
cortex/first_run_wizard.py (1)
  • run (169-228)
cortex/cli.py (2)
cortex/hardware_detection.py (1)
  • estimate_gpu_battery_impact (813-847)
cortex/branding.py (1)
  • cx_print (49-69)
tests/test_hybrid_gpu_manager.py (1)
cortex/hardware_detection.py (3)
  • detect_gpu_mode (800-810)
  • detect_nvidia_gpu (780-797)
  • estimate_gpu_battery_impact (813-847)
🪛 GitHub Actions: CI
cortex/cli.py

[error] 1953-1953: Ruff check failed. W293 Blank line contains whitespace. cortex/cli.py:1953

🪛 GitHub Check: lint
cortex/hardware_detection.py

[failure] 847-847: Ruff (W292)
cortex/hardware_detection.py:847:6: W292 No newline at end of file


[failure] 777-777: Ruff (W293)
cortex/hardware_detection.py:777:1: W293 Blank line contains whitespace

cortex/cli.py

[failure] 1953-1953: Ruff (W293)
cortex/cli.py:1953:1: W293 Blank line contains whitespace

tests/test_hybrid_gpu_manager.py

[failure] 41-41: Ruff (W292)
tests/test_hybrid_gpu_manager.py:41:28: W292 No newline at end of file

🪛 GitHub Check: Lint
cortex/hardware_detection.py

[failure] 847-847: Ruff (W292)
cortex/hardware_detection.py:847:6: W292 No newline at end of file


[failure] 777-777: Ruff (W293)
cortex/hardware_detection.py:777:1: W293 Blank line contains whitespace

cortex/cli.py

[failure] 1953-1953: Ruff (W293)
cortex/cli.py:1953:1: W293 Blank line contains whitespace

tests/test_hybrid_gpu_manager.py

[failure] 41-41: Ruff (W292)
tests/test_hybrid_gpu_manager.py:41:28: W292 No newline at end of file

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
🔇 Additional comments (4)
tests/test_hybrid_gpu_manager.py (3)

8-11: LGTM!

The test properly validates that detect_gpu_mode() returns a valid state string.


14-31: LGTM!

The test comprehensively validates the structure of the battery impact estimation data, ensuring the contract is met.


34-36: LGTM!

The test validates that detect_nvidia_gpu() safely returns a boolean value.

cortex/hardware_detection.py (1)

780-797: LGTM!

The function provides a clean, safe way to detect NVIDIA GPUs using nvidia-smi. The docstring appropriately documents limitations and the implementation is straightforward.

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

Fix all issues with AI Agents 🤖
In @cortex/hardware_detection.py:
- Around line 800-810: The detect_gpu_mode function's docstring is missing
documentation of its possible return values; update the docstring for
detect_gpu_mode() to list and describe each return string ("Integrated" = no PCI
GPUs detected or lspci unavailable, "NVIDIA" = an NVIDIA GPU was detected via
detect_nvidia_gpu(), "Hybrid" = PCI devices present but no NVIDIA detected), and
include the return type annotation note (str) and brief explanation of detection
heuristics so callers understand the meanings.
♻️ Duplicate comments (3)
cortex/cli.py (1)

1951-1953: Fix trailing whitespace on line 1953.

Line 1953 contains trailing whitespace, causing linter and pipeline failures (Ruff W293). This issue was previously flagged and remains unresolved.

🔎 Proposed fix
         elif args.command == "gpu-battery":
             return cli.gpu_battery()
-        
+
         else:
cortex/hardware_detection.py (2)

757-778: Fix trailing whitespace on blank line.

The _run() helper function is well-implemented with proper error handling, comprehensive docstring, and type hints. However, the blank line after the function (line 778) contains trailing whitespace, causing linter failures (Ruff W293). This issue was previously flagged and remains unresolved.

🔎 Proposed fix
         return result.stdout.strip()
     except (subprocess.CalledProcessError, OSError):
         return ""
-    
+

 def detect_nvidia_gpu() -> bool:

813-847: Add newline at end of file and expand docstring.

The battery impact estimation logic is sound and follows the heuristic approach described in the PR objectives. However:

  1. Critical: Line 847 is missing a trailing newline, causing linter failures (Ruff W292). This issue was previously flagged and remains unresolved.

  2. Essential: Per coding guidelines and previous review feedback, the docstring should document the return value structure, estimation methodology, and caveats.

🔎 Proposed fixes

Fix missing newline:

     return {
         "mode": mode,
         "current": current,
         "estimates": estimates,
     }
+

Expand docstring:

 def estimate_gpu_battery_impact() -> dict[str, Any]:
     """
-    Heuristic battery impact estimation based on GPU mode.
-    No realtime measurement, safe for non-root usage.
+    Estimate battery impact of GPU usage based on detected GPU mode.
+
+    Provides heuristic estimates of power draw and battery life impact for
+    different GPU configurations. Does not perform real-time power measurement
+    and is safe to call without root privileges.
+
+    Returns:
+        dict[str, Any]: Dictionary containing:
+            - mode (str): Detected GPU mode ("Integrated", "NVIDIA", or "Hybrid")
+            - current (str): Current power profile ("integrated", "nvidia_active", or "hybrid_idle")
+            - estimates (dict): Power and impact estimates for each profile, with keys:
+                - "integrated": {"power": str, "impact": str}
+                - "hybrid_idle": {"power": str, "impact": str}
+                - "nvidia_active": {"power": str, "impact": str}
+
+    Note:
+        Estimates are heuristic approximations. Actual power draw varies
+        significantly based on hardware, workload, and system configuration.
     """

As per coding guidelines, docstrings are required for all public APIs.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 098a7de and 02f7cf1.

📒 Files selected for processing (3)
  • cortex/cli.py
  • cortex/hardware_detection.py
  • tests/test_hybrid_gpu_manager.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_hybrid_gpu_manager.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/hardware_detection.py
  • cortex/cli.py
🧬 Code graph analysis (1)
cortex/cli.py (2)
cortex/hardware_detection.py (1)
  • estimate_gpu_battery_impact (813-847)
cortex/branding.py (1)
  • cx_print (49-69)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test (Python 3.10)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
🔇 Additional comments (3)
cortex/cli.py (2)

21-21: LGTM!

The import is properly formatted and correctly used by the gpu_battery() method.


1669-1673: LGTM!

The subparser registration follows the established pattern and provides clear help text.

cortex/hardware_detection.py (1)

780-797: LGTM!

Excellent implementation with comprehensive docstring, proper type hints, and sound detection logic. The use of nvidia-smi for detection is appropriate and the error handling via _run() is robust.

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

Fix all issues with AI Agents 🤖
In @cortex/cli.py:
- Around line 1951-1954: Remove the extra blank line after the "return
cli.gpu_battery()" branch to satisfy Black's formatting rules: locate the elif
block handling args.command == "gpu-battery" (the line with "return
cli.gpu_battery()") and ensure there is only a single blank line following that
statement (no consecutive blank lines).

In @cortex/hardware_detection.py:
- Around line 780-801: The docstring for detect_nvidia_gpu() is incorrect: it
describes string mode values but the function returns a bool via return
bool(_run(["nvidia-smi"])). Update the docstring to document that
detect_nvidia_gpu() returns a boolean (True if an NVIDIA GPU is detectable via
nvidia-smi, False otherwise), describe the detection method
(_run(["nvidia-smi"]) invocation) and remove or replace the
"Integrated"/"NVIDIA"/"Hybrid" return value text; alternatively, if you intended
string modes, change the function implementation to call detect_gpu_mode() or
implement mode detection—ensure detect_nvidia_gpu and its return type and
docstring are consistent.
♻️ Duplicate comments (2)
cortex/cli.py (1)

935-958: Enhance docstring and add dictionary validation.

The past review comment on this method remains valid:

  1. Essential: The docstring should document the return value (0 on success), any exceptions that may propagate, and provide more detail about what battery impact estimation entails, per coding guidelines.

  2. Recommended: Direct access to nested dictionary keys (e.g., data['estimates']['integrated']['power']) assumes a specific structure. While the main() exception handler provides a safety net, consider adding validation or a try/except block to handle unexpected dictionary structures more gracefully.

As per coding guidelines, docstrings are required for all public APIs.

cortex/hardware_detection.py (1)

804-814: Expand docstring to document return values.

The past review comment on this function remains valid: The docstring should document the possible return values ("Integrated", "NVIDIA", "Hybrid") and their meanings per coding guidelines.

As per coding guidelines, docstrings are required for all public APIs.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02f7cf1 and 0dd65ea.

📒 Files selected for processing (2)
  • cortex/cli.py
  • cortex/hardware_detection.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/cli.py
  • cortex/hardware_detection.py
🧬 Code graph analysis (1)
cortex/cli.py (2)
cortex/hardware_detection.py (1)
  • estimate_gpu_battery_impact (817-868)
cortex/branding.py (1)
  • cx_print (49-69)
🪛 GitHub Actions: CI
cortex/cli.py

[error] 1-1: Black formatting check failed. 1 file would be reformatted, 124 files would be left unchanged. Run 'black --write' to format the code.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.12)
🔇 Additional comments (4)
cortex/cli.py (2)

21-21: LGTM!

The import is correctly placed and follows the existing import organization pattern.


1669-1673: LGTM!

The subparser definition follows the existing pattern and provides clear help text.

cortex/hardware_detection.py (2)

757-777: LGTM!

The helper function is well-implemented with proper error handling, comprehensive docstring, and appropriate type hints. The empty string fallback on failure is a safe default for the detection use cases.


817-868: LGTM!

The function is well-implemented with a comprehensive docstring that documents the return value structure, estimation methodology, and appropriate caveats. The heuristic power estimates are reasonable and the logic is clear.

@tuanwannafly
Copy link
Contributor Author

Hi @Anshgrover23 , I’ve addressed all CodeRabbit bot comments and pushed the final fixes.
There are no remaining bot comments after the last push and CI is now clean.

@tuanwannafly
Copy link
Contributor Author

@Anshgrover23 hey man still looking for your review

Copy link
Collaborator

@Anshgrover23 Anshgrover23 left a comment

Choose a reason for hiding this comment

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

@tuanwannafly Documentation is missing. Also your PR covers battery impact estimation and basic GPU detection, but per-app GPU assignment and actual GPU mode switching are still missing, so it doesn’t fully address the Hybrid GPU Manager scope yet.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Need to add more tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks
I'll update soon

@github-actions
Copy link

github-actions bot commented Jan 7, 2026

CLA Verification Passed

All contributors have signed the CLA.

Contributor Signed As
@tuanwannafly @tuanwannafly
@Anshgrover23 @Anshgrover23

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

🤖 Fix all issues with AI agents
In @cortex/hardware_detection.py:
- Around line 817-864: The docstring for estimate_gpu_battery_impact is
incorrect (it describes a boolean NVIDIA detection and return type) — update it
to accurately describe what estimate_gpu_battery_impact does, its return type,
and the dictionary shape it returns: mention it calls detect_gpu_mode() and
detect_nvidia_gpu(), returns dict[str, Any] with keys "mode" (e.g.,
"Integrated"/other), "current" (one of "integrated", "hybrid_idle",
"nvidia_active"), and "estimates" (mapping of those keys to power/impact
strings); also fix the Returns section to show a dict with those keys and adjust
the Note/description accordingly.
🧹 Nitpick comments (1)
cortex/hardware_detection.py (1)

804-814: Expand docstring to document return values.

The implementation is correct. Per coding guidelines, public API docstrings should document return values.

🔎 Proposed docstring enhancement
 def detect_gpu_mode() -> str:
     """
-    Best-effort GPU mode detection
+    Detect the current GPU mode on hybrid graphics systems.
+
+    Returns:
+        str: One of:
+            - "Integrated": lspci unavailable or no GPU devices detected
+            - "NVIDIA": NVIDIA discrete GPU detected via nvidia-smi
+            - "Hybrid": GPU devices present but NVIDIA not active
     """
     if not _run(["lspci"]):
         return "Integrated"
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0dd65ea and 464df80.

📒 Files selected for processing (2)
  • cortex/cli.py
  • cortex/hardware_detection.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/cli.py
  • cortex/hardware_detection.py
🧬 Code graph analysis (1)
cortex/cli.py (2)
cortex/hardware_detection.py (1)
  • estimate_gpu_battery_impact (817-864)
cortex/branding.py (1)
  • cx_print (49-69)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build Package
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.12)
🔇 Additional comments (4)
cortex/cli.py (3)

21-21: LGTM!

Import is correctly placed alongside other cortex.hardware_detection module imports.


1770-1774: LGTM!

Subparser registration follows the established pattern for simple commands without additional arguments.


2061-2063: LGTM!

Command dispatch correctly routes to the new gpu_battery() method, following the established pattern.

cortex/hardware_detection.py (1)

757-777: LGTM!

Well-implemented helper with proper error handling. The function gracefully returns an empty string on failure, allowing callers to use truthiness checks.

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: 4

🤖 Fix all issues with AI agents
In @cortex/cli.py:
- Around line 1174-1178: Wrap the call to set_app_gpu_preference in a try/except
that catches ValueError, print a user-friendly error via cx_print including the
exception message, and return a non-zero exit code (e.g., return 1) so the CLI
fails gracefully; apply this handling where args.gpu_command == "app" and
args.app_action == "set" around the set_app_gpu_preference(args.app, args.mode)
invocation.
- Around line 1143-1148: The interactive prompt uses input() without catching
EOFError or KeyboardInterrupt, so when stdin is closed or the user hits Ctrl+C
the command will crash; wrap the input() call in a try/except that catches
EOFError and KeyboardInterrupt around the resp = input().strip().lower() logic
(in the same block that checks getattr(args, "yes", False)), treat these
exceptions as a non-affirmative response (e.g., set resp = "" or return 0) and
preserve the existing flow that aborts when resp is not "y"/"yes"; update the
code paths referenced (args, console.print, resp) accordingly to ensure a clean
exit instead of an uncaught exception.

In @cortex/hardware_detection.py:
- Around line 824-845: The docstring for detect_nvidia_gpu is incorrect: it
describes returning "Integrated"/"NVIDIA"/"Hybrid" strings but the function
actually returns a bool; update the docstring to accurately state that
detect_nvidia_gpu() performs a best-effort NVIDIA presence check (using
_run(["nvidia-smi"])) and returns True if an NVIDIA GPU was detected (i.e.,
nvidia-smi succeeded) and False otherwise, and remove or replace the paragraph
that lists string return values so the Return section and Notes match the bool
behavior.

In @docs/HYBRID_GPU_MANAGER.md:
- Around line 281-380: Add a starting fenced code block (```bash) before the
script that begins with set -euo pipefail so the entire shell snippet is
rendered as code, and remove the unnecessary backslashes escaping underscores in
the variable names (replace \_\_NV_PRIME_RENDER_OFFLOAD and
\_\_GLX_VENDOR_LIBRARY_NAME with __NV_PRIME_RENDER_OFFLOAD and
__GLX_VENDOR_LIBRARY_NAME) in the cortex gpu run echo lines and any other
occurrences so the bash variables render correctly.
🧹 Nitpick comments (5)
cortex/gpu_manager.py (3)

116-120: Consider cleanup of temp file on failure.

If os.replace fails (e.g., permission error), the temp file remains orphaned. This is a minor edge case but could leave stale files in the config directory.

♻️ Suggested improvement
     path = _config_path()
-    with tempfile.NamedTemporaryFile("w", delete=False, dir=str(cfg_dir), encoding="utf-8") as tf:
-        tf.write(serialized)
-        tmp = Path(tf.name)
-    os.replace(tmp, path)
+    tmp: Path | None = None
+    try:
+        with tempfile.NamedTemporaryFile("w", delete=False, dir=str(cfg_dir), encoding="utf-8") as tf:
+            tf.write(serialized)
+            tmp = Path(tf.name)
+        os.replace(tmp, path)
+    except OSError:
+        if tmp and tmp.exists():
+            tmp.unlink(missing_ok=True)
+        raise

282-298: Consider adding a timeout to prevent indefinite hangs.

subprocess.run without a timeout could block indefinitely if the command hangs (e.g., waiting for user input from sudo without a TTY).

♻️ Suggested improvement
-    completed = subprocess.run(cmd, env=env)
+    completed = subprocess.run(cmd, env=env, timeout=60)

You may want to catch subprocess.TimeoutExpired and return a non-zero code or raise.


320-325: Add docstring for public function.

Per coding guidelines, docstrings are required for all public APIs. This function lacks documentation explaining its purpose and the expected mode parameter values.

📝 Suggested improvement
 def get_gpu_profile(mode: str) -> dict[str, bool]:
+    """Return a profile dict indicating which GPU mode is active.
+
+    Args:
+        mode: One of "Integrated", "NVIDIA", or "Hybrid".
+
+    Returns:
+        Dict with keys 'use_integrated', 'use_nvidia', 'hybrid' set to True/False.
+    """
     return {
         "use_integrated": mode == "Integrated",
         "use_nvidia": mode == "NVIDIA",
         "hybrid": mode == "Hybrid",
     }
cortex/hardware_detection.py (2)

759-778: Consider adding a timeout to prevent hangs.

The _run helper doesn't specify a timeout, which could cause the process to hang indefinitely if the command blocks.

♻️ Suggested improvement
     try:
         result = subprocess.run(
             cmd,
             stdout=subprocess.PIPE,
             stderr=subprocess.DEVNULL,
             text=True,
             check=False,
+            timeout=5,
         )
     except OSError:
         return ""
+    except subprocess.TimeoutExpired:
+        return ""

881-882: Minor inefficiency: detect_nvidia_gpu() called twice.

detect_nvidia_gpu() is called both inside detect_gpu_mode() (line 855) and directly on line 882. This results in two nvidia-smi invocations. Consider refactoring to avoid the redundant call.

♻️ Suggested optimization
-    mode = detect_gpu_mode()
-    nvidia_active = detect_nvidia_gpu()
+    nvidia_active = detect_nvidia_gpu()
+    if not _run(["lspci"]):
+        mode = "Integrated"
+    elif nvidia_active:
+        mode = "NVIDIA"
+    else:
+        mode = "Hybrid"

Alternatively, refactor detect_gpu_mode to accept or return both values.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e3e74b3 and 8bc8044.

📒 Files selected for processing (6)
  • cortex/cli.py
  • cortex/gpu_manager.py
  • cortex/hardware_detection.py
  • docs/HYBRID_GPU_MANAGER.md
  • tests/test_gpu_manager.py
  • tests/test_hybrid_gpu_manager.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • tests/test_gpu_manager.py
  • tests/test_hybrid_gpu_manager.py
  • cortex/hardware_detection.py
  • cortex/cli.py
  • cortex/gpu_manager.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Maintain >80% test coverage for pull requests

Files:

  • tests/test_gpu_manager.py
  • tests/test_hybrid_gpu_manager.py
🧬 Code graph analysis (2)
tests/test_gpu_manager.py (1)
cortex/gpu_manager.py (9)
  • detect_gpu_switch_backend (220-231)
  • GPUSwitchBackend (31-34)
  • plan_gpu_mode_switch (234-279)
  • run_command_with_env (282-298)
  • set_app_gpu_preference (134-150)
  • get_app_gpu_preference (170-180)
  • list_app_gpu_preferences (183-190)
  • get_per_app_gpu_env (193-217)
  • remove_app_gpu_preference (153-167)
tests/test_hybrid_gpu_manager.py (1)
cortex/hardware_detection.py (3)
  • detect_gpu_mode (848-858)
  • detect_nvidia_gpu (824-845)
  • estimate_gpu_battery_impact (861-939)
🪛 markdownlint-cli2 (0.18.1)
docs/HYBRID_GPU_MANAGER.md

354-354: No space after hash on atx style heading

(MD018, no-missing-space-atx)


361-361: No space after hash on atx style heading

(MD018, no-missing-space-atx)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
🔇 Additional comments (16)
cortex/gpu_manager.py (5)

1-28: LGTM!

Module docstring, imports, and PRIME_OFFLOAD_ENV constant are well-structured and appropriately typed.


31-51: LGTM!

Enums and dataclass are well-designed. Using str inheritance for enums enables clean serialization, and the frozen dataclass ensures immutability of switch plans.


134-191: LGTM!

Per-app preference APIs have proper input validation, docstrings, and type hints. The config read-modify-write cycle is correctly implemented.


193-218: LGTM!

The function correctly handles both explicit use_nvidia and app-based lookup, and returns a copy to prevent mutation of the module constant.


220-280: LGTM!

Backend detection and plan construction are well-implemented with proper validation, docstrings, and correct command mappings for both prime-select and system76-power.

cortex/cli.py (4)

22-34: LGTM!

Imports are well-organized and all are used by the new GPU management methods.


1028-1116: LGTM!

The gpu_battery method handles both heuristic and measured data gracefully with appropriate fallbacks. The defensive float conversions and distinct return codes (0 success, 2 no data) are good practices.


2312-2354: LGTM!

CLI subparser structure is well-organized with appropriate choices, flags, and required subcommands. The use of argparse.REMAINDER for the run command correctly captures arbitrary command arguments.


2756-2759: LGTM!

Command routing for gpu-battery and gpu is correctly wired to the respective CLI methods.

docs/HYBRID_GPU_MANAGER.md (1)

1-80: LGTM!

The documentation provides clear overview, feature table, and quick start examples that align with the implemented CLI commands and API.

tests/test_hybrid_gpu_manager.py (1)

1-82: LGTM!

Tests provide good coverage for GPU mode detection and battery impact estimation. The monkeypatched scenarios effectively test the three code paths (Integrated, NVIDIA, Hybrid) without requiring actual hardware.

tests/test_gpu_manager.py (1)

1-115: LGTM!

Comprehensive test coverage for GPU manager functionality including backend detection, plan generation, command execution, and per-app configuration. Good use of monkeypatching and tmp_path fixture for isolation.

cortex/hardware_detection.py (4)

781-821: LGTM!

Robust implementation that handles multiple GPUs, non-zero exit codes, N/A values, and locale-specific decimal separators. Good defensive parsing.


848-858: LGTM!

Simple and effective GPU mode detection logic with clear fallback behavior.


942-999: LGTM!

Battery metrics implementation correctly handles the various sysfs file formats (power_now vs current_now+voltage_now) with proper unit conversions from micro-units to Watts.


1002-1054: LGTM!

Windows/WSL battery metrics implementation handles both environments correctly with appropriate timeouts and graceful fallbacks.

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

🤖 Fix all issues with AI agents
In @cortex/cli.py:
- Line 1118: The gpu method lacks a type annotation for its args parameter;
update the signature of gpu to include an explicit type (e.g., change def
gpu(self, args) -> int to def gpu(self, args: argparse.Namespace) -> int) and
add or ensure import argparse is present at the top of the file; if this project
prefers a different type (e.g., click.Context or a specific Args dataclass),
annotate args with that concrete type instead and import the corresponding
symbol.
- Around line 1161-1177: The GPU flag handling for args.gpu_command == "run"
allows conflicting flags (--nvidia, --integrated, --app); enforce mutual
exclusion either at parse time by using argparse.add_mutually_exclusive_group()
when defining the --nvidia, --integrated and --app arguments, or add a runtime
guard before calling get_per_app_gpu_env that counts how many of getattr(args,
"nvidia", False), getattr(args, "integrated", False), and getattr(args, "app",
None) are set and returns an error (cx_print + return 2) if more than one is
present, otherwise proceed to call get_per_app_gpu_env with the single selected
flag.

In @cortex/hardware_detection.py:
- Around line 839-849: The detect_gpu_mode docstring is too terse; update the
docstring of detect_gpu_mode() to document each return value ("NVIDIA",
"Hybrid", "Integrated") and explain what each means (e.g., "NVIDIA": NVIDIA GPU
detected; "Hybrid": non‑NVIDIA PCI GPU found via lspci, e.g., AMD dGPU + iGPU;
"Integrated": no discrete GPU detected or lspci unavailable) and add a note
about the NVIDIA‑centric/Optimus assumption and limitation that non‑NVIDIA
discrete GPUs may be misclassified as "Hybrid".
🧹 Nitpick comments (1)
cortex/cli.py (1)

1047-1051: Clarify the API contract for NVIDIA power metrics.

Three different keys are checked for NVIDIA power draw (nvidia_power_w, nvidia_power_watts, nvidia_power). This suggests API instability or unclear data contracts from estimate_gpu_battery_impact().

Consider standardizing on a single key name in the hardware detection module to avoid this defensive fallback pattern.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d462d2a and bda199e.

📒 Files selected for processing (2)
  • cortex/cli.py
  • cortex/hardware_detection.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/hardware_detection.py
  • cortex/cli.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test (Python 3.11)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
🔇 Additional comments (11)
cortex/cli.py (4)

22-33: LGTM! Clean GPU manager imports.

All imported functions are used in the new gpu_battery and gpu methods. The multi-line import style follows PEP 8.


1103-1109: Verify exit code 2 convention for missing measurements.

When --measured-only is specified but no real measurements are available, the method returns exit code 2. Confirm this is the intended convention for "data unavailable" scenarios.

Typical exit code conventions are:

  • 0: success
  • 1: general error
  • 2+: specific error conditions

1028-1038: Potential AttributeError if args is None.

Line 1028 allows args to be None, but line 1038 calls getattr(args, "measured_only", False) without checking. If this method is ever called with args=None, it will raise AttributeError.

🔒 Recommended fix
 def gpu_battery(self, args: argparse.Namespace | None = None) -> int:
     """
     Estimate battery impact based on current GPU usage and mode.

     Prints:
     - Heuristic estimates (unless --measured-only)
     - Measured battery discharge (if available)
     - Measured NVIDIA GPU power draw (when supported by nvidia-smi)
     """
     data = estimate_gpu_battery_impact()
-    measured_only = bool(getattr(args, "measured_only", False))
+    measured_only = bool(getattr(args, "measured_only", False) if args else False)

Likely an incorrect or invalid review comment.


2337-2367: No issues found. The GPU mode choices in the argument parser correctly align with the modes supported by the backend functions.

cortex/hardware_detection.py (7)

10-10: LGTM! Future annotations import.

Adding from __future__ import annotations enables postponed evaluation of type hints, allowing cleaner syntax like str | None instead of Optional[str]. This is good practice for Python 3.9+ compatibility.


759-778: LGTM! Defensive command runner.

The _run helper properly handles missing commands and non-zero exit codes. The docstring clearly explains the check=False rationale.

Note: Line 771 suppresses stderr (stderr=subprocess.DEVNULL), which is appropriate for this heuristic detection use case but means diagnostic information won't be visible if commands fail.


781-818: LGTM! Robust NVIDIA power draw detection.

Excellent defensive coding:

  • Handles missing nvidia-smi (line 799)
  • Handles "N/A" values (line 809)
  • Supports comma decimal separators for European locales (line 812)
  • Sums power across multiple GPUs (line 818)
  • Returns None when unavailable

821-836: LGTM! NVIDIA detection helper.

Simple and effective detection using nvidia-smi. The docstring appropriately notes the heuristic nature and limitations.


852-930: LGTM! Comprehensive battery impact estimation.

The function provides both heuristic estimates and optional real measurements with good error handling. The docstring clearly explains the capabilities and limitations.

The hard-coded string estimates (lines 875-888) like "~6–8 W" are human-readable but not machine-parseable. This is acceptable for the current implementation, but consider adding numeric fields in future versions if programmatic access to estimates is needed.


933-990: LGTM! Robust Linux battery metrics.

Excellent implementation:

  • Safe file reading with None fallback (lines 948-963)
  • Correct power calculation from current × voltage when power_now unavailable (lines 969-980)
  • Proper unit conversions (µW → W, µA × µV → W)
  • Returns None when battery unavailable rather than raising errors

993-1046: LGTM! Windows/WSL battery metrics support.

Great cross-platform support:

  • Detects Windows native vs WSL (line 1004)
  • Uses modern Get-CimInstance instead of deprecated WMI
  • 2-second timeout prevents hangs (line 1014)
  • Graceful degradation on any error (returns None)

This enables the battery impact feature to work on WSL, which aligns with the PR's stated testing environment.

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: 4

🤖 Fix all issues with AI agents
In @cortex/cli.py:
- Around line 1118-1207: The public method CortexCLI.gpu() lacks a docstring and
a type annotation for its args parameter; add a concise docstring describing
purpose, accepted subcommands (status/set/run/app), parameters (args:
argparse.Namespace or a more specific CLI args dataclass), and return value (int
exit code), and add a type hint to the signature (e.g., args:
argparse.Namespace) so the declaration becomes def gpu(self, args:
argparse.Namespace) -> int; ensure imports include argparse.Namespace (or the
specific type) and keep the docstring in Google/NumPy style consistent with the
project's guidelines.

In @cortex/hardware_detection.py:
- Around line 759-779: The _run() helper can hang because subprocess.run has no
timeout; update _run to pass a reasonable timeout (e.g., 5–10s) into
subprocess.run and catch subprocess.TimeoutExpired to return "" (same behavior
as OSError), and then update any direct subprocess.run calls in
get_nvidia_power_draw_watts() (and similar calls around lines 781-819) to either
call the updated _run() or include the same timeout and TimeoutExpired handling
so CLI hangs are prevented.
- Around line 842-847: The docstring for detect_gpu_mode contains trailing
whitespace on otherwise blank lines causing Ruff W293; edit the detect_gpu_mode
docstring to remove any spaces on the empty lines (ensure blank lines between
the Returns: description lines are truly empty with no spaces or tabs) so there
is no trailing whitespace on those lines.
🧹 Nitpick comments (2)
cortex/hardware_detection.py (1)

861-905: estimate_gpu_battery_impact() uses “tooling present” as “NVIDIA active”.

nvidia_active = detect_nvidia_gpu() is just “nvidia-smi produced stdout”, not “dGPU is currently driving rendering / drawing power”. This can incorrectly label current as nvidia_active on hybrid laptops where NVIDIA is idle but drivers are installed.

Suggested direction
-    nvidia_active = detect_nvidia_gpu()
+    # "nvidia-smi available" != "NVIDIA active".
+    # If we can measure power, use it as a proxy for activity; otherwise keep it heuristic.
+    nvidia_power_w = get_nvidia_power_draw_watts()
+    nvidia_active = bool(nvidia_power_w is not None and nvidia_power_w > 3.0)
@@
-    if mode == "Integrated":
+    if mode == "Integrated":
         current = "integrated"
-    elif nvidia_active:
+    elif nvidia_active:
         current = "nvidia_active"
     else:
         current = "hybrid_idle"
cortex/cli.py (1)

2342-2347: --dry-run on cortex gpu set is currently redundant / unused.

The command defaults to printing the plan unless --execute is set, so --dry-run doesn’t change behavior. Either remove it, or use it to explicitly block execution (e.g., error if both --dry-run and --execute are set).

Also applies to: 1129-1160

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bda199e and 9260f0b.

📒 Files selected for processing (2)
  • cortex/cli.py
  • cortex/hardware_detection.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/cli.py
  • cortex/hardware_detection.py
🧬 Code graph analysis (1)
cortex/cli.py (2)
cortex/gpu_manager.py (9)
  • apply_gpu_mode_switch (301-317)
  • detect_gpu_switch_backend (220-231)
  • get_app_gpu_preference (170-180)
  • get_per_app_gpu_env (193-217)
  • list_app_gpu_preferences (183-190)
  • plan_gpu_mode_switch (234-279)
  • remove_app_gpu_preference (153-167)
  • run_command_with_env (282-298)
  • set_app_gpu_preference (134-150)
cortex/hardware_detection.py (2)
  • detect_gpu_mode (839-858)
  • estimate_gpu_battery_impact (861-939)
🪛 GitHub Actions: CI
cortex/hardware_detection.py

[error] 842-842: ruff: W293 Blank line contains whitespace.

🪛 GitHub Check: lint
cortex/hardware_detection.py

[failure] 847-847: Ruff (W293)
cortex/hardware_detection.py:847:1: W293 Blank line contains whitespace


[failure] 842-842: Ruff (W293)
cortex/hardware_detection.py:842:1: W293 Blank line contains whitespace

🪛 GitHub Check: Lint
cortex/hardware_detection.py

[failure] 847-847: Ruff (W293)
cortex/hardware_detection.py:847:1: W293 Blank line contains whitespace


[failure] 842-842: Ruff (W293)
cortex/hardware_detection.py:842:1: W293 Blank line contains whitespace

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
🔇 Additional comments (2)
cortex/cli.py (2)

22-33: GPU imports look consistent with the new CLI surface.

No issues with the import wiring for the new gpu / gpu-battery commands.


2337-2340: No action required. The repository declares a minimum Python version of 3.10 (requires-python = ">=3.10" in pyproject.toml and python_requires=">=3.10" in setup.py), which exceeds the Python 3.7+ requirement for required=True in add_subparsers(). The code is fully compatible with the declared minimum supported version.

Comment on lines +759 to +779
def _run(cmd: list[str]) -> str:
"""
Run a system command and return stdout.
Notes:
- Uses check=False so we can still parse stdout even when returncode != 0.
(Some driver/tool combinations emit useful output but exit non-zero.)
"""
try:
result = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
check=False,
)
except OSError:
return ""

return (result.stdout or "").strip()

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add timeouts to _run() / nvidia-smi calls to avoid rare CLI hangs.

Right now _run() and get_nvidia_power_draw_watts() can block indefinitely if a command stalls (driver/tooling edge-cases). This is especially painful for CLI commands.

Proposed fix
 def _run(cmd: list[str]) -> str:
@@
     try:
         result = subprocess.run(
             cmd,
             stdout=subprocess.PIPE,
             stderr=subprocess.DEVNULL,
             text=True,
             check=False,
+            timeout=2,
         )
-    except OSError:
+    except (OSError, subprocess.TimeoutExpired):
         return ""
@@
 def get_nvidia_power_draw_watts() -> float | None:
@@
     try:
         proc = subprocess.run(
             ["nvidia-smi", "--query-gpu=power.draw", "--format=csv,noheader,nounits"],
             stdout=subprocess.PIPE,
             stderr=subprocess.DEVNULL,
             text=True,
             check=False,
+            timeout=2,
         )
-    except OSError:
+    except (OSError, subprocess.TimeoutExpired):
         return None

Also applies to: 781-819

🤖 Prompt for AI Agents
In @cortex/hardware_detection.py around lines 759 - 779, The _run() helper can
hang because subprocess.run has no timeout; update _run to pass a reasonable
timeout (e.g., 5–10s) into subprocess.run and catch subprocess.TimeoutExpired to
return "" (same behavior as OSError), and then update any direct subprocess.run
calls in get_nvidia_power_draw_watts() (and similar calls around lines 781-819)
to either call the updated _run() or include the same timeout and TimeoutExpired
handling so CLI hangs are prevented.

Comment on lines 839 to 858
def detect_gpu_mode() -> str:
"""
Detect GPU mode for NVIDIA Optimus hybrid systems.
Returns:
"NVIDIA": NVIDIA GPU detected (nvidia-smi available)
"Hybrid": Non-NVIDIA GPU detected via lspci (e.g., AMD dGPU with iGPU)
"Integrated": No discrete GPU detected or lspci unavailable
Note: This detection is NVIDIA-centric and may not accurately classify
non-NVIDIA discrete GPUs. Returns "Hybrid" for any PCI GPU when NVIDIA
is not present.
"""
if not _run(["lspci"]):
return "Integrated"

if detect_nvidia_gpu():
return "NVIDIA"

return "Hybrid"
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

detect_gpu_mode() misclassifies Intel-only systems as “Hybrid”.

On any normal Linux machine, lspci prints output, so this returns "Hybrid" whenever nvidia-smi is absent—even if there’s only an integrated GPU. That makes cortex gpu status / gpu-battery misleading.

Proposed fix (still “best-effort”, but avoids the Intel-only false positive)
 def detect_gpu_mode() -> str:
@@
-    if not _run(["lspci"]):
+    lspci_out = _run(["lspci", "-nn"])
+    if not lspci_out:
         return "Integrated"
 
     if detect_nvidia_gpu():
         return "NVIDIA"
 
-    return "Hybrid"
+    # Heuristic: consider it "Hybrid" only if lspci indicates multiple GPUs
+    # or a likely discrete vendor (AMD/ATI/NVIDIA) alongside an iGPU.
+    gpu_lines = [
+        ln for ln in lspci_out.splitlines()
+        if (" vga " in ln.lower()) or ("3d controller" in ln.lower()) or ("display controller" in ln.lower())
+    ]
+    if len(gpu_lines) >= 2:
+        return "Hybrid"
+    if gpu_lines and any(v in gpu_lines[0].upper() for v in ("AMD", "ATI", "NVIDIA")):
+        # Single GPU line but non-Intel vendor; keep "Hybrid" as a conservative bucket.
+        return "Hybrid"
+    return "Integrated"
🧰 Tools
🪛 GitHub Actions: CI

[error] 842-842: ruff: W293 Blank line contains whitespace.

🪛 GitHub Check: lint

[failure] 847-847: Ruff (W293)
cortex/hardware_detection.py:847:1: W293 Blank line contains whitespace


[failure] 842-842: Ruff (W293)
cortex/hardware_detection.py:842:1: W293 Blank line contains whitespace

🪛 GitHub Check: Lint

[failure] 847-847: Ruff (W293)
cortex/hardware_detection.py:847:1: W293 Blank line contains whitespace


[failure] 842-842: Ruff (W293)
cortex/hardware_detection.py:842:1: W293 Blank line contains whitespace

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

🤖 Fix all issues with AI agents
In @cortex/cli.py:
- Line 1118: The public method gpu is missing a type hint for its parameter and
a docstring; update the signature of gpu to annotate args (e.g., args:
argparse.Namespace) and add a concise docstring immediately under def gpu that
explains the method's purpose, documents the args parameter and return value
(int), and notes any raised exceptions or side effects; ensure imports include
argparse if not already present and follow existing docstring style in the
module.
🧹 Nitpick comments (3)
cortex/cli.py (3)

1038-1038: Optional: Remove redundant bool() call.

The getattr() with a False default already returns a boolean, making the outer bool() call unnecessary.

♻️ Simplify the expression
-        measured_only = bool(getattr(args, "measured_only", False))
+        measured_only = getattr(args, "measured_only", False)

1085-1099: Consider consolidating repetitive float conversion logic.

The method contains multiple similar try-except blocks for converting values to float. This pattern could be extracted into a helper function to reduce duplication and improve maintainability.

♻️ Proposed refactor with helper function

Add a helper function at the module or class level:

def _safe_format_float(value, precision: int = 2, fallback_unit: str = "") -> str:
    """Safely format a value as float with precision, or return as-is."""
    try:
        return f"~{float(value):.{precision}f}{fallback_unit}"
    except (TypeError, ValueError):
        return f"~{value}{fallback_unit}"

Then simplify lines 1085-1099:

                 if power_watts is not None:
-                    try:
-                        print(f"- Battery draw: ~{float(power_watts):.2f} W")
-                    except (TypeError, ValueError):
-                        print(f"- Battery draw: ~{power_watts} W")
+                    print(f"- Battery draw: {_safe_format_float(power_watts, 2, ' W')}")
                 if hours_remaining is not None:
-                    try:
-                        print(f"- Est. time remaining: ~{float(hours_remaining):.2f} h")
-                    except (TypeError, ValueError):
-                        print(f"- Est. time remaining: ~{hours_remaining} h")
+                    print(f"- Est. time remaining: {_safe_format_float(hours_remaining, 2, ' h')}")
 
             if has_nvidia_data:
-                try:
-                    print(f"- NVIDIA power draw: ~{float(nvidia_watts):.2f} W")
-                except (TypeError, ValueError):
-                    print(f"- NVIDIA power draw: ~{nvidia_watts} W")
+                print(f"- NVIDIA power draw: {_safe_format_float(nvidia_watts, 2, ' W')}")

1130-1133: Consider error handling for GPU backend functions.

The plan_gpu_mode_switch() and other imported GPU functions could raise exceptions. While plan_gpu_mode_switch returns None on failure (handled on line 1131), adding broader error handling would improve robustness.

🛡️ Consider adding try-except wrapper

Wrap the GPU operations in a try-except block to catch potential exceptions:

     def gpu(self, args: argparse.Namespace) -> int:
+        try:
             if args.gpu_command == "status":
                 backend = detect_gpu_switch_backend()
                 # ... rest of logic
+        except Exception as e:
+            cx_print(f"GPU operation failed: {e}", "error")
+            if self.verbose:
+                import traceback
+                traceback.print_exc()
+            return 1

Note: This is optional since individual subcommands already have some error handling (e.g., ValueError on line 1185).

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9260f0b and 4152c9f.

📒 Files selected for processing (2)
  • cortex/cli.py
  • cortex/hardware_detection.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • cortex/hardware_detection.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/cli.py
🧬 Code graph analysis (1)
cortex/cli.py (8)
cortex/gpu_manager.py (8)
  • apply_gpu_mode_switch (301-317)
  • detect_gpu_switch_backend (220-231)
  • get_app_gpu_preference (170-180)
  • get_per_app_gpu_env (193-217)
  • list_app_gpu_preferences (183-190)
  • plan_gpu_mode_switch (234-279)
  • remove_app_gpu_preference (153-167)
  • run_command_with_env (282-298)
cortex/hardware_detection.py (2)
  • detect_gpu_mode (839-858)
  • estimate_gpu_battery_impact (861-939)
cortex/kernel_features/llm_device.py (1)
  • getattr (81-105)
cortex/graceful_degradation.py (1)
  • get (109-141)
cortex/kernel_features/accelerator_limits.py (2)
  • get (69-72)
  • status (97-105)
cortex/branding.py (1)
  • cx_print (49-69)
cortex/kernel_features/kv_cache_manager.py (1)
  • status (130-137)
cortex/coordinator.py (1)
  • execute (230-279)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build Package
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)

tuanwannafly and others added 2 commits January 9, 2026 17:51
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@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

🤖 Fix all issues with AI agents
In @cortex/cli.py:
- Around line 1127-1128: Remove the malformed duplicated line checking
args.gpu_command (the extra "if args.gpu_command == \"status\":" with incorrect
indentation) so only a single correctly-indented conditional remains; locate the
GPU command handling block in cortex/cli.py (the if args.gpu_command == "status"
check) and delete the duplicate/incorrect line, then confirm surrounding
indentation and syntax for that conditional are correct so the module can
import.
- Around line 1118-1126: The gpu method's docstring contains trailing whitespace
on the second and fifth lines causing a PEP 8 W293 violation; edit the gpu
function docstring (def gpu(...)) to remove any trailing spaces at the ends of
lines (especially the lines starting with "Args:" and "Returns:" in that
docstring) so all lines end with no extra whitespace.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4152c9f and baec843.

📒 Files selected for processing (1)
  • cortex/cli.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/cli.py
🧬 Code graph analysis (1)
cortex/cli.py (2)
cortex/gpu_manager.py (9)
  • apply_gpu_mode_switch (301-317)
  • detect_gpu_switch_backend (220-231)
  • get_app_gpu_preference (170-180)
  • get_per_app_gpu_env (193-217)
  • list_app_gpu_preferences (183-190)
  • plan_gpu_mode_switch (234-279)
  • remove_app_gpu_preference (153-167)
  • run_command_with_env (282-298)
  • set_app_gpu_preference (134-150)
cortex/hardware_detection.py (2)
  • detect_gpu_mode (839-858)
  • estimate_gpu_battery_impact (861-939)
🪛 GitHub Actions: CI
cortex/cli.py

[error] 1120-1120: W293 Blank line contains whitespace. (ruff check)

🪛 GitHub Actions: Cortex Automation
cortex/cli.py

[error] 1127-1127: IndentationError: unexpected indent encountered while importing cortex.cli during pytest collection (line 1127).

🪛 GitHub Check: lint
cortex/cli.py

[failure] 1127-1128: Ruff (invalid-syntax)
cortex/cli.py:1127:42: invalid-syntax: Expected an indented block after if statement


[failure] 1127-1127: Ruff (invalid-syntax)
cortex/cli.py:1127:1: invalid-syntax: Unexpected indentation


[failure] 1123-1123: Ruff (W293)
cortex/cli.py:1123:1: W293 Blank line contains whitespace


[failure] 1120-1120: Ruff (W293)
cortex/cli.py:1120:1: W293 Blank line contains whitespace

🪛 GitHub Check: Lint
cortex/cli.py

[failure] 1127-1128: Ruff (invalid-syntax)
cortex/cli.py:1127:42: invalid-syntax: Expected an indented block after if statement


[failure] 1127-1127: Ruff (invalid-syntax)
cortex/cli.py:1127:1: invalid-syntax: Unexpected indentation


[failure] 1123-1123: Ruff (W293)
cortex/cli.py:1123:1: W293 Blank line contains whitespace


[failure] 1120-1120: Ruff (W293)
cortex/cli.py:1120:1: W293 Blank line contains whitespace

cortex/cli.py Outdated
Comment on lines 1127 to 1128
if args.gpu_command == "status":
if args.gpu_command == "status":
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical syntax error: Remove duplicate malformed line.

Line 1127 has incorrect indentation and duplicates line 1128, causing a syntax error that prevents the module from being imported. This blocks all tests and execution.

🐛 Fix: Remove the malformed duplicate line
-         if args.gpu_command == "status":
         if args.gpu_command == "status":
🧰 Tools
🪛 GitHub Actions: Cortex Automation

[error] 1127-1127: IndentationError: unexpected indent encountered while importing cortex.cli during pytest collection (line 1127).

🪛 GitHub Check: lint

[failure] 1127-1128: Ruff (invalid-syntax)
cortex/cli.py:1127:42: invalid-syntax: Expected an indented block after if statement


[failure] 1127-1127: Ruff (invalid-syntax)
cortex/cli.py:1127:1: invalid-syntax: Unexpected indentation

🪛 GitHub Check: Lint

[failure] 1127-1128: Ruff (invalid-syntax)
cortex/cli.py:1127:42: invalid-syntax: Expected an indented block after if statement


[failure] 1127-1127: Ruff (invalid-syntax)
cortex/cli.py:1127:1: invalid-syntax: Unexpected indentation

🤖 Prompt for AI Agents
In @cortex/cli.py around lines 1127 - 1128, Remove the malformed duplicated line
checking args.gpu_command (the extra "if args.gpu_command == \"status\":" with
incorrect indentation) so only a single correctly-indented conditional remains;
locate the GPU command handling block in cortex/cli.py (the if args.gpu_command
== "status" check) and delete the duplicate/incorrect line, then confirm
surrounding indentation and syntax for that conditional are correct so the
module can import.

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

🤖 Fix all issues with AI agents
In @cortex/cli.py:
- Around line 1138-1142: plan_gpu_mode_switch can raise ValueError for an
invalid target_mode; wrap the call to plan_gpu_mode_switch(args.mode) (the block
handling args.gpu_command == "set") in a try/except that catches ValueError and
prints a friendly error via cx_print (or returns nonzero) instead of letting the
exception propagate, keeping the existing logic that returns 2 when plan is
None.
- Around line 1210-1212: Call and capture the boolean result of
remove_app_gpu_preference(args.app) instead of ignoring it; e.g. result =
remove_app_gpu_preference(args.app) then print or log a clear user-facing
message: on True print "GPU preference removed for app '<app>'" and return 0, on
False print "No GPU preference found for app '<app>'" (or "failed to remove GPU
preference for '<app>'" if failure), and return a non-zero exit code; update the
branch handling args.app_action == "remove" accordingly.
- Around line 1047-1051: The code in the NVIDIA power compatibility block (the
nvidia_watts assignment in cortex/cli.py) checks fallback keys
"nvidia_power_watts" and "nvidia_power" that are never produced by
estimate_gpu_battery_impact() (which only sets "nvidia_power_w"); remove those
two obsolete fallback gets and simplify the logic to only read
measured.get("nvidia_power_w") (or, if you want to retain explicit
compatibility, replace them with a single documented alias check that maps
legacy keys upstream), ensuring the variable name nvidia_watts is set directly
from the actual key emitted by estimate_gpu_battery_impact().
🧹 Nitpick comments (2)
cortex/cli.py (2)

1028-1028: Remove unused None default from args parameter.

The args parameter has a None default, but the calling code at line 2782 always passes args from argparse. The None default is never used and adds unnecessary complexity.

♻️ Simplify signature
-    def gpu_battery(self, args: argparse.Namespace | None = None) -> int:
+    def gpu_battery(self, args: argparse.Namespace) -> int:

1085-1099: Reduce duplication in float conversion logic.

The try/except pattern for converting values to float is repeated three times with identical logic. Extract this into a helper function to improve maintainability.

♻️ Extract helper function

Add a helper method to the class:

def _safe_float_format(self, value: Any, precision: int = 2) -> str:
    """Format a value as float with fallback to string representation."""
    try:
        return f"~{float(value):.{precision}f} W"
    except (TypeError, ValueError):
        return f"~{value} W"

Then apply this diff to use the helper:

                 if power_watts is not None:
-                    try:
-                        print(f"- Battery draw: ~{float(power_watts):.2f} W")
-                    except (TypeError, ValueError):
-                        print(f"- Battery draw: ~{power_watts} W")
+                    print(f"- Battery draw: {self._safe_float_format(power_watts)}")
                 if hours_remaining is not None:
-                    try:
-                        print(f"- Est. time remaining: ~{float(hours_remaining):.2f} h")
-                    except (TypeError, ValueError):
-                        print(f"- Est. time remaining: ~{hours_remaining} h")
+                    # Note: hours use different unit, so handle separately
+                    try:
+                        print(f"- Est. time remaining: ~{float(hours_remaining):.2f} h")
+                    except (TypeError, ValueError):
+                        print(f"- Est. time remaining: ~{hours_remaining} h")
 
             if has_nvidia_data:
-                try:
-                    print(f"- NVIDIA power draw: ~{float(nvidia_watts):.2f} W")
-                except (TypeError, ValueError):
-                    print(f"- NVIDIA power draw: ~{nvidia_watts} W")
+                print(f"- NVIDIA power draw: {self._safe_float_format(nvidia_watts)}")

Or even simpler: create a local helper that adapts for both watts and hours.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between baec843 and 4d32157.

📒 Files selected for processing (1)
  • cortex/cli.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/cli.py
🧬 Code graph analysis (1)
cortex/cli.py (3)
cortex/gpu_manager.py (9)
  • apply_gpu_mode_switch (301-317)
  • detect_gpu_switch_backend (220-231)
  • get_app_gpu_preference (170-180)
  • get_per_app_gpu_env (193-217)
  • list_app_gpu_preferences (183-190)
  • plan_gpu_mode_switch (234-279)
  • remove_app_gpu_preference (153-167)
  • run_command_with_env (282-298)
  • set_app_gpu_preference (134-150)
cortex/hardware_detection.py (2)
  • detect_gpu_mode (839-858)
  • estimate_gpu_battery_impact (861-939)
cortex/branding.py (1)
  • cx_print (49-69)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Test (Python 3.11)
  • GitHub Check: Test (Python 3.10)
  • GitHub Check: Test (Python 3.12)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
🔇 Additional comments (3)
cortex/cli.py (3)

22-33: LGTM: GPU module imports are well-organized.

The imports from cortex.gpu_manager and cortex.hardware_detection are clean, necessary, and follow the existing import patterns in the file.


2334-2379: LGTM: GPU CLI argument parsing is well-structured.

The argument parsing for both gpu-battery and gpu commands is properly organized:

  • Clear subcommand hierarchy
  • Appropriate use of mutually exclusive groups for gpu run
  • Mode validation via choices prevents invalid input
  • argparse.REMAINDER correctly captures arbitrary command arguments for gpu run

2781-2784: LGTM: Command routing is correct.

The routing for gpu-battery and gpu commands follows the established pattern in main() and correctly passes the parsed arguments to the respective handler methods.

@tuanwannafly
Copy link
Contributor Author

@Anshgrover23 review my pr, thanks

Copy link
Collaborator

@Anshgrover23 Anshgrover23 left a comment

Choose a reason for hiding this comment

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

@tuanwannafly Do not resolve the coderabbitai comments, address them fully, then ask for review.

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

🤖 Fix all issues with AI agents
In @cortex/cli.py:
- Around line 1114-1170: The gpu set flow currently ignores a --dry-run flag and
allows --dry-run and --execute together; update the argparse setup to make
--dry-run and --execute mutually exclusive (or remove --dry-run) and change the
gpu(self, args) logic in the gpu method to honor args.dry_run explicitly: when
args.dry_run is True, do not prompt or call apply_gpu_mode_switch(execute=True)
and always treat as dry-run (show plan only); when args.execute is True, require
confirmation (unless args.yes) and then call apply_gpu_mode_switch(plan,
execute=True); also add an explicit error/return when both flags are present if
you cannot change argparse, and update the user tip text to reflect the
corrected flags.
- Around line 1028-1113: Wrap the call to estimate_gpu_battery_impact() inside
gpu_battery with a try/except that catches expected probing errors (e.g.,
OSError, PermissionError, subprocess.CalledProcessError, ValueError,
RuntimeError and a broad Exception fallback), and on error print a concise
user-facing message via cx_print (e.g., "Failed to probe battery/GPU info:
<error>") and return a non-zero exit code (e.g., 2); keep the rest of
gpu_battery logic unchanged so measured_only handling and printing still work
when probing succeeds.
🧹 Nitpick comments (3)
cortex/cli.py (3)

22-33: Consider lazy-importing GPU modules to avoid slowing CLI startup for non-GPU commands.
Right now cortex.gpu_manager and especially cortex.hardware_detection get imported on every cortex invocation, even when running unrelated commands. Moving these imports into gpu_battery() / gpu() keeps feature behavior the same but reduces baseline startup risk.

Proposed refactor (lazy imports)
-from cortex.gpu_manager import (
-    apply_gpu_mode_switch,
-    detect_gpu_switch_backend,
-    get_app_gpu_preference,
-    get_per_app_gpu_env,
-    list_app_gpu_preferences,
-    plan_gpu_mode_switch,
-    remove_app_gpu_preference,
-    run_command_with_env,
-    set_app_gpu_preference,
-)
-from cortex.hardware_detection import detect_gpu_mode, estimate_gpu_battery_impact
+#
+# NOTE: Consider lazy-importing GPU modules inside gpu_battery()/gpu()
+# to avoid CLI startup overhead for non-GPU commands.
+#

Then inside gpu_battery() / gpu() add local imports.


1171-1222: CLI ergonomics: gpu run should print the injected env in verbose mode (optional) + align exit codes.
The flow is solid and keeps switching “per app” without global mode changes. Two small nits:

  • Consider printing (or self._debug-logging) the env keys being injected when --verbose is enabled (helps users verify PRIME offload vars).
  • gpu app remove returns 1 when nothing removed, while other “not available” states often return 2 in this command; not wrong, but worth aligning if you want 2 to consistently mean “not applicable / missing backend / invalid usage.”

2340-2385: Argparse wiring: make gpu set --dry-run / --execute mutually exclusive; consider adding help strings + update rich help table.

  • gpu_set.add_argument("--dry-run"... should be mutually exclusive with --execute to avoid ambiguous invocation.
  • Subcommands like gpu app list have no help=... (minor, but improves UX in --help).
  • show_rich_help() prints a curated command table; consider adding gpu and gpu-battery there so they show up when users run cortex with no args.
Proposed argparse tweak (mutually exclusive dry-run/execute)
     gpu_set = gpu_sub.add_parser("set", help="Switch GPU mode")
     gpu_set.add_argument("mode", choices=["integrated", "hybrid", "nvidia"])
-    gpu_set.add_argument("--dry-run", action="store_true")
-    gpu_set.add_argument("--execute", action="store_true")
+    gpu_set_mode = gpu_set.add_mutually_exclusive_group()
+    gpu_set_mode.add_argument("--dry-run", action="store_true", help="Show commands only")
+    gpu_set_mode.add_argument("--execute", action="store_true", help="Run commands (may require sudo)")
     gpu_set.add_argument("-y", "--yes", action="store_true")
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d32157 and eff7f23.

📒 Files selected for processing (1)
  • cortex/cli.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/cli.py
🧬 Code graph analysis (1)
cortex/cli.py (2)
cortex/gpu_manager.py (9)
  • apply_gpu_mode_switch (301-317)
  • detect_gpu_switch_backend (220-231)
  • get_app_gpu_preference (170-180)
  • get_per_app_gpu_env (193-217)
  • list_app_gpu_preferences (183-190)
  • plan_gpu_mode_switch (234-279)
  • remove_app_gpu_preference (153-167)
  • run_command_with_env (282-298)
  • set_app_gpu_preference (134-150)
cortex/hardware_detection.py (2)
  • detect_gpu_mode (839-858)
  • estimate_gpu_battery_impact (861-939)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build Package
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.10)
🔇 Additional comments (1)
cortex/cli.py (1)

2787-2791: Main dispatch additions look correct.
Routes gpu-battery and gpu to the new handlers consistently with existing command routing.

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

🤖 Fix all issues with AI agents
In @cortex/cli.py:
- Around line 1235-1238: The current gpu app get branch prints "app: None",
which is unclear; update the block that calls get_app_gpu_preference(args.app)
to check if pref is None, and if so call cx_print with a friendly message like
"{args.app}: GPU preference not set" and return a distinct non-zero exit code
(e.g., return 2) so callers can detect the "not set" case; otherwise keep the
existing cx_print("{args.app}: {pref}") and return 0.
- Around line 2373-2430: show_rich_help() currently builds the top-level command
table but misses the new "gpu" and "gpu-battery" parsers so running `cortex`
with no args won't list them; update show_rich_help() to include entries for the
gpu subparser (created as gpu_parser) and gpu-battery subparser
(gpu_battery_parser) when it enumerates top-level subcommands (or when iterating
subparsers._actions), ensuring both names and short help texts are added to the
displayed table so the no-args landing help remains discoverable.
- Around line 1204-1223: The integrated-GPU path can be ineffective because
get_per_app_gpu_env(use_nvidia=False) (and the app-resolves-to-integrated case)
returns an empty dict and run_command_with_env simply merges it into os.environ,
leaving existing PRIME vars set in the parent process; fix by explicitly
unsetting those PRIME vars when launching integrated GPU runs: in the
args.gpu_command == "run" branch (the code that calls get_per_app_gpu_env and
run_command_with_env), detect the integrated case (get_per_app_gpu_env called
with use_nvidia=False or app preference resolved to integrated) and if the
returned env is empty, prepend an explicit wrapper to the command such as
["env", "-u", "__NV_PRIME_RENDER_OFFLOAD", "-u", "__GLX_VENDOR_LIBRARY_NAME",
"-u", "__VK_LAYER_NV_optimus", "--"] + cmd before calling
run_command_with_env(cmd, extra_env=env), so the child process runs with those
PRIME vars unset; apply the same logic for the --app path when the app
preference resolves to integrated.
🧹 Nitpick comments (3)
cortex/cli.py (3)

4-35: Consider lazy-importing GPU modules to avoid CLI startup overhead / platform import hazards

Right now cortex/cli.py imports GPU probing/switching modules at import time. If cortex.hardware_detection (or transitive imports) ever becomes “heavy” or platform-sensitive, this impacts every CLI invocation, even non-GPU commands. A simple pattern is to import GPU modules inside gpu_battery() / gpu() only.

Proposed refactor (lazy imports)
-import subprocess
+import subprocess

 ...
-from cortex.gpu_manager import (
-    apply_gpu_mode_switch,
-    detect_gpu_switch_backend,
-    get_app_gpu_preference,
-    get_per_app_gpu_env,
-    list_app_gpu_preferences,
-    plan_gpu_mode_switch,
-    remove_app_gpu_preference,
-    run_command_with_env,
-    set_app_gpu_preference,
-)
-from cortex.hardware_detection import detect_gpu_mode, estimate_gpu_battery_impact
+ # NOTE: Consider lazy-importing GPU modules inside `gpu()` / `gpu_battery()`
+ # to avoid import-time overhead for non-GPU commands.

And inside gpu_battery() / gpu():

from cortex.gpu_manager import ...
from cortex.hardware_detection import ...

1029-1127: gpu_battery() looks solid; consider returning structured output (or a --json mode) later

Error handling and the --measured-only UX are reasonable, and the public method has a docstring + type hints (per guidelines).


1151-1203: Use shlex.join() when printing planned commands to avoid ambiguity

" ".join(c) can mislead when args contain spaces/special chars; shlex.join(c) prints a shell-accurate representation.

Proposed change
+import shlex
...
             cx_print("Commands:")
             for c in plan.commands:
-                cx_print("  " + " ".join(c))
+                cx_print("  " + shlex.join(c))
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a9894e and d96af30.

📒 Files selected for processing (1)
  • cortex/cli.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/cli.py
🧬 Code graph analysis (1)
cortex/cli.py (2)
cortex/gpu_manager.py (9)
  • apply_gpu_mode_switch (301-317)
  • detect_gpu_switch_backend (220-231)
  • get_app_gpu_preference (170-180)
  • get_per_app_gpu_env (193-217)
  • list_app_gpu_preferences (183-190)
  • plan_gpu_mode_switch (234-279)
  • remove_app_gpu_preference (153-167)
  • run_command_with_env (282-298)
  • set_app_gpu_preference (134-150)
cortex/hardware_detection.py (1)
  • detect_gpu_mode (839-858)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build Package
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.11)
🔇 Additional comments (1)
cortex/cli.py (1)

2832-2836: Dispatch for gpu-battery / gpu is clean

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

🤖 Fix all issues with AI agents
In @cortex/cli.py:
- Around line 1040-1051: The except tuple that handles errors from calling
estimate_gpu_battery_impact incorrectly includes subprocess.CalledProcessError;
remove subprocess.CalledProcessError from that tuple (leave OSError,
PermissionError, ValueError, RuntimeError) and also remove the redundant second
generic "except Exception as e" block since estimate_gpu_battery_impact handles
subprocess errors internally—keep the single except that logs via cx_print and
returns 2.
🧹 Nitpick comments (3)
cortex/cli.py (3)

1096-1110: Consider simplifying repetitive type conversion pattern.

The try/except blocks for type conversion are defensive but repetitive. Consider extracting a helper function for safe float formatting.

♻️ Proposed refactor

Add a helper function earlier in the class:

def _safe_format_float(self, value: Any, precision: int = 2) -> str:
    """Safely format a value as a float with given precision."""
    try:
        return f"~{float(value):.{precision}f}"
    except (TypeError, ValueError):
        return f"~{value}"

Then simplify the calls:

                 if power_watts is not None:
-                    try:
-                        print(f"- Battery draw: ~{float(power_watts):.2f} W")
-                    except (TypeError, ValueError):
-                        print(f"- Battery draw: ~{power_watts} W")
+                    print(f"- Battery draw: {self._safe_format_float(power_watts)} W")
                 if hours_remaining is not None:
-                    try:
-                        print(f"- Est. time remaining: ~{float(hours_remaining):.2f} h")
-                    except (TypeError, ValueError):
-                        print(f"- Est. time remaining: ~{hours_remaining} h")
+                    print(f"- Est. time remaining: {self._safe_format_float(hours_remaining)} h")
 
             if has_nvidia_data:
-                try:
-                    print(f"- NVIDIA power draw: ~{float(nvidia_watts):.2f} W")
-                except (TypeError, ValueError):
-                    print(f"- NVIDIA power draw: ~{nvidia_watts} W")
+                print(f"- NVIDIA power draw: {self._safe_format_float(nvidia_watts)} W")

1153-1155: Defensive guard is redundant (nitpick).

The mutual exclusivity between --dry-run and --execute is already enforced by argparse (line 2418), making this runtime check unnecessary. However, keeping it as defensive programming is acceptable.


1204-1240: Consider documenting the integrated GPU environment unsetting behavior.

The logic at lines 1225-1238 builds an env -u command prefix to explicitly unset NVIDIA PRIME variables for integrated GPU runs. This is a subtle but important detail for the integrated mode to work correctly when the parent shell has these variables set. Consider adding a comment explaining why this is necessary.

📝 Suggested comment
+            # For integrated GPU mode, we must explicitly unset NVIDIA PRIME
+            # offload variables that may be present in the parent shell environment.
+            # run_command_with_env only adds vars, so we use 'env -u' to remove them.
             if (integrated_case or app_case) and not env:
                 cmd = [
                     "env",
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d96af30 and 7d2d30a.

📒 Files selected for processing (1)
  • cortex/cli.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Follow PEP 8 style guide
Type hints required in Python code
Docstrings required for all public APIs

Files:

  • cortex/cli.py
🧬 Code graph analysis (1)
cortex/cli.py (7)
cortex/gpu_manager.py (9)
  • apply_gpu_mode_switch (301-317)
  • detect_gpu_switch_backend (220-231)
  • get_app_gpu_preference (170-180)
  • get_per_app_gpu_env (193-217)
  • list_app_gpu_preferences (183-190)
  • plan_gpu_mode_switch (234-279)
  • remove_app_gpu_preference (153-167)
  • run_command_with_env (282-298)
  • set_app_gpu_preference (134-150)
cortex/hardware_detection.py (2)
  • detect_gpu_mode (839-858)
  • estimate_gpu_battery_impact (861-939)
cortex/branding.py (1)
  • cx_print (49-69)
cortex/kernel_features/llm_device.py (1)
  • getattr (81-105)
cortex/kernel_features/accelerator_limits.py (2)
  • get (69-72)
  • status (97-105)
cortex/kernel_features/model_lifecycle.py (1)
  • status (135-143)
cortex/sandbox/sandbox_executor.py (1)
  • execute (501-633)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test (Python 3.10)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
🔇 Additional comments (5)
cortex/cli.py (5)

4-4: LGTM!

The new imports are properly organized and all are actively used in the GPU management methods below.

Also applies to: 23-34


2295-2296: LGTM!

Help table entries are clear and follow the existing formatting pattern.


2856-2859: LGTM!

Command routing is properly implemented and follows the existing pattern in the main function.


1225-1238: Unsetting is necessary and correct — no changes needed.

The explicit env -u commands are justified. These NVIDIA PRIME variables (__NV_PRIME_RENDER_OFFLOAD, __GLX_VENDOR_LIBRARY_NAME, __VK_LAYER_NV_optimus) may be set in the parent shell from previous NVIDIA-mode runs or user environment configuration. Since run_command_with_env inherits the current environment and only adds variables (doesn't remove), the only way to prevent these PRIME variables from affecting integrated GPU mode is to explicitly unset them with env -u. The condition (integrated_case or app_case) and not env correctly limits unsetting to cases where no GPU-specific env vars need to be set, making this approach sound.


2397-2453: Well-structured argument parsing with proper constraints.

The GPU command parsers are correctly set up with:

  • Mutual exclusivity for --dry-run and --execute
  • Required subparsers to enforce subcommand selection
  • Proper use of argparse.REMAINDER for the run command

The project requires Python 3.10+, which fully supports required=True on subparsers (added in Python 3.7) and argparse.REMAINDER (added in Python 3.5), so all features used here are safe.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 9, 2026

@tuanwannafly
Copy link
Contributor Author

@Anshgrover23 I resolved all bot comment
review for me, thanks

Copy link
Collaborator

@Anshgrover23 Anshgrover23 left a comment

Choose a reason for hiding this comment

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

@tuanwannafly Have u reviewed this on hybrid system ? Attach a video.

- Heuristic: `nvidia-smi` may be missing or blocked even on NVIDIA systems.
- On some hybrid laptops, NVIDIA may be detected but still idle.
"""
return bool(_run(["nvidia-smi"]))
Copy link
Collaborator

Choose a reason for hiding this comment

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

This only checks whether nvidia-smi exists/returns output. That does not mean the NVIDIA GPU is active or drawing power on Optimus systems.
@tuanwannafly Have u really checked this on a hybrid system ?


if args.gpu_command == "status":
backend = detect_gpu_switch_backend()
mode = detect_gpu_mode()
Copy link
Collaborator

Choose a reason for hiding this comment

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

This logic is NVIDIA-centric only and collapses multiple distinct cases (idle NVIDIA, non-NVIDIA dGPU) into misleading modes.

# Execute only when --execute is set (otherwise plan-only)
if getattr(args, "execute", False):
if not getattr(args, "yes", False):
console.print("\n⚠ This will run GPU switch commands with sudo.")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Needs a stronger warning that this can break display sessions and requires reboot/logout.

return ""

monkeypatch.setattr(hd, "_run", fake_run)
assert hd.detect_gpu_mode() == "NVIDIA"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Current tests lock in the incorrect assumption that nvidia-smi implies active NVIDIA usage, making future fixes harder.

@tuanwannafly
Copy link
Contributor Author

@tuanwannafly Have u reviewed this on hybrid system ? Attach a video.

I use win and Ubuntu wsl so I cann't test it on Hybrid system
I will try it

@Anshgrover23
Copy link
Collaborator

@tuanwannafly Thanks for the update. Since this feature targets real hybrid (Optimus) GPU behavior and hasn’t been validated on actual hybrid hardware, we can’t proceed with this PR.
We’re closing this one for now, but feel free to open a new PR once you can test on a real hybrid system, as required by the scope of the issue.

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.

Hybrid GPU (Optimus) Manager

2 participants