Skip to content

Conversation

@RIVALHIDE
Copy link

@RIVALHIDE RIVALHIDE commented Dec 22, 2025

Related Issue

Closes #45

Summary

This PR implements a comprehensive system snapshot and rollback feature for Cortex Linux. Users can now create system snapshots before installations, list existing snapshots, and restore to previous states if something goes wrong.

Key Features

  • Snapshot Creation: Captures current state of APT, PIP, and NPM packages with unique timestamped IDs
  • Snapshot Management: List, view details, and delete snapshots via CLI
  • System Restoration: Rollback to previous package states with dry-run support
  • Automatic Retention: Maintains maximum of 10 snapshots, auto-deletes oldest
  • Security Hardened: Protection against shell injection, sudo validation, proper permissions (700)

Implementation Details

  • New SnapshotManager class in cortex/snapshot_manager.py (398 lines)
  • CLI integration with 5 commands: create, list, show, restore, delete
  • Comprehensive test suite with 15 unit tests above 90% coverage
  • Storage in ~/.cortex/snapshots/ with JSON metadata
  • Microsecond-precision snapshot IDs to prevent race conditions

Security Fixes

  • Eliminated shell injection vulnerabilities (6 locations)
  • Added sudo permission validation before restore operations
  • Implemented timeout protection for subprocess calls
  • Enhanced error handling with detailed messages

Usage Examples

# Create a snapshot
cortex snapshot create "Before major upgrade"
image
# List all snapshots
cortex snapshot list
image
# Restore to a previous state (dry-run)
cortex snapshot restore 20251222_160045_531066 --dry-run
image
# Restore to a previous state
cortex snapshot restore 20251222_160045_531066
image
# Delete a snapshot
cortex snapshot delete 20251222_160045_531066
image

Test Results

Dependency Resolver Tests (9/9 passed)
test_basic_conflict_resolution - Semantic version conflict resolution
test_missing_keys_raises_keyerror - Required field validation
test_invalid_semver_produces_error_strategy - Invalid version handling
test_resolution_with_default_version - Default version support
test_complex_version_constraints - Complex constraint resolution
test_wildcard_version - Wildcard version support
test_major_version_conflict - Major version conflict handling
test_recommended_strategy_targets_package_b - Strategy validation
test_alternative_strategy_targets_dependency - Alternative strategies

Snapshot Manager Tests (27/27 passed)
Snapshot creation and exception handling
List, get, and delete snapshot operations
APT/PIP/NPM package detection
Dry-run and live restore operations
Error handling and timeout scenarios
System info retrieval and validation
Retention policy enforcement

Overall Test Suite
706 passed, 10 skipped in 40.28s

Summary by CodeRabbit

  • New Features

    • System snapshot CLI: create, list, show, restore (dry-run preview), delete, automatic retention and user snapshot preferences.
    • Dependency conflict resolver offering recommended and alternative resolution strategies.
  • Documentation

    • Comprehensive snapshot system design, rollback guide, race-fix notes and test plan.
  • Other Improvements

    • Richer CLI help, improved hardware info display, and cross-process locking for safe concurrent snapshot operations.
  • Tests

    • New unit, concurrency and race-condition tests; test commands updated to use python3.

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

- Implement SnapshotManager with create/list/restore/delete commands
- Fix shell injection vulnerabilities in restore operations
- Add microseconds to snapshot IDs to prevent collisions
- Include 15 unit tests with 66% code coverage
- All 662 tests passing

Security: Replace shell=True with list-based subprocess calls
Copilot AI review requested due to automatic review settings December 22, 2025 11:50
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 22, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds a Snapshot subsystem (SnapshotManager + SnapshotMetadata), integrates cortex snapshot CLI commands (create, list, show, restore, delete) with rich help, persists secure metadata with retention and file-locking, adds unit and concurrency tests and docs, extends user preferences, and introduces a DependencyResolver with tests.

Changes

Cohort / File(s) Summary
CLI integration
cortex/cli.py
Adds CortexCLI.snapshot(self, args: argparse.Namespace) -> int, wires cortex snapshot subcommands (create, list, show, restore, delete), updates parsing and rich help, and routes snapshot actions to SnapshotManager.
Snapshot core
cortex/snapshot_manager.py
New SnapshotManager and SnapshotMetadata dataclass: create (APT/PIP/NPM detection), list/get/delete, restore (dry-run & execute), secure metadata persistence (dir 700, file 600), retention policy, cross-process file locking, and safe JSON reads with retry.
Documentation
docs/SNAPSHOT_SYSTEM_IMPLEMENTATION.md, MULTIPROCESS_RACE_FIX.md, COMMIT_MSG.txt
New design/implementation and race-fix docs describing commands, data models, security, locking strategy, tests, and rationale.
Snapshot unit tests
tests/unit/test_snapshot_manager.py
New comprehensive unit tests using mocks for create/list/get/delete/restore, package-detection paths, retention, metadata IO, ID format, and error scenarios.
Concurrent / race tests & scripts
test_concurrent_simple.py, test_multiprocess_race.py
New scripts exercising concurrent snapshot creation and race conditions; aggregate results and detect JSON corruption patterns.
User preferences
cortex/user_preferences.py
Adds SnapshotSettings dataclass and snapshots: SnapshotSettings field on UserPreferences.
Dependency resolver
cortex/resolver.py, tests/test_resolver.py
New DependencyResolver with resolve(conflict_data: dict) -> list[dict] producing resolution strategies; tests covering valid/invalid inputs and strategy contents.
Demo & minor test tweaks
cortex/demo.py, tests/installer/test_parallel_install.py
cortex/demo.py: attribute-based hardware access and GPU flag logic; installer tests: change pythonpython3 in commands.
Requirements & reporting
requirements.txt, TEST_SUMMARY.md
Add filelock and semantic-version entries; update requirements.txt; add TEST_SUMMARY.md with test/coverage summary.

Sequence Diagram(s)

sequenceDiagram
    actor User as User/CLI
    participant CLI as CortexCLI
    participant SM as SnapshotManager
    participant PKG as PackageManagers
    participant FS as Filesystem

    rect rgb(223,247,223)
    Note over User,CLI: Create snapshot
    User->>CLI: cortex snapshot create "desc"
    CLI->>SM: create_snapshot(desc)
    SM->>PKG: detect installed packages (APT/PIP/NPM)
    PKG-->>SM: package lists
    SM->>SM: collect system info & generate id
    SM->>FS: create snapshot dir & write metadata.json (dir 700, file 600) with FileLock
    SM->>SM: apply retention policy
    SM-->>CLI: (success, snapshot_id, message)
    CLI-->>User: print confirmation
    end

    rect rgb(255,245,209)
    Note over User,CLI: Restore (dry-run)
    User->>CLI: cortex snapshot restore <id> --dry-run
    CLI->>SM: restore_snapshot(id, dry_run=True)
    SM->>FS: read metadata.json (safe read with retry, FileLock)
    SM->>PKG: detect current packages
    SM->>SM: compute diffs & generate commands
    SM-->>CLI: planned commands (dry-run)
    CLI-->>User: print commands
    end

    rect rgb(251,239,246)
    Note over User,CLI: Restore (execute)
    User->>CLI: cortex snapshot restore <id>
    CLI->>SM: restore_snapshot(id, dry_run=False)
    SM->>SM: verify sudo/permissions
    SM->>PKG: execute install/uninstall commands (may use sudo)
    PKG-->>SM: execution results
    SM-->>CLI: success/failure + executed commands
    CLI-->>User: print result
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • cortexlinux/cortex#345 — Also modifies cortex/cli.py by adding new subcommands to CortexCLI, touching the same class/file and likely related to CLI surface changes.

Suggested labels

enhancement

Suggested reviewers

  • mikejmorgan-ai
  • Suyashd999

Poem

"I hopped through code with nimble paws,
I tucked your packages without a pause.
Snapshots snug in folders tight,
Rewind and restore by morning light.
🐇🔁 Safe and fluffy—snapshot delight."

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Most changes are within scope for issue #45. However, the PR includes out-of-scope changes: DependencyResolver implementation, user preferences refactoring, demo.py GPU detection improvements, Python 3 test updates, and concurrent test scripts that are not required by the snapshot feature. Separate out-of-scope changes into dedicated PRs. Keep this PR focused on snapshot functionality only, removing resolver.py, user_preferences.py changes, demo.py modifications, test_concurrent_simple.py, test_multiprocess_race.py, and unrelated test updates.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main feature being implemented: a system snapshot and rollback capability, matching the primary objective of the PR.
Description check ✅ Passed The PR description comprehensively covers the feature summary, implementation details, security fixes, usage examples, and test results. It follows the template with issue number, summary, and checklist structure.
Linked Issues check ✅ Passed The PR successfully implements all requirements from issue #45: snapshot creation with APT/PIP/NPM package detection, list and show commands, restore with dry-run support, configurable retention (default 10), secure metadata storage, and comprehensive test coverage (27 tests passed).
Docstring Coverage ✅ Passed Docstring coverage is 85.29% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 790e88d and 9433901.

📒 Files selected for processing (1)
  • requirements.txt
🚧 Files skipped from review as they are similar to previous changes (1)
  • requirements.txt

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

🧹 Nitpick comments (7)
docs/SNAPSHOT_SYSTEM_IMPLEMENTATION.md (1)

62-67: Add language specifiers to fenced code blocks.

Multiple code blocks throughout this document lack language specifiers (e.g., lines 62, 122, 143, 167, 229, 247, etc.). Adding language identifiers improves syntax highlighting and accessibility.

🔎 Example fix
-```
+```text
  CX  ✓ ✅ Snapshot 20251222_160045_531066 created successfully with 1766 packages
tests/unit/test_snapshot_manager.py (3)

271-276: Directory permission check is incomplete.

The test verifies directory existence but not the actual 700 permissions as claimed. Consider using stat to verify the mode:

🔎 Proposed enhancement
 def test_directory_security(self):
     """Test that snapshot directory has secure permissions."""
     # Directory should be created with 700 permissions
     self.assertTrue(self.snapshots_dir.exists())
-    # Note: Permission checking requires actual filesystem, 
-    # but we verify the directory exists
+    # Verify 700 permissions (owner rwx only)
+    import stat
+    mode = self.snapshots_dir.stat().st_mode & 0o777
+    self.assertEqual(mode, 0o700, f"Expected 700 permissions, got {oct(mode)}")

108-109: Unused @patch.object decorator.

The mock_create parameter is declared but never used in the test body. The same applies to test_get_snapshot_success (line 140) and test_delete_snapshot_success (line 172). Remove unused patches to clarify test intent.

🔎 Proposed fix
-@patch.object(SnapshotManager, "create_snapshot")
-def test_list_snapshots_with_data(self, mock_create):
+def test_list_snapshots_with_data(self):
     """Test listing snapshots with existing data."""

75-79: Dictionary ordering assumption in NPM test.

The test assumes packages[0]["name"] equals "express", but dictionary iteration order in npm_data["dependencies"] is insertion order (Python 3.7+). While this works, the assertion is fragile if the mock data structure changes. Consider asserting membership instead:

names = {pkg["name"] for pkg in packages}
self.assertIn("express", names)
self.assertIn("lodash", names)
cortex/snapshot_manager.py (3)

154-170: Add timeout to uname subprocess calls.

Unlike the package detection methods, the uname calls lack timeout protection. While uname is typically fast, consistency with the rest of the codebase is preferred.

🔎 Proposed fix
 result = subprocess.run(
     ["uname", "-r"],
     capture_output=True,
     text=True,
     check=False,
+    timeout=self.TIMEOUT,
 )

74-76: Consider validating snapshot_id against path traversal.

The _get_snapshot_path method directly concatenates snapshot_id into the path. While the ID is generated internally, external callers (via CLI) could potentially pass malicious IDs like ../../../etc. Consider validating the ID format:

🔎 Proposed validation
 def _get_snapshot_path(self, snapshot_id: str) -> Path:
     """Get path to snapshot directory"""
+    # Validate snapshot_id to prevent path traversal
+    if not snapshot_id or "/" in snapshot_id or snapshot_id.startswith("."):
+        raise ValueError(f"Invalid snapshot ID: {snapshot_id}")
     return self.snapshots_dir / snapshot_id

345-357: Restore can leave system in partial state on failure.

If the first APT remove succeeds but APT install fails, the system is left in an inconsistent state. Consider:

  1. Documenting this limitation clearly
  2. Or implementing a two-phase approach (dry-run validation first, then execute)

This is noted as a reliability concern for future improvement.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f6bfa49 and d6421c2.

📒 Files selected for processing (4)
  • cortex/cli.py
  • cortex/snapshot_manager.py
  • docs/SNAPSHOT_SYSTEM_IMPLEMENTATION.md
  • tests/unit/test_snapshot_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/cli.py
  • cortex/snapshot_manager.py
  • tests/unit/test_snapshot_manager.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Maintain >80% test coverage for pull requests

Files:

  • tests/unit/test_snapshot_manager.py
🧬 Code graph analysis (3)
cortex/cli.py (3)
cortex/snapshot_manager.py (6)
  • SnapshotManager (37-429)
  • create_snapshot (177-233)
  • list_snapshots (235-253)
  • get_snapshot (255-273)
  • restore_snapshot (298-411)
  • delete_snapshot (275-296)
cortex/first_run_wizard.py (1)
  • _print_error (746-748)
cortex/branding.py (1)
  • cx_print (49-69)
cortex/snapshot_manager.py (4)
cortex/logging_system.py (2)
  • warning (202-204)
  • info (198-200)
cortex/kernel_features/llm_device.py (1)
  • open (158-159)
cortex/user_preferences.py (1)
  • load (117-148)
cortex/cli.py (1)
  • snapshot (747-835)
tests/unit/test_snapshot_manager.py (1)
cortex/snapshot_manager.py (5)
  • SnapshotMetadata (25-34)
  • _detect_apt_packages (82-101)
  • _detect_pip_packages (103-120)
  • _detect_npm_packages (122-140)
  • _generate_snapshot_id (70-72)
🪛 LanguageTool
docs/SNAPSHOT_SYSTEM_IMPLEMENTATION.md

[style] ~4-~4: Some style guides suggest that commas should set off the year in a month-day-year date.
Context: ... Implementation Date: December 22, 2025 Status: ✅ Complete and Production...

(MISSING_COMMA_AFTER_YEAR)


[uncategorized] ~634-~634: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...eges. Please run: sudo -v ``` --- ### Medium Priority Issues (Fixed) #### 5. 📝 Poor Error M...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

🪛 markdownlint-cli2 (0.18.1)
docs/SNAPSHOT_SYSTEM_IMPLEMENTATION.md

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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)

⏰ 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). (1)
  • GitHub Check: Agent
🔇 Additional comments (7)
cortex/snapshot_manager.py (3)

24-35: LGTM!

The SnapshotMetadata dataclass is well-defined with proper type hints and default values for optional fields.


82-101: LGTM on APT package detection.

Proper timeout, error handling, and output parsing. Good use of check=False with explicit return code validation.


413-428: LGTM on retention policy.

The logic correctly sorts by timestamp and deletes the oldest snapshots when exceeding the limit. Error handling is appropriate.

cortex/cli.py (4)

747-756: LGTM on snapshot action validation.

Proper handling of missing subcommand with helpful error messages, consistent with other CLI commands like notify.


784-788: Potential AttributeError if snapshot_id not provided.

While argparse marks snapshot_id as required for show, restore, and delete subparsers, if a user bypasses argparse or the parser is misconfigured, args.snapshot_id could be missing. Consider defensive validation:

if not getattr(args, 'snapshot_id', None):
    self._print_error("Snapshot ID required")
    return 1

However, since argparse enforces this, the risk is low.


983-1005: LGTM on argument parser configuration.

Subparsers are correctly configured with appropriate help text and required arguments. The structure mirrors other CLI commands like notify.


1050-1051: LGTM on command routing.

Clean integration following the existing pattern for other commands.

Comment on lines +683 to +684
**Code Coverage**: 66.43%
**All Tests**: ✅ PASSING
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 | 🟡 Minor

Test coverage below required threshold.

The documentation states 66.43% coverage, but the coding guidelines require >80% test coverage for pull requests in tests/**/*.py. Consider adding tests for uncovered paths such as:

  • Error handling in _get_system_info
  • Actual restore execution (non-dry-run paths)
  • Edge cases in package detection failures
🤖 Prompt for AI Agents
In docs/SNAPSHOT_SYSTEM_IMPLEMENTATION.md around lines 683-684 the reported test
coverage is 66.43% which is below the required >80% for tests/**/*.py; add unit
and/or integration tests under tests/**/*.py to raise coverage by covering
currently untested paths: add tests that simulate and assert error handling in
_get_system_info (raise expected exceptions and verify logging/return behavior),
tests that exercise actual restore execution (non-dry-run behavior, state
changes, side effects and cleanup), and tests for package detection edge cases
(corrupt/missing manifests, unsupported formats, partial detections), run the
test suite with coverage to confirm >80% and iterate until coverage and
assertions pass.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a comprehensive system snapshot and rollback feature for Cortex Linux, allowing users to create, manage, and restore package state snapshots. The implementation includes support for APT, PIP, and NPM packages with security hardening against shell injection and proper permission management.

Key Changes:

  • New SnapshotManager class with snapshot creation, listing, restoration, and deletion capabilities
  • CLI integration with 5 commands for snapshot management (create, list, show, restore, delete)
  • Comprehensive test suite with 15 unit tests achieving 66.43% coverage
  • Detailed implementation documentation

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
tests/unit/test_snapshot_manager.py Comprehensive unit tests for SnapshotManager with 15 test cases covering all major functionality including package detection, snapshot CRUD operations, and retention policy
docs/SNAPSHOT_SYSTEM_IMPLEMENTATION.md Extensive documentation covering commands reference, implementation details, testing documentation, and security considerations
cortex/snapshot_manager.py Core implementation of SnapshotManager class with package detection for APT/PIP/NPM, snapshot creation/restoration with shell injection protection, and retention policy management
cortex/cli.py CLI integration adding snapshot subcommand with parsers for create, list, show, restore, and delete operations

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 197 to 203
snapshot_id = f"2025010{i % 10}_12000{i}"
snapshot_path = self.snapshots_dir / snapshot_id
snapshot_path.mkdir(parents=True)

metadata = {
"id": snapshot_id,
"timestamp": f"2025-01-0{i % 10}T12:00:0{i}",
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

The snapshot IDs created in this test also contain invalid date formats. The pattern "2025010{i % 10}_12000{i}" generates IDs like "20250100_120000" (when i=0) where "20250100" represents "2025-01-00" - an invalid date with day 0. For i=10 and i=11, this creates "20250100_1200010" and "20250101_1200011" which break the expected format entirely. These malformed IDs could cause unexpected behavior in the retention policy logic.

Suggested change
snapshot_id = f"2025010{i % 10}_12000{i}"
snapshot_path = self.snapshots_dir / snapshot_id
snapshot_path.mkdir(parents=True)
metadata = {
"id": snapshot_id,
"timestamp": f"2025-01-0{i % 10}T12:00:0{i}",
day = i + 1 # Days 01-12 (all valid)
snapshot_id = f"202501{day:02d}_120000"
snapshot_path = self.snapshots_dir / snapshot_id
snapshot_path.mkdir(parents=True)
metadata = {
"id": snapshot_id,
"timestamp": f"2025-01-{day:02d}T12:00:00",

Copilot uses AI. Check for mistakes.
RIVALHIDE and others added 6 commits December 22, 2025 17:29
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
cortex/snapshot_manager.py (1)

143-177: Consider adding timeouts to subprocess calls for consistency.

While uname commands typically execute instantly, adding timeout parameters would improve consistency with other subprocess calls in this file and provide defense-in-depth.

🔎 Proposed enhancement
             result = subprocess.run(
                 ["uname", "-r"],
                 capture_output=True,
                 text=True,
                 check=False,
+                timeout=5,
             )

Apply the same change at line 165 for the uname -m call.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 78fa576 and ad24b2c.

📒 Files selected for processing (2)
  • cortex/cli.py
  • cortex/snapshot_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/cli.py
  • cortex/snapshot_manager.py
🧬 Code graph analysis (2)
cortex/cli.py (3)
cortex/snapshot_manager.py (6)
  • SnapshotManager (37-456)
  • create_snapshot (178-235)
  • list_snapshots (237-255)
  • get_snapshot (257-275)
  • restore_snapshot (369-438)
  • delete_snapshot (277-298)
cortex/first_run_wizard.py (1)
  • _print_error (746-748)
cortex/branding.py (1)
  • cx_print (49-69)
cortex/snapshot_manager.py (1)
cortex/cli.py (1)
  • snapshot (747-835)
🔇 Additional comments (22)
cortex/snapshot_manager.py (13)

1-22: LGTM!

Module structure, imports, and documentation are well-organized and follow Python best practices.


24-35: LGTM!

The dataclass is well-structured with appropriate type hints and documentation.


37-52: LGTM!

Class documentation is clear, and timeout constants are well-defined.


53-70: LGTM!

Directory initialization and security enforcement are correctly implemented. The use of chmod() after directory creation properly addresses umask limitations.


71-82: LGTM!

Helper methods are well-documented with appropriate type hints. Microsecond-precision ID generation effectively prevents race conditions.


83-142: LGTM!

Package detection methods properly implement timeout protection and graceful error handling. The use of check=False with explicit return code checking is appropriate.


178-236: LGTM!

Snapshot creation is well-implemented with proper security measures. The use of os.open() with explicit mode flags ensures metadata files are created with secure permissions from the outset, avoiding TOCTOU vulnerabilities.


237-256: LGTM!

Snapshot listing logic is correct and provides appropriate error handling.


257-276: LGTM!

Snapshot retrieval is straightforward and properly documented.


277-299: LGTM!

Snapshot deletion is correctly implemented with appropriate existence checking and error handling.


300-319: LGTM!

Command execution helper properly implements timeout protection as per previous feedback. The separation of dry-run logic is clean.


369-439: LGTM!

Restoration logic is well-structured with comprehensive error handling. The sudo permission check and timeout protection are properly implemented.


440-456: LGTM!

Retention policy implementation correctly maintains the snapshot limit by removing oldest snapshots.

cortex/cli.py (9)

747-756: LGTM!

Method signature and initial validation are well-structured. The local import of SnapshotManager is appropriate for avoiding potential import overhead.


758-765: LGTM!

Snapshot creation flow is clean and provides appropriate user feedback.


767-782: LGTM!

Snapshot listing display properly accommodates microsecond-precision IDs with the 24-character width, addressing previous feedback.


784-800: LGTM!

Snapshot detail display is comprehensive and well-formatted.


802-824: LGTM!

Restore flow handles both dry-run and actual execution scenarios with appropriate user feedback.


826-834: LGTM!

Snapshot deletion flow is straightforward and provides clear feedback.


864-864: LGTM!

Help table entry is consistent with other command descriptions.


982-1006: LGTM!

Argument parser configuration is well-structured and follows CLI conventions consistently.


1050-1051: LGTM!

Command routing is properly integrated into the main dispatcher.

@RIVALHIDE RIVALHIDE changed the title Issue 45 #45] System Snapshot and Rollback Feature Dec 22, 2025
@RIVALHIDE RIVALHIDE changed the title #45] System Snapshot and Rollback Feature [#45] System Snapshot and Rollback Feature Dec 22, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
cortex/snapshot_manager.py (1)

342-389: APT restore ignores package versions from snapshot.

Line 374 defines the APT version format as lambda name, ver: name, discarding version information. Although snapshots capture APT versions via dpkg output, the restore logic fails to use them. APT packages install at their latest available version rather than the exact snapshot version, contradicting the PR's goal of capturing and restoring exact package states.

The correct fix uses APT's version specification syntax with an equals sign:

 version_formats = {
-    "apt": lambda name, ver: name,  # APT installs latest by default
+    "apt": lambda name, ver: f"{name}={ver}" if ver else name,
     "pip": lambda name, ver: f"{name}=={ver}",
     "npm": lambda name, ver: f"{name}@{ver}"
 }

Note: This may require error handling if the exact version is unavailable in APT repositories. Consider adding fallback logic or logging when version-specific installation fails.

🧹 Nitpick comments (3)
cortex/snapshot_manager.py (3)

83-112: Add type hint for parser_func parameter.

The parser_func parameter lacks a type annotation. Consider adding Callable[[str], list[dict[str, str]]] to improve type safety.

🔎 Proposed fix

Import Callable at the top of the file:

 from pathlib import Path
-from typing import Optional
+from typing import Callable, Optional

Then add the type hint:

 def _run_package_detection(
     self,
     cmd: list[str],
-    parser_func,
+    parser_func: Callable[[str], list[dict[str, str]]],
     manager_name: str
 ) -> list[dict[str, str]]:

132-139: Consider using None or empty string instead of "unknown" for missing NPM versions.

Line 138 uses "unknown" as a fallback version. Using None or an empty string might be more consistent and avoid potential issues if version strings are used for comparison or restoration.

🔎 Proposed change
         if "dependencies" in npm_data:
             for name, info in npm_data["dependencies"].items():
-                packages.append({"name": name, "version": info.get("version", "unknown")})
+                packages.append({"name": name, "version": info.get("version", "")})

165-198: Add timeout to subprocess calls in system info detection.

The subprocess.run calls at lines 177 and 187 lack a timeout parameter. While less critical than package operations, these could still hang if the system is under stress.

🔎 Proposed fix
         # Kernel version
         result = subprocess.run(
             ["uname", "-r"],
             capture_output=True,
             text=True,
             check=False,
+            timeout=5,
         )
         if result.returncode == 0:
             info["kernel"] = result.stdout.strip()
             
         # Architecture
         result = subprocess.run(
             ["uname", "-m"],
             capture_output=True,
             text=True,
             check=False,
+            timeout=5,
         )
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad24b2c and 747d4bc.

📒 Files selected for processing (1)
  • cortex/snapshot_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/snapshot_manager.py
🧬 Code graph analysis (1)
cortex/snapshot_manager.py (2)
cortex/logging_system.py (2)
  • warning (202-204)
  • info (198-200)
cortex/cli.py (1)
  • snapshot (747-835)
🔇 Additional comments (10)
cortex/snapshot_manager.py (10)

1-34: LGTM!

The module header, imports, and SnapshotMetadata dataclass are well-structured with proper type hints and documentation.


37-51: LGTM!

Class documentation is clear, and the constants are well-defined. The RESTORE_TIMEOUT of 300 seconds addresses previous feedback about timeout protection.


53-69: LGTM!

The directory initialization and security enforcement properly address previous concerns about permission windows by explicitly calling chmod after directory creation.


71-81: LGTM!

The snapshot ID generation with microsecond precision and path helper methods are correctly implemented.


141-163: LGTM!

The package detection methods correctly use the generic wrapper with appropriate commands for each package manager.


200-257: LGTM!

Snapshot creation properly implements security hardening with secure file permissions from creation (using os.open with 0o600 at line 241) and directory permissions enforcement. This addresses previous concerns about permission windows.


259-320: LGTM!

The snapshot listing, retrieval, and deletion methods are well-implemented with proper error handling and type hints.


322-340: LGTM!

The command execution wrapper properly implements timeout protection (300 seconds) and error propagation, addressing previous feedback about indefinite hangs.


391-460: LGTM!

The restore logic is well-structured with comprehensive error handling for timeout and subprocess failures. The sudo permission check (lines 409-424) is a good security practice before attempting system modifications.


462-478: LGTM!

The retention policy correctly implements the requirement to maintain a maximum of 10 snapshots by deleting the oldest ones.

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

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 747d4bc and c18a5f9.

📒 Files selected for processing (2)
  • TEST_SUMMARY.md
  • tests/installer/test_parallel_install.py
🧰 Additional context used
📓 Path-based instructions (3)
**/*.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/installer/test_parallel_install.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Maintain >80% test coverage for pull requests

Files:

  • tests/installer/test_parallel_install.py
**/*install*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*install*.py: Dry-run by default for all installations in command execution
No silent sudo execution - require explicit user confirmation
Implement audit logging to ~/.cortex/history.db for all package operations

Files:

  • tests/installer/test_parallel_install.py
🪛 markdownlint-cli2 (0.18.1)
TEST_SUMMARY.md

76-76: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🔇 Additional comments (3)
tests/installer/test_parallel_install.py (2)

20-22: LGTM! Consistent Python 3 invocation.

The updates from python to python3 across all test commands ensure explicit Python 3 interpreter usage, improving consistency and reliability.

Also applies to: 45-47, 73-75, 101-104, 124-124, 141-141, 154-154, 180-180, 200-202, 221-221


250-253: LGTM! Integration tests consistently updated.

The integration test commands are consistently updated to use python3, maintaining the same explicit Python 3 usage pattern as the unit tests.

Also applies to: 279-281

TEST_SUMMARY.md (1)

5-15: The review comment is incorrect and should be retracted.

Key findings:

  • TEST_SUMMARY.md explicitly states the coverage requirement as "> 0%" (lines 12-15), not ">80%"
  • The overall coverage of 60.06% exceeds the stated > 0% requirement and should show ✅ PASS
  • Issue #45 is not a coverage requirement issue—it documents the "System Snapshot and Rollback Points" feature
  • The 66.43% figure is module-specific coverage (snapshot_manager.py from SNAPSHOT_SYSTEM_IMPLEMENTATION.md), not a discrepancy with the overall 60.06% metric
  • CI workflow enforces --cov-fail-under=0, confirming the acceptance criteria is 0% overall coverage

The document's claim of "All Requirements Met" is accurate based on the stated requirements.

Likely an incorrect or invalid review comment.


**Date**: December 22, 2025

## ✅ All Requirements Met
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

Misleading claim when acceptance criteria are unmet.

Line 5 states "✅ All Requirements Met," but the coverage requirement of >80% from issue #45 is not satisfied (60.06% reported). Either the coverage requirement has been waived, or this claim should be revised to accurately reflect the status.

Consider updating the headline to reflect that coverage remains below the target or explicitly document the waiver/deferral of this requirement.

🤖 Prompt for AI Agents
In TEST_SUMMARY.md around line 5, the heading "✅ All Requirements Met" is
misleading because test coverage is 60.06% (below the >80% requirement from
issue #45); update the headline to accurately reflect the status (e.g., "⚠️ Some
Requirements Not Met" or "Coverage Below Target") and add one sentence noting
either the unmet coverage percentage or that the coverage requirement was
formally waived/deferred with a reference to the waiver ticket.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
cortex/snapshot_manager.py (1)

324-392: LGTM! Timeout protection and version handling properly implemented.

The code correctly addresses past review concerns:

  • Line 341: RESTORE_TIMEOUT prevents indefinite hangs
  • Lines 375-379: APT version format now properly specifies exact versions with f"{name}={ver}"

The conditional version formatting (if ver else name) provides good defensive handling for edge cases.

💡 Optional: Extract command templates to class constants

Lines 365-379 define manager-specific command templates. Consider extracting these to class-level constants for better organization:

class SnapshotManager:
    RETENTION_LIMIT = 10
    TIMEOUT = 30
    RESTORE_TIMEOUT = 300
    
    # Package manager command templates
    REMOVE_COMMANDS = {
        "apt": ["sudo", "apt-get", "remove", "-y"],
        "pip": ["pip", "uninstall", "-y"],
        "npm": ["npm", "uninstall", "-g"]
    }
    INSTALL_COMMANDS = {
        "apt": ["sudo", "apt-get", "install", "-y"],
        "pip": ["pip", "install"],
        "npm": ["npm", "install", "-g"]
    }
    
    @staticmethod
    def _version_format(manager: str, name: str, ver: str) -> str:
        """Format package name with version for specific manager."""
        formats = {
            "apt": lambda n, v: f"{n}={v}" if v else n,
            "pip": lambda n, v: f"{n}=={v}" if v else n,
            "npm": lambda n, v: f"{n}@{v}" if v else n
        }
        return formats[manager](name, ver)

This improves maintainability but is not critical for current functionality.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c18a5f9 and d90b2d4.

📒 Files selected for processing (1)
  • cortex/snapshot_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/snapshot_manager.py
🧬 Code graph analysis (1)
cortex/snapshot_manager.py (1)
cortex/cli.py (1)
  • snapshot (747-835)
🔇 Additional comments (10)
cortex/snapshot_manager.py (10)

1-35: LGTM! Clean structure and proper type hints.

The imports, module docstring, and SnapshotMetadata dataclass are well-structured with comprehensive type hints and documentation.


53-69: LGTM! Proper initialization with security enforcement.

The constructor correctly initializes the snapshots directory and enforces secure permissions via _enforce_directory_security(). The explicit chmod call addresses umask-related permission issues mentioned in past reviews.


71-81: LGTM! Clean utility methods.

Helper methods are well-documented with proper type hints and clear implementations.


165-200: LGTM! Robust system info gathering.

System information detection properly handles /etc/os-release parsing with maxsplit=1, uses timeout protection for subprocess calls, and includes comprehensive exception handling.


202-259: LGTM! Secure snapshot creation with proper permission handling.

The method correctly addresses past security concerns:

  • Line 217 uses explicit os.chmod() for directory permissions
  • Lines 243-245 use os.open() with mode 0o600 for secure file creation

The retention policy is properly applied after snapshot creation.


261-299: LGTM! Clean snapshot retrieval methods.

Both list_snapshots and get_snapshot have proper exception handling and return types matching their signatures. The newest-first sorting is appropriate for user experience.


301-322: LGTM! Safe snapshot deletion.

The method properly validates snapshot existence before deletion and includes exception handling.


393-462: LGTM! Comprehensive restore implementation with proper error handling.

The restore method includes:

  • Sudo validation (lines 411-426) before attempting changes
  • Specific exception handling for TimeoutExpired (lines 450-454) and CalledProcessError (lines 455-459)
  • Clear messaging for both dry-run and actual restore modes
  • Proper command tracking for user visibility

464-480: LGTM! Correct retention policy implementation.

The retention policy correctly maintains a maximum of 10 snapshots by deleting the oldest ones. Exception handling is appropriate with warning logs for best-effort cleanup.


1-480: Note: Test coverage gap relative to acceptance criteria.

The PR reports 66.43% test coverage for this implementation, while the linked issue #45 acceptance criteria require >80% coverage. Consider expanding unit tests to meet the target, particularly for edge cases in package detection, restoration error paths, and permission handling.

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

🧹 Nitpick comments (3)
cortex/snapshot_manager.py (3)

239-239: Offer to implement actual size calculation.

The size_bytes field is currently hardcoded to 0. This could be useful information for users managing disk space.

Do you want me to generate code to calculate the actual snapshot size by summing the metadata file size and any additional snapshot data?


271-271: Consider more explicit timestamp-based sorting.

The sorting relies on directory names (snapshot IDs) which happen to be timestamp-based. While this works, it's somewhat implicit. For consistency with _apply_retention_policy (line 471), consider sorting by the parsed timestamp from metadata.

🔎 Proposed refactor for explicit sorting
-            for snapshot_dir in sorted(self.snapshots_dir.iterdir(), reverse=True):
+            snapshot_list = []
+            for snapshot_dir in self.snapshots_dir.iterdir():
                 if snapshot_dir.is_dir():
                     metadata_path = snapshot_dir / "metadata.json"
                     if metadata_path.exists():
                         with open(metadata_path, "r") as f:
                             data = json.load(f)
-                            snapshots.append(SnapshotMetadata(**data))
+                            snapshot_list.append(SnapshotMetadata(**data))
+            # Sort by timestamp (newest first)
+            snapshots = sorted(snapshot_list, key=lambda s: s.timestamp, reverse=True)

377-379: Version format check could be more explicit.

The lambda functions use if ver to check for version presence, which will evaluate to False for empty strings. While this works, it's somewhat implicit.

🔎 More explicit version check
         version_formats = {
-            "apt": lambda name, ver: f"{name}={ver}" if ver else name,
-            "pip": lambda name, ver: f"{name}=={ver}" if ver else name,
-            "npm": lambda name, ver: f"{name}@{ver}" if ver else name
+            "apt": lambda name, ver: f"{name}={ver}" if ver is not None and ver != "" else name,
+            "pip": lambda name, ver: f"{name}=={ver}" if ver is not None and ver != "" else name,
+            "npm": lambda name, ver: f"{name}@{ver}" if ver is not None and ver != "" else name
         }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d90b2d4 and d716257.

📒 Files selected for processing (3)
  • TEST_SUMMARY.md
  • cortex/snapshot_manager.py
  • tests/unit/test_snapshot_manager.py
✅ Files skipped from review due to trivial changes (1)
  • TEST_SUMMARY.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/test_snapshot_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/snapshot_manager.py
🧬 Code graph analysis (1)
cortex/snapshot_manager.py (1)
cortex/cli.py (1)
  • snapshot (747-835)
🔇 Additional comments (1)
cortex/snapshot_manager.py (1)

394-463: Comprehensive restore implementation with excellent error handling.

The restore logic is well-structured with:

  • Snapshot validation before proceeding
  • Sudo permission check with clear user guidance
  • Per-manager package diff computation
  • Comprehensive exception handling for timeouts and command failures
  • Detailed error messages including command context

The dry-run support is properly implemented throughout.

# Define manager-specific command templates
remove_cmds = {
"apt": ["sudo", "apt-get", "remove", "-y"],
"pip": ["pip", "uninstall", "-y"],
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use sys.executable for pip commands to match detection.

The pip uninstall and install commands use the plain pip executable, but package detection (line 153) uses sys.executable -m pip to ensure consistency with the running Python interpreter. This inconsistency could cause restoration to use a different pip binary than detection, leading to mismatched package states.

🔎 Proposed fix for pip command consistency
         remove_cmds = {
             "apt": ["sudo", "apt-get", "remove", "-y"],
-            "pip": ["pip", "uninstall", "-y"],
+            "pip": [sys.executable, "-m", "pip", "uninstall", "-y"],
             "npm": ["npm", "uninstall", "-g"]
         }
         install_cmds = {
             "apt": ["sudo", "apt-get", "install", "-y"],
-            "pip": ["pip", "install"],
+            "pip": [sys.executable, "-m", "pip", "install"],
             "npm": ["npm", "install", "-g"]
         }

Also applies to: 373-373

🤖 Prompt for AI Agents
In cortex/snapshot_manager.py around lines 368 and 373, the pip
uninstall/install commands use the literal "pip" executable which is
inconsistent with package detection at line 153 that uses sys.executable -m pip;
update those command lists to invoke pip via the running Python interpreter
(i.e., use sys.executable with "-m", "pip", followed by the existing args such
as "uninstall", "-y" or "install", etc.) so the same interpreter is used for
detection and restoration.

remove_cmds = {
"apt": ["sudo", "apt-get", "remove", "-y"],
"pip": ["pip", "uninstall", "-y"],
"npm": ["npm", "uninstall", "-g"]
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

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the file and understand the context around lines 369-374
cat -n cortex/snapshot_manager.py | sed -n '360,385p'

Repository: cortexlinux/cortex

Length of output: 1325


🏁 Script executed:

# Check the _execute_package_command method to see if sudo is applied there
cat -n cortex/snapshot_manager.py | grep -A 20 "_execute_package_command"

Repository: cortexlinux/cortex

Length of output: 2121


🏁 Script executed:

# Check what happens around line 411-412 about sudo permissions check
cat -n cortex/snapshot_manager.py | sed -n '411,430p'

Repository: cortexlinux/cortex

Length of output: 903


🏁 Script executed:

# Let's check if there's any environment setup or sudo wrapping around the entire restore process
cat -n cortex/snapshot_manager.py | sed -n '429,450p'

Repository: cortexlinux/cortex

Length of output: 1453


🏁 Script executed:

# Check if there are any comments or configuration about npm permissions elsewhere in the file
rg -n "npm|sudo" cortex/snapshot_manager.py | head -30

Repository: cortexlinux/cortex

Length of output: 1465


🏁 Script executed:

# Check if there's documentation about npm configuration requirements
find . -type f \( -name "README*" -o -name "SETUP*" -o -name "INSTALL*" \) | head -10

Repository: cortexlinux/cortex

Length of output: 391


🏁 Script executed:

# Check for npm-related documentation and setup instructions
rg -i "npm|node|global" README.md docs/modules/README_DEPENDENCIES.md | head -20

Repository: cortexlinux/cortex

Length of output: 181


🏁 Script executed:

# Let's check how _detect_npm_packages detects npm packages
cat -n cortex/snapshot_manager.py | sed -n '158,170p'

Repository: cortexlinux/cortex

Length of output: 582


🏁 Script executed:

# Check if there's any issue tracking or TODO comments about npm sudo
rg -i "TODO|FIXME|BUG|issue" cortex/snapshot_manager.py | grep -i "npm\|sudo"

Repository: cortexlinux/cortex

Length of output: 44


🏁 Script executed:

# Check for any tests that might reveal intended behavior
find . -type f -name "*test*.py" -o -name "*_test.py" | grep -i snapshot

Repository: cortexlinux/cortex

Length of output: 99


🏁 Script executed:

# Check test file for npm/sudo related tests
rg -n "npm|sudo" tests/unit/test_snapshot_manager.py

Repository: cortexlinux/cortex

Length of output: 3769


🏁 Script executed:

# Check the test_restore_snapshot_live_execution test to see what npm commands are being tested
cat -n tests/unit/test_snapshot_manager.py | sed -n '281,320p'

Repository: cortexlinux/cortex

Length of output: 2072


🏁 Script executed:

# Let's check if there are any comments or documentation about npm configuration in the codebase
rg -i "npm.*prefix|npm.*config|node.*global" --type md --type txt

Repository: cortexlinux/cortex

Length of output: 126


🏁 Script executed:

# Check if there's any npm configuration or setup documentation
cat -n docs/CONFIGURATION.md | grep -A 10 -B 5 "npm"

Repository: cortexlinux/cortex

Length of output: 2440


🏁 Script executed:

# Check if there's any npm configuration or setup documentation in the project
find . -name ".npmrc" -o -name "package.json" -o -name ".nvm*" | head -10

Repository: cortexlinux/cortex

Length of output: 44


🏁 Script executed:

# Check if there are any setup instructions or comments about npm configuration
grep -r "npm" cortex/snapshot_manager.py | grep -i "config\|setup\|prefix\|sudo"

Repository: cortexlinux/cortex

Length of output: 44


Add sudo prefix to npm global commands to match apt behavior.

The npm commands don't include sudo for global operations (lines 369, 374), despite the code checking sudo availability at the start of restoration and apt commands including sudo in their templates. By default, installing global packages using NPM requires administrator privileges on most Linux systems. The mismatch between apt (which includes sudo) and npm (which doesn't) will cause npm restoration to fail with permission errors on standard system configurations.

Change:

  • Line 369: "npm": ["npm", "uninstall", "-g"]"npm": ["sudo", "npm", "uninstall", "-g"]
  • Line 374: "npm": ["npm", "install", "-g"]"npm": ["sudo", "npm", "install", "-g"]
🤖 Prompt for AI Agents
In cortex/snapshot_manager.py around line 369 (and similarly at line 374), the
npm command templates lack the sudo prefix for global operations; update the npm
arrays to prefix commands with "sudo" so they read ["sudo", "npm", "uninstall",
"-g"] at line 369 and ["sudo", "npm", "install", "-g"] at line 374 to match apt
behavior and the earlier sudo availability 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: 1

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d716257 and acb5880.

📒 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
🔇 Additional comments (4)
cortex/cli.py (4)

787-838: LGTM - Show, restore, and delete actions are well-implemented.

The show action provides clear detailed output, the restore action properly handles both dry-run preview and actual restoration with appropriate error reporting, and the delete action follows consistent patterns. Error handling is thorough across all three actions.


867-867: LGTM - Help table updated correctly.

The snapshot command is properly documented in the help table following the established pattern.


986-1008: LGTM - Argument parser setup is correct.

The snapshot command argument structure is well-defined with appropriate subcommands, required/optional arguments, and help text. The pattern is consistent with the existing notify command's subparser approach.


1053-1054: LGTM - Command routing is correct.

The snapshot command is properly routed to the handler method following the established pattern for other commands.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/unit/test_snapshot_manager.py (2)

30-102: Add return type hints to helper methods.

Per the coding guidelines, type hints are required in Python code. The three helper methods lack return type hints:

  • _create_mock_snapshot_metadata should return -> SnapshotMetadata
  • _create_mock_snapshot_on_disk should return -> Path
  • _setup_mock_packages should return -> None
🔎 Proposed fix
-    def _create_mock_snapshot_metadata(self, snapshot_id="test_snapshot",
+    def _create_mock_snapshot_metadata(self, snapshot_id: str = "test_snapshot",
                                       apt_packages=None, pip_packages=None,
-                                       npm_packages=None, description="Test"):
+                                       npm_packages=None, description: str = "Test") -> SnapshotMetadata:
         """Helper method to create mock snapshot metadata.

-    def _create_mock_snapshot_on_disk(self, snapshot_id, metadata_dict=None):
+    def _create_mock_snapshot_on_disk(self, snapshot_id: str, metadata_dict: dict = None) -> Path:
         """Helper method to create a mock snapshot directory and metadata file.

-    def _setup_mock_packages(self, mock_apt, mock_pip, mock_npm,
+    def _setup_mock_packages(self, mock_apt, mock_pip, mock_npm,
                             apt_packages=None, pip_packages=None, npm_packages=None):
+                             apt_packages=None, pip_packages=None, npm_packages=None) -> None:
         """Helper method to setup mock package detection return values.

306-311: Strengthen the directory security test to verify actual permissions.

The current test only checks that the directory exists but doesn't verify the 0o700 permissions that are enforced in create_snapshot. The comment "Permission checking requires actual filesystem" is misleading—tests can verify permissions using os.stat(path).st_mode.

🔎 Proposed enhancement
     def test_directory_security(self):
         """Test that snapshot directory has secure permissions."""
-        # Directory should be created with 700 permissions
+        # Create a snapshot to trigger directory creation with secure permissions
+        with patch.object(SnapshotManager, "_detect_apt_packages", return_value=[]):
+            with patch.object(SnapshotManager, "_detect_pip_packages", return_value=[]):
+                with patch.object(SnapshotManager, "_detect_npm_packages", return_value=[]):
+                    with patch.object(SnapshotManager, "_get_system_info", return_value={}):
+                        success, snapshot_id, _ = self.manager.create_snapshot("Security test")
+        
+        self.assertTrue(success)
+        snapshot_path = self.snapshots_dir / snapshot_id
         self.assertTrue(self.snapshots_dir.exists())
-        # Note: Permission checking requires actual filesystem,
-        # but we verify the directory exists
+        
+        # Verify directory has 700 permissions (owner read/write/execute only)
+        import stat
+        mode = os.stat(snapshot_path).st_mode
+        self.assertEqual(stat.S_IMODE(mode), 0o700)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between acb5880 and 374a760.

📒 Files selected for processing (2)
  • cortex/cli.py
  • tests/unit/test_snapshot_manager.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • cortex/cli.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/unit/test_snapshot_manager.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Maintain >80% test coverage for pull requests

Files:

  • tests/unit/test_snapshot_manager.py
🧠 Learnings (1)
📚 Learning: 2025-12-11T12:03:24.071Z
Learnt from: CR
Repo: cortexlinux/cortex PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-11T12:03:24.071Z
Learning: Applies to tests/**/*.py : Maintain >80% test coverage for pull requests

Applied to files:

  • tests/unit/test_snapshot_manager.py
🧬 Code graph analysis (1)
tests/unit/test_snapshot_manager.py (1)
cortex/snapshot_manager.py (12)
  • SnapshotMetadata (26-35)
  • _detect_apt_packages (142-148)
  • _detect_pip_packages (150-156)
  • _detect_npm_packages (158-164)
  • create_snapshot (203-260)
  • list_snapshots (262-280)
  • get_snapshot (282-300)
  • delete_snapshot (302-323)
  • _apply_retention_policy (465-481)
  • restore_snapshot (394-463)
  • _generate_snapshot_id (72-74)
  • _get_system_info (166-201)
🔇 Additional comments (1)
tests/unit/test_snapshot_manager.py (1)

349-556: LGTM! Comprehensive error handling coverage.

The error handling tests thoroughly cover various failure scenarios:

  • System info detection failures (exceptions and non-zero return codes)
  • CalledProcessError with and without stderr
  • Timeout scenarios (TimeoutExpired)
  • Missing binaries (FileNotFoundError)
  • Invalid JSON parsing
  • Sudo permission failures
  • Snapshot creation exceptions

These tests address the coverage gaps identified in previous reviews and properly verify that the system degrades gracefully under error conditions.

Comment on lines 246 to 258
# Create 12 snapshots manually (exceeds limit of 10)
for i in range(12):
snapshot_id = f"2025010{(i % 9) + 1}_12000{i}"
metadata = {
"id": snapshot_id,
"timestamp": f"2025-01-0{(i % 9) + 1}T12:00:0{i}",
"description": f"Snapshot {i}",
"packages": {"apt": [], "pip": [], "npm": []},
"system_info": {},
"file_count": 0,
"size_bytes": 0
}
self._create_mock_snapshot_on_disk(snapshot_id, metadata)
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 | 🟡 Minor

Fix malformed snapshot IDs and timestamps for i >= 10.

The current loop creates invalid snapshot IDs and timestamps when i reaches 10 or 11:

  • For i=10: snapshot_id becomes "20250102_1200010" (7 digits in time portion instead of 6)
  • For i=10: timestamp becomes "2025-01-02T12:00:010" (3 digits in seconds instead of 2)

These malformed identifiers break the expected format and could cause parsing or sorting failures in the retention policy logic.

🔎 Proposed fix
         # Create 12 snapshots manually (exceeds limit of 10)
         for i in range(12):
-            snapshot_id = f"2025010{(i % 9) + 1}_12000{i}"
+            day = (i % 28) + 1  # Valid days 01-28
+            snapshot_id = f"202501{day:02d}_120000"
             metadata = {
                 "id": snapshot_id,
-                "timestamp": f"2025-01-0{(i % 9) + 1}T12:00:0{i}",
+                "timestamp": f"2025-01-{day:02d}T12:00:{i:02d}",
                 "description": f"Snapshot {i}",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Create 12 snapshots manually (exceeds limit of 10)
for i in range(12):
snapshot_id = f"2025010{(i % 9) + 1}_12000{i}"
metadata = {
"id": snapshot_id,
"timestamp": f"2025-01-0{(i % 9) + 1}T12:00:0{i}",
"description": f"Snapshot {i}",
"packages": {"apt": [], "pip": [], "npm": []},
"system_info": {},
"file_count": 0,
"size_bytes": 0
}
self._create_mock_snapshot_on_disk(snapshot_id, metadata)
# Create 12 snapshots manually (exceeds limit of 10)
for i in range(12):
day = (i % 28) + 1 # Valid days 01-28
snapshot_id = f"202501{day:02d}_120000"
metadata = {
"id": snapshot_id,
"timestamp": f"2025-01-{day:02d}T12:00:{i:02d}",
"description": f"Snapshot {i}",
"packages": {"apt": [], "pip": [], "npm": []},
"system_info": {},
"file_count": 0,
"size_bytes": 0
}
self._create_mock_snapshot_on_disk(snapshot_id, metadata)
🤖 Prompt for AI Agents
In tests/unit/test_snapshot_manager.py around lines 246 to 258, the loop
constructs malformed snapshot IDs and timestamps once i >= 10; compute
zero-padded day and seconds instead of string-concatenating raw i: set day_str =
f"{i+1:02d}" and sec_str = f"{i:02d}", then use snapshot_id =
f"202501{day_str}_1200{sec_str}" and timestamp =
f"2025-01-{day_str}T12:00:{sec_str}" so both the date/day and seconds are always
two digits and the time portion remains exactly 6 digits, fixing parsing/sorting
issues.

@Suyashd999 Suyashd999 assigned sujay-d07 and RIVALHIDE and unassigned sujay-d07 Dec 23, 2025
Issues Fixed:
- Missing dependency resolver for package version conflicts
- demo.py crashes with AttributeError on SystemInfo object access
- snapshot_manager.py has outdated type hints (Optional[X] vs X | None)
- CLI snapshot commands have inconsistent formatting
- Missing comprehensive tests for resolver functionality
- Code quality issues (trailing whitespace, import ordering)

Solutions Implemented:
- Added DependencyResolver module with semantic version conflict resolution
- Implemented smart upgrade and conservative downgrade strategies
- Fixed demo.py to access SystemInfo attributes directly instead of dict methods
- Updated snapshot_manager.py type hints to modern Python 3.10+ syntax
- Applied PEP 8 formatting with ruff and black across all modified files
- Added 9 comprehensive resolver tests covering all conflict scenarios
- Updated 27 snapshot manager tests with proper formatting
- Fixed import ordering and removed all trailing whitespace

Technical Details:
- New module: cortex/resolver.py (226 lines)
- New tests: tests/test_resolver.py (226 lines, 9 test cases)
- Modified: cortex/cli.py (45 changes)
- Modified: cortex/demo.py (20 changes)
- Modified: cortex/snapshot_manager.py (67 changes)
- Modified: tests/unit/test_snapshot_manager.py (131 changes)

Test Results:
- Resolver tests: 9/9 passed
- Snapshot tests: 27/27 passed
- Full suite: 706 passed, 10 skipped
- Code quality: All ruff and black checks passed

Closes cortexlinux#45
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
cortex/snapshot_manager.py (2)

357-362: Add sudo prefix to npm global commands to match apt behavior.

NPM global install/uninstall operations require administrator privileges on most Linux systems. The sudo check at lines 400-408 already validates sudo availability before restore, so adding sudo to npm commands aligns with the apt behavior and past review feedback.

🔎 Proposed fix
         remove_cmds = {
             "apt": ["sudo", "apt-get", "remove", "-y"],
             "pip": [sys.executable, "-m", "pip", "uninstall", "-y"],
-            "npm": ["npm", "uninstall", "-g"],
+            "npm": ["sudo", "npm", "uninstall", "-g"],
         }
         install_cmds = {
             "apt": ["sudo", "apt-get", "install", "-y"],
             "pip": [sys.executable, "-m", "pip", "install"],
-            "npm": ["npm", "install", "-g"],
+            "npm": ["sudo", "npm", "install", "-g"],
         }

354-362: Pip commands inconsistent with detection; use sys.executable -m pip.

Package detection at line 149 uses [sys.executable, "-m", "pip", ...] but restore commands at lines 356 and 361 use the plain pip executable. This could cause packages to be restored under a different Python interpreter than the one used for detection.

🔎 Proposed fix
         remove_cmds = {
             "apt": ["sudo", "apt-get", "remove", "-y"],
-            "pip": ["pip", "uninstall", "-y"],
+            "pip": [sys.executable, "-m", "pip", "uninstall", "-y"],
             "npm": ["npm", "uninstall", "-g"],
         }
         install_cmds = {
             "apt": ["sudo", "apt-get", "install", "-y"],
-            "pip": ["pip", "install"],
+            "pip": [sys.executable, "-m", "pip", "install"],
             "npm": ["npm", "install", "-g"],
         }
🧹 Nitpick comments (3)
cortex/demo.py (1)

19-19: Minor: Redundant rounding on total_gb.

Per cortex/hardware_detection.py:86-87, total_gb already returns a value rounded to 1 decimal place. The additional round(..., 1) here is harmless but unnecessary.

🔎 Optional simplification
-    memory_gb = round(hw.memory.total_gb, 1) if hw.memory else 0
+    memory_gb = hw.memory.total_gb if hw.memory else 0
cortex/resolver.py (2)

49-69: Parsed specs are unused; strategies lack computed version recommendations.

spec_a and spec_b are parsed at lines 51-52 but only serve to validate syntax. The comment at line 71 mentions "Find the intersection or highest compatible version" but no intersection is actually computed. The returned strategies provide generic guidance without concrete version numbers.

If this is intentional as a first iteration, consider adding a # TODO comment. Otherwise, you could compute the intersection using semantic_version to provide actionable version recommendations.

🔎 Example: Add TODO or utilize specs
         # Find the intersection or highest compatible version
-        # For successful parsing, return two strategies
+        # TODO: Compute intersection of spec_a and spec_b to recommend concrete versions
+        # For now, return generic strategies
         strategies = []

114-116: Docstring in __main__ block should be a comment.

Line 115 uses a docstring inside if __name__ == "__main__":, which is valid Python but unconventional. A comment would be more idiomatic.

🔎 Proposed fix
 if __name__ == "__main__":
-    """Demonstration of the DependencyResolver."""
+    # Demonstration of the DependencyResolver
     resolver = DependencyResolver()
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 374a760 and 697965c.

📒 Files selected for processing (6)
  • cortex/cli.py
  • cortex/demo.py
  • cortex/resolver.py
  • cortex/snapshot_manager.py
  • tests/test_resolver.py
  • tests/unit/test_snapshot_manager.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/test_snapshot_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/resolver.py
  • cortex/cli.py
  • tests/test_resolver.py
  • cortex/demo.py
  • cortex/snapshot_manager.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Maintain >80% test coverage for pull requests

Files:

  • tests/test_resolver.py
🧠 Learnings (1)
📚 Learning: 2025-12-11T12:03:24.071Z
Learnt from: CR
Repo: cortexlinux/cortex PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-11T12:03:24.071Z
Learning: Applies to **/*install*.py : No silent sudo execution - require explicit user confirmation

Applied to files:

  • cortex/snapshot_manager.py
🧬 Code graph analysis (3)
tests/test_resolver.py (1)
cortex/resolver.py (2)
  • DependencyResolver (11-111)
  • resolve (16-111)
cortex/demo.py (1)
cortex/hardware_detection.py (2)
  • total_gb (87-88)
  • has_nvidia_gpu (662-664)
cortex/snapshot_manager.py (1)
cortex/cli.py (1)
  • snapshot (750-839)
🔇 Additional comments (8)
cortex/cli.py (2)

750-839: LGTM - Snapshot CLI integration is well-structured.

The snapshot command handler correctly:

  • Handles missing action gracefully with helpful error messages
  • Uses lazy import for SnapshotManager to avoid circular imports
  • Properly unpacks tuple returns from manager methods
  • Supports dry-run mode for restore operations
  • Column widths (24 chars for ID) properly accommodate microsecond-precision snapshot IDs

986-1009: LGTM - Subparser configuration is complete.

All snapshot subcommands are properly configured with required arguments:

  • create: optional description
  • list: no additional args needed
  • show, restore, delete: require snapshot_id
  • restore: includes --dry-run flag
tests/test_resolver.py (1)

10-225: LGTM - Comprehensive test coverage for DependencyResolver.

The test suite thoroughly covers:

  • Basic conflict resolution with valid semver
  • Missing required keys raising KeyError
  • Invalid semver producing error strategy
  • Default wildcard behavior when versions omitted
  • Complex and wildcard version constraints
  • Major version conflicts
  • Strategy targeting verification

Good use of setup_method for fixture initialization and clear docstrings for each test case.

cortex/snapshot_manager.py (4)

85-111: LGTM - Robust package detection with proper error handling.

The _run_package_detection method correctly:

  • Uses configurable timeout to prevent hangs
  • Catches relevant exceptions including TimeoutExpired, FileNotFoundError, and JSONDecodeError
  • Logs warnings without crashing on detection failures
  • Returns empty list as safe fallback

195-252: LGTM - Secure snapshot creation with proper permissions.

The create_snapshot method addresses previous review feedback:

  • Uses os.chmod(snapshot_path, 0o700) after mkdir to enforce permissions regardless of umask
  • Uses os.open() with explicit mode 0o600 for metadata file to avoid permission race
  • Applies retention policy after creation

382-448: LGTM - Comprehensive restore with sudo validation and timeout protection.

The restore_snapshot method properly:

  • Validates sudo permissions before attempting privileged operations (lines 400-408)
  • Uses RESTORE_TIMEOUT for subprocess calls
  • Handles TimeoutExpired and CalledProcessError with informative error messages
  • Returns commands list for both success and failure cases

450-465: LGTM - Retention policy correctly deletes oldest snapshots.

The retention policy:

  • Sorts snapshots by timestamp (oldest first)
  • Deletes only the excess over RETENTION_LIMIT
  • Logs deletions for auditability
cortex/demo.py (1)

17-28: No issues found. The has_nvidia_gpu and has_amd_gpu attributes exist on the SystemInfo object returned by detect_hardware() and are used correctly in the code.

Features:
- Added DependencyResolver for semantic version conflict resolution
- Made snapshot retention limit user-configurable (default: 10)
- Added SnapshotSettings to user preferences

Fixes:
- Fixed demo.py SystemInfo object access bug
- Updated type hints to Python 3.10+ syntax
- Applied PEP 8 formatting with ruff/black

Files Changed:
- New: cortex/resolver.py (226 lines)
- New: tests/test_resolver.py (9 tests)
- Modified: cortex/snapshot_manager.py (reads from preferences)
- Modified: cortex/user_preferences.py (added SnapshotSettings)
- Modified: cortex/demo.py, cortex/cli.py
- Modified: tests/unit/test_snapshot_manager.py

Configuration:
- cortex check-pref snapshots.retention_limit
- cortex edit-pref set snapshots.retention_limit 20

Tests: 36/36 passed (9 resolver + 27 snapshot)
Full suite: 706 passed, 10 skipped

Closes cortexlinux#45
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
cortex/snapshot_manager.py (1)

374-388: Pip commands should use sys.executable for consistency with detection.

The restore commands use plain pip (lines 376, 381), but package detection uses sys.executable -m pip (line 169). This inconsistency could cause restoration to use a different pip binary than detection, leading to mismatched package states.

Also, npm global commands may require sudo on standard Linux systems to match the sudo requirement checked at line 420.

🔎 Proposed fix
         remove_cmds = {
             "apt": ["sudo", "apt-get", "remove", "-y"],
-            "pip": ["pip", "uninstall", "-y"],
-            "npm": ["npm", "uninstall", "-g"],
+            "pip": [sys.executable, "-m", "pip", "uninstall", "-y"],
+            "npm": ["sudo", "npm", "uninstall", "-g"],
         }
         install_cmds = {
             "apt": ["sudo", "apt-get", "install", "-y"],
-            "pip": ["pip", "install"],
-            "npm": ["npm", "install", "-g"],
+            "pip": [sys.executable, "-m", "pip", "install"],
+            "npm": ["sudo", "npm", "install", "-g"],
         }
🧹 Nitpick comments (1)
cortex/snapshot_manager.py (1)

314-335: Consider validating snapshot_id to prevent path traversal.

The delete_snapshot method uses shutil.rmtree on a path derived from user-provided snapshot_id. If an attacker passes something like ../../../important_dir, this could delete arbitrary directories.

🔎 Proposed validation
     def delete_snapshot(self, snapshot_id: str) -> tuple[bool, str]:
         """
         Delete a snapshot.
         ...
         """
         try:
+            # Validate snapshot_id to prevent path traversal
+            if not snapshot_id or "/" in snapshot_id or "\\" in snapshot_id or snapshot_id.startswith("."):
+                return (False, f"Invalid snapshot ID: {snapshot_id}")
+            
             snapshot_path = self._get_snapshot_path(snapshot_id)
+            # Ensure the resolved path is within snapshots_dir
+            if not snapshot_path.resolve().is_relative_to(self.snapshots_dir.resolve()):
+                return (False, f"Invalid snapshot ID: {snapshot_id}")
+            
             if not snapshot_path.exists():
                 return (False, f"Snapshot {snapshot_id} not found")
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 697965c and dcf67b6.

📒 Files selected for processing (2)
  • cortex/snapshot_manager.py
  • cortex/user_preferences.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/user_preferences.py
  • cortex/snapshot_manager.py
🧠 Learnings (1)
📚 Learning: 2025-12-11T12:03:24.071Z
Learnt from: CR
Repo: cortexlinux/cortex PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-11T12:03:24.071Z
Learning: Applies to **/*install*.py : No silent sudo execution - require explicit user confirmation

Applied to files:

  • cortex/snapshot_manager.py
🧬 Code graph analysis (1)
cortex/snapshot_manager.py (2)
cortex/user_preferences.py (2)
  • get (202-221)
  • load (130-161)
cortex/cli.py (1)
  • snapshot (750-839)
🔇 Additional comments (8)
cortex/user_preferences.py (1)

82-92: Well-structured dataclass with sensible defaults.

The SnapshotSettings dataclass follows the established pattern in this file and provides appropriate configuration options. The inline comments adequately document each field's purpose.

cortex/snapshot_manager.py (7)

26-54: LGTM!

The SnapshotMetadata dataclass and SnapshotManager class docstring are well-structured with clear type hints and documentation of features.


86-103: LGTM!

The security enforcement and helper methods are correctly implemented. Using explicit chmod() after directory creation properly addresses the umask concern.


105-176: LGTM!

The package detection methods follow a clean pattern with proper timeout handling. The use of sys.executable -m pip ensures consistency with the running Python interpreter.


178-213: LGTM!

System info gathering has appropriate timeout protection and graceful error handling for non-critical metadata.


215-272: LGTM!

Snapshot creation is well-implemented with proper security measures (explicit chmod for directories, secure file creation using os.open with mode 0o600), and appropriate retention policy enforcement.


402-468: Solid restore implementation with appropriate safeguards.

The restore logic properly validates sudo permissions upfront (lines 420-428), provides dry-run capability, and has comprehensive error handling for timeouts and command failures. Based on learnings, sudo validation before execution is appropriate.

One consideration: the method executes all package managers sequentially. If APT restore succeeds but PIP fails, the system is left in a partial state. Consider documenting this behavior or implementing transactional semantics.


470-486: LGTM!

The retention policy correctly enforces the configured limit by removing the oldest snapshots first.

Comment on lines +75 to +84
def _load_retention_from_preferences(self) -> int:
"""Load retention limit from user preferences"""
try:
from cortex.user_preferences import PreferencesManager
prefs_manager = PreferencesManager()
prefs = prefs_manager.get_preferences()
return prefs.snapshots.retention_limit
except Exception:
# Fall back to default if preferences not available
return self.DEFAULT_RETENTION_LIMIT
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for get_preferences method in user_preferences.py
rg -n "def get_preferences" cortex/

Repository: cortexlinux/cortex

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Search for the PreferencesManager class and its methods
rg -n "class PreferencesManager" cortex/ -A 30

Repository: cortexlinux/cortex

Length of output: 2060


🏁 Script executed:

#!/bin/bash
# Search for all method definitions in PreferencesManager
rg -n "def " cortex/user_preferences.py | head -20

Repository: cortexlinux/cortex

Length of output: 722


get_preferences() method does not exist on PreferencesManager.

Line 80 calls prefs_manager.get_preferences(), but PreferencesManager exposes the preferences via the .preferences attribute or the .get() method for dot-notation access—not a get_preferences() method. This will raise an AttributeError at runtime.

🔎 Proposed fix
     def _load_retention_from_preferences(self) -> int:
         """Load retention limit from user preferences"""
         try:
             from cortex.user_preferences import PreferencesManager
             prefs_manager = PreferencesManager()
-            prefs = prefs_manager.get_preferences()
-            return prefs.snapshots.retention_limit
+            return prefs_manager.preferences.snapshots.retention_limit
         except Exception:
             # Fall back to default if preferences not available
             return self.DEFAULT_RETENTION_LIMIT

Or using the dot-notation getter:

return prefs_manager.get("snapshots.retention_limit", self.DEFAULT_RETENTION_LIMIT)
🤖 Prompt for AI Agents
In cortex/snapshot_manager.py around lines 75 to 84, the code calls
prefs_manager.get_preferences() which does not exist on PreferencesManager and
will raise AttributeError; replace that call by reading the preferences via the
correct API (either use prefs_manager.preferences to access the preferences
object and then read snapshots.retention_limit with a fallback, or use the
dot-notation getter like prefs_manager.get("snapshots.retention_limit",
self.DEFAULT_RETENTION_LIMIT)) so the retention value is retrieved using the
correct method and a default is returned when the key is missing.

auto_update: AutoUpdateSettings = field(default_factory=AutoUpdateSettings)
ai: AISettings = field(default_factory=AISettings)
packages: PackageSettings = field(default_factory=PackageSettings)
snapshots: SnapshotSettings = field(default_factory=SnapshotSettings)
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

Snapshots field not persisted in load/save operations.

The snapshots field is added to UserPreferences, but the load() method (lines 142-154) and save() method (lines 174-186) don't include handling for the snapshots key. This means user-configured snapshot preferences won't survive across sessions—they'll reset to defaults each time.

The same issue affects export_json(), import_json(), and get_all_settings().

🔎 Proposed fix for load() and save()

In load() around line 150, add:

                 packages=PackageSettings(**data.get("packages", {})),
+                snapshots=SnapshotSettings(**data.get("snapshots", {})),
                 theme=data.get("theme", "default"),

In save() around line 182, add:

             "packages": asdict(self.preferences.packages),
+            "snapshots": asdict(self.preferences.snapshots),
             "theme": self.preferences.theme,

Apply similar changes to export_json(), import_json(), and get_all_settings().

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In cortex/user_preferences.py around line 103, the new snapshots:
SnapshotSettings field is not persisted because load(), save(), export_json(),
import_json(), and get_all_settings() do not handle the "snapshots" key; update
each of those methods to read/write the snapshots state. Specifically, in load()
parse the snapshots dict (if present) and assign UserPreferences.snapshots =
SnapshotSettings.from_dict(...) or equivalent, in save() include snapshots:
self.snapshots.to_dict() in the serialized dict, in export_json() include
snapshots in the exported payload, in import_json() accept and deserialize
snapshots into SnapshotSettings, and in get_all_settings() return the snapshots
representation alongside other settings so user snapshot preferences persist
across sessions.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
cortex/snapshot_manager.py (2)

85-94: get_preferences() method still doesn't exist on PreferencesManager.

Line 90 calls prefs_manager.get_preferences(), but PreferencesManager exposes preferences via the .preferences attribute, not a get_preferences() method. This will raise an AttributeError at runtime.

🔎 Proposed fix
     def _load_retention_from_preferences(self) -> int:
         """Load retention limit from user preferences"""
         try:
             from cortex.user_preferences import PreferencesManager
             prefs_manager = PreferencesManager()
-            prefs = prefs_manager.get_preferences()
-            return prefs.snapshots.retention_limit
+            return prefs_manager.preferences.snapshots.retention_limit
         except Exception:
             # Fall back to default if preferences not available
             return self.DEFAULT_RETENTION_LIMIT

494-508: Fix pip and npm command inconsistencies.

Two issues remain unresolved from previous reviews:

  1. Pip commands don't use sys.executable: Lines 496 and 501 use plain "pip" but detection at line 210 uses sys.executable -m pip, which can cause restoration to use a different pip binary than detection.

  2. NPM commands lack sudo prefix: Lines 497 and 502 don't include "sudo" for global operations, despite the code checking sudo availability (line 543) and apt commands including sudo. Global npm operations typically require administrator privileges.

🔎 Proposed fix
         remove_cmds = {
             "apt": ["sudo", "apt-get", "remove", "-y"],
-            "pip": ["pip", "uninstall", "-y"],
-            "npm": ["npm", "uninstall", "-g"],
+            "pip": [sys.executable, "-m", "pip", "uninstall", "-y"],
+            "npm": ["sudo", "npm", "uninstall", "-g"],
         }
         install_cmds = {
             "apt": ["sudo", "apt-get", "install", "-y"],
-            "pip": ["pip", "install"],
-            "npm": ["npm", "install", "-g"],
+            "pip": [sys.executable, "-m", "pip", "install"],
+            "npm": ["sudo", "npm", "install", "-g"],
         }
tests/unit/test_snapshot_manager.py (1)

250-262: Fix malformed snapshot IDs and timestamps for i >= 10.

Lines 252 and 255 still create invalid formats when i >= 10:

  • For i=10: snapshot_id = "20250102_1200010" (7 digits in time portion)
  • For i=10: timestamp = "2025-01-02T12:00:010" (3 digits in seconds)

The seconds portion needs zero-padding to maintain exactly 2 digits.

🔎 Proposed fix
         # Create 12 snapshots manually (exceeds limit of 10)
         for i in range(12):
-            snapshot_id = f"2025010{(i % 9) + 1}_12000{i}"
+            day = (i % 28) + 1
+            snapshot_id = f"202501{day:02d}_1200{i:02d}"
             metadata = {
                 "id": snapshot_id,
-                "timestamp": f"2025-01-0{(i % 9) + 1}T12:00:0{i}",
+                "timestamp": f"2025-01-{day:02d}T12:00:{i:02d}",
                 "description": f"Snapshot {i}",
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dcf67b6 and 81506a3.

📒 Files selected for processing (5)
  • cortex/snapshot_manager.py
  • requirements.txt
  • test_concurrent_simple.py
  • test_multiprocess_race.py
  • tests/unit/test_snapshot_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:

  • test_concurrent_simple.py
  • test_multiprocess_race.py
  • tests/unit/test_snapshot_manager.py
  • cortex/snapshot_manager.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Maintain >80% test coverage for pull requests

Files:

  • tests/unit/test_snapshot_manager.py
🧠 Learnings (2)
📚 Learning: 2025-12-11T12:03:24.071Z
Learnt from: CR
Repo: cortexlinux/cortex PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-11T12:03:24.071Z
Learning: Applies to tests/**/*.py : Maintain >80% test coverage for pull requests

Applied to files:

  • tests/unit/test_snapshot_manager.py
📚 Learning: 2025-12-11T12:03:24.071Z
Learnt from: CR
Repo: cortexlinux/cortex PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-11T12:03:24.071Z
Learning: Applies to **/*install*.py : No silent sudo execution - require explicit user confirmation

Applied to files:

  • cortex/snapshot_manager.py
🧬 Code graph analysis (2)
test_concurrent_simple.py (1)
cortex/sandbox/sandbox_executor.py (1)
  • success (74-76)
test_multiprocess_race.py (1)
cortex/snapshot_manager.py (1)
  • create_snapshot (256-352)
🔇 Additional comments (13)
test_concurrent_simple.py (2)

8-66: LGTM! Well-structured concurrency test.

The test properly spawns concurrent snapshot creation processes, aggregates results, and checks for JSON corruption patterns. The 60-second timeout per process is reasonable for snapshot operations.


68-76: LGTM! Proper exception handling and exit codes.

The main block correctly handles exceptions, prints errors with traceback, and returns appropriate exit codes.

cortex/snapshot_manager.py (4)

117-144: LGTM! Robust JSON reading with retry logic.

The retry mechanism with brief delays effectively handles transient JSON corruption that can occur during concurrent writes. Error handling and logging are appropriate.


256-352: LGTM! Well-designed concurrent snapshot creation.

The implementation effectively handles multi-process concurrency through:

  • UUID-based IDs to prevent collisions
  • File locking only for the critical metadata write section
  • Atomic writes via temp file + rename
  • Proper cleanup on failure
  • Secure permissions (700/600)

522-588: LGTM! Comprehensive restore logic with proper error handling.

The restore implementation includes:

  • Sudo permission check before attempting restore
  • Timeout protection (300s) for package operations
  • Proper exception handling for TimeoutExpired and CalledProcessError
  • Dry-run support for preview
  • Detailed error messages

The command execution is correct once the command templates in _restore_package_manager() are fixed.


590-610: LGTM! Correct retention policy implementation.

The retention logic properly:

  • Sorts snapshots by timestamp (oldest first)
  • Deletes excess snapshots beyond the limit
  • Uses unsafe methods correctly (already within locked context)
  • Handles errors gracefully with logging
test_multiprocess_race.py (2)

9-30: LGTM! Clean subprocess wrapper with proper error handling.

The helper function correctly:

  • Executes snapshot creation in a separate process
  • Applies a reasonable 60-second timeout
  • Returns structured result with all relevant information
  • Handles exceptions gracefully

32-87: LGTM! Effective multi-process race condition test.

The test properly:

  • Uses ProcessPoolExecutor for true parallel process execution
  • Aggregates results as futures complete
  • Detects JSON corruption patterns
  • Provides detailed summary with timing and success/failure counts
  • Returns appropriate exit codes
tests/unit/test_snapshot_manager.py (5)

1-109: LGTM! Well-structured test foundation.

The test setup includes:

  • Proper temporary directory management
  • Reusable helper methods for creating mock snapshots and metadata
  • Clean separation of concerns
  • Appropriate use of mocks

110-240: LGTM! Comprehensive CRUD and detection tests.

The tests cover:

  • Package detection for all three managers (APT, PIP, NPM)
  • Snapshot creation with proper verification
  • Listing, retrieval, and deletion operations
  • Edge cases like empty lists and non-existent snapshots

271-366: LGTM! Thorough restore functionality tests.

The tests validate:

  • Dry-run mode with command preview
  • Snapshot not found error handling
  • Snapshot ID format requirements (date, time, UUID parts)
  • Live restore execution with subprocess mocking
  • Proper command execution verification

368-533: LGTM! Excellent error handling test coverage.

The tests comprehensively cover:

  • System info detection failures
  • CalledProcessError with and without stderr
  • Sudo permission check failures and exceptions
  • Package detection timeouts, FileNotFoundError, and JSON decode errors
  • Exception handling throughout the snapshot lifecycle

This contributes significantly to the >80% coverage requirement.


535-587: LGTM! Complete error recovery test coverage.

The final tests validate:

  • Snapshot creation exception handling with proper cleanup
  • Restore timeout handling with informative error messages
  • Timeout duration included in error message

The test suite provides comprehensive coverage of both happy paths and error scenarios, meeting the >80% coverage requirement.

cortex -h           # Standard argparse help
cortex install -h   # Install command details
cortex snapshot -h  # Snapshot subcommands
cortex stack -h     # Stack management details
@Suyashd999 Suyashd999 self-requested a review December 24, 2025 12:26
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
cortex/cli.py (1)

763-770: Minor: Redundant fallback for description.

At line 764, args.description or "" is technically redundant since the argparse definition at line 1129 already sets default="". However, this is harmless defensive coding that protects against potential future changes to the argument definition.

🔎 Optional simplification
         if args.snapshot_action == "create":
-            success, snapshot_id, message = manager.create_snapshot(args.description or "")
+            success, snapshot_id, message = manager.create_snapshot(args.description)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81506a3 and 1a8094d.

📒 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
🔇 Additional comments (4)
cortex/cli.py (4)

750-839: LGTM - Snapshot method implementation is well-structured.

The method correctly handles all snapshot actions with appropriate error handling and return values. The lazy import of SnapshotManager is acceptable for reducing startup time. Previous feedback on ID column width has been addressed (24 chars at line 779).

Minor observations:

  • Line 839's return 1 serves as a defensive fallback for any unhandled action, which is good practice.
  • Error messages are consistent and informative.

842-952: LGTM - Rich help display is comprehensive and well-organized.

The updated help includes snapshot commands in the Core Commands panel with clear action descriptions. The three-panel structure (Core, Utility, Configuration) improves discoverability, and the examples section now demonstrates snapshot usage.


1115-1161: LGTM - Snapshot subparsers are well-defined.

The snapshot command hierarchy is properly structured with:

  • create with optional description
  • list for viewing all snapshots
  • show requiring snapshot_id
  • restore with --dry-run support
  • delete requiring snapshot_id

The descriptions are informative and help text follows conventions used elsewhere in the CLI.


1206-1207: LGTM - Snapshot command routing follows established patterns.

The routing is consistent with how other subcommand-based commands (stack, notify) are handled.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
requirements.txt (1)

9-9: Remove duplicate PyYAML dependency with conflicting version specifiers.

The file contains two separate PyYAML entries:

  • Line 9: PyYAML>=6.0.0
  • Line 22: PyYAML==6.0.3

This creates ambiguity in dependency resolution and violates the DRY principle. The pinned version (==6.0.3) should be the single source of truth. Remove line 9 to keep only the explicit version constraint on line 22.

🔎 Proposed fix: Remove duplicate PyYAML entry
 # Configuration
-PyYAML>=6.0.0
 
 # Environment variable loading from .env files

Also applies to: 22-22

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a8094d and 6ef6f3c.

📒 Files selected for processing (1)
  • requirements.txt
🔇 Additional comments (1)
requirements.txt (1)

17-18: ✅ Filelock CVE-2025-68146 is properly patched.

The upgrade to filelock>=3.20.1 correctly addresses the critical TOCTOU (Time-of-Check-Time-of-Use) vulnerability reported in the previous review. The version constraint is appropriate.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
MULTIPROCESS_RACE_FIX.md (1)

7-12: Add language identifier to code block for markdown compliance.

The fenced code block is missing a language identifier. Add bash to the opening code fence to comply with markdown linting standards.

🔎 Proposed fix
-```
+```bash
 for i in {1..5}; do
   cortex snapshot create "Race test $i" &
 done
 wait
-```
+```bash
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ef6f3c and 790e88d.

📒 Files selected for processing (3)
  • COMMIT_MSG.txt
  • MULTIPROCESS_RACE_FIX.md
  • cortex/demo.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • cortex/demo.py
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
MULTIPROCESS_RACE_FIX.md

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

(MD040, fenced-code-language)

🔇 Additional comments (2)
COMMIT_MSG.txt (1)

1-35: Good structure, but verify that implementation files are included in review scope.

The commit message is well-organized and clearly documents the problem, root cause, and solution. However, since the actual implementation file (cortex/snapshot_manager.py) and test files referenced here are not included in the files under review, the complete validation of these changes cannot be performed.

Please confirm whether cortex/snapshot_manager.py, test_concurrent_simple.py, and test_multiprocess_race.py should be included in this review. The commit message references concrete changes to these files that need to be validated against the actual code.

MULTIPROCESS_RACE_FIX.md (1)

1-202: Excellent technical documentation with clear problem-to-solution narrative.

The documentation comprehensively explains the multi-process race condition, root cause analysis, and the optimized solution with file-based locking. The performance metrics (50x faster lock duration, unlimited concurrent capacity) and protection layers table provide strong evidence of the fix's effectiveness.

Key strengths:

  • Clear before/after comparison with concrete examples
  • Realistic performance improvements documented
  • Verification steps are comprehensive and actionable
  • Edge cases properly identified

However, the actual implementation in cortex/snapshot_manager.py is not provided, so the specific integration of FileLock, _safe_read_json(), and _file_lock_path() cannot be validated against this documentation.

Please include cortex/snapshot_manager.py in the review so that:

  1. The actual FileLock usage matches the described timeout and retry logic
  2. The _safe_read_json() implementation handles the 3-attempt retry with 100ms delays as documented
  3. The lock placement truly minimizes critical section time (aiming for ~0.2s as claimed)
  4. All create/list/delete operations properly wrap JSON operations with FileLock contexts

@sonarqubecloud
Copy link

@Suyashd999 Suyashd999 marked this pull request as draft December 24, 2025 19:59
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.

System Snapshot and Rollback Points

3 participants