Skip to content

Conversation

@echobt
Copy link
Contributor

@echobt echobt commented Feb 2, 2026

Summary

This PR addresses multiple open issues from the PlatformNetwork/bounty-challenge repository affecting cortex-cli.

Note: This PR was migrated from CortexLM/cortex-cli#564 to the cortex monorepo.

Issues Fixed

Exit Code Issues

  • #3896: cortex debug file now returns non-zero exit code when file does not exist
  • #3891: whoami command now returns error when checking login status fails
  • #3843: upgrade command now returns error for invalid channel (stable/beta/nightly)
  • #3820: config get/unset now return error when key not found

Conflicting Flags Validation

  • #3885: exec command validates that --enabled-tools and --disabled-tools cannot both be specified
  • #3857: mcp logout validates that --name and --all cannot both be specified
  • #3856: agent list validates that --primary and --subagents cannot both be specified
  • #3855: plugin list validates that --enabled and --disabled cannot both be specified
  • #3819: mcp add validates that --url and --sse cannot both be specified
  • #3814: login validates that only one auth method can be specified

JSON Output Issues

  • #3879: debug config --diff --json now outputs pure JSON
  • #3838: dag run --strategy dry-run respects --format flag
  • #3826: compact run --dry-run --json outputs JSON
  • #3738: dag list --format json outputs proper JSON
  • #3735: alias show --json outputs JSON on errors
  • #3723: plugin show --json outputs JSON on errors
  • #947: debug wait --json outputs JSON on errors
  • #948: mcp get --json outputs JSON on errors
  • #952: run --format json outputs JSON on auth errors

Security Fix

  • #3851: Fixed allows_risk() to pass actual command to is_read_only_command() instead of risk level

Input Validation Issues

  • #3815: DAG validation now rejects duplicate task IDs
  • #3722: models list --sort validates sort values (name, provider, context, created, id)
  • #3716: dag run --jobs 0 now errors instead of hanging
  • #3700: plugin install "" now returns proper error for empty name
  • #3696: lock add now validates session ID format (UUID or 8-char prefix)

User Experience Fixes

  • #3646: resume command now accepts 'last' as SESSION_ID per help text
  • #3651: upgrade --changelog now fetches raw content instead of HTML
  • #3150: debug config now correctly detects config.json files
  • #3682: uninstall --backup now creates backup after user confirmation
  • #3678: Fixed help text to reference correct GitHub repository URL
  • #3792: Removed emoji from pr command output (replaced with text alternatives)

Changes

  • Multiple files modified across cortex-cli
  • All error paths return proper exit codes using bail!() macro
  • All commands with --json flag output valid JSON even on errors
  • Mutually exclusive flags produce validation errors when both specified
  • Security bug fixed in autonomy level checking

Testing

  • cargo check -p cortex-cli passes
  • All changes follow existing codebase patterns and conventions

Note

This PR is for review only - DO NOT MERGE until reviewed.

- Add validation for mutually exclusive flags across commands:
  - agent list: --primary vs --subagents
  - login: --token, --sso, --device-auth, --with-api-key
  - exec: --enabled-tools vs --disabled-tools
  - plugin list: --enabled vs --disabled
  - mcp add: --url vs --sse
  - mcp logout: --name vs --all

- Improve error handling with JSON support:
  - alias show: JSON error output
  - mcp get: JSON error output
  - plugin show: JSON error output
  - debug wait: JSON error output
  - run cmd: auth error JSON output

- Fix bug in autonomy.rs: is_read_only_command now uses command string
- Add session ID validation in lock_cmd
- Support 'last' as SESSION_ID in resume command
- Add duplicate task ID check in DAG helpers
- Add --jobs validation in DAG execute
- Add sort value validation in models list
- Add empty plugin name validation

- Update help text URLs to point to correct repo
- Replace emojis with text in PR checkout
- Move confirmation before backup in uninstall
- Convert GitHub URLs to raw URLs in upgrade changelog
- Support config.json in debug config command
- Add dry_run JSON output in compact and DAG commands

- Make run_whoami return Result<()> for proper error handling
- Change config get/unset to bail! on errors

Issues: #3646, #3651, #3682, #3696, #3700, #3716, #3722, #3815
- Fix clippy::bind_instead_of_map in debug_cmd/handlers/config.rs
  by replacing and_then() with map() since closure always returns Some()

- Fix duplicate error output in alias_cmd.rs and plugin_cmd.rs
  by using std::process::exit(1) after JSON error output instead of bail!()
  to avoid duplicating error message to stderr

- Replace raw .unwrap() calls with safer patterns in dag_cmd/helpers.rs
  using if-let guards and Option::and_then() instead of panicking

- Improve is_read_only_command() in exec_cmd/autonomy.rs to use
  exact word matching instead of prefix matching to prevent false
  positives (e.g., 'catfile' no longer matches 'cat')
@echobt echobt force-pushed the fix/cortex-cli-issue-batch-1 branch from a73f5fc to 239e0af Compare February 2, 2026 19:47
@echobt
Copy link
Contributor Author

echobt commented Feb 2, 2026

@greptile

@greptile-apps
Copy link

greptile-apps bot commented Feb 2, 2026

Greptile Overview

Greptile Summary

This PR addresses 26+ bug fixes across cortex-cli, focusing on proper exit codes, JSON output consistency, input validation, and a critical security fix in autonomy level checking.

Key Changes:

  • Security Fix: Fixed allows_risk() in exec_cmd/autonomy.rs to pass the actual command string to is_read_only_command() instead of the risk level string, preventing incorrect permission checks
  • Exit Codes: Commands now properly return non-zero exit codes on errors using bail!() macro (whoami, config get/unset, debug file, upgrade with invalid channel)
  • JSON Output: All commands with --json flags now output pure JSON even on errors (debug config --diff, dag run --dry-run, compact run --dry-run, alias show, plugin show, debug wait, mcp get, run)
  • Conflicting Flags: Added validation to prevent mutually exclusive flags from being used together (exec, mcp logout/add, agent list, plugin list, login)
  • Input Validation: Added proper validation for DAG duplicate task IDs, models list --sort values, dag run --jobs 0, plugin install empty names, lock add session ID format
  • UX Improvements: Resume command accepts 'last' as session ID, upgrade --changelog fetches raw content, uninstall --backup creates backup after confirmation, updated GitHub repo URL, removed emoji from pr command

Issues Found:

  • Two files use std::process::exit(1) instead of bail!() after printing JSON errors (plugin_cmd.rs, alias_cmd.rs), which bypasses proper error handling
  • URL conversion logic in upgrade_cmd.rs has a bug where it adds /raw/ then immediately removes it

Confidence Score: 4/5

  • This PR is mostly safe to merge with minor fixes needed for error handling consistency
  • The PR fixes critical security bug and addresses numerous legitimate issues. However, two files have incorrect error handling patterns (using std::process::exit() instead of bail!()), and one has a URL conversion logic bug that needs correction before merge
  • Pay close attention to src/cortex-cli/src/plugin_cmd.rs, src/cortex-cli/src/alias_cmd.rs, and src/cortex-cli/src/upgrade_cmd.rs for the identified issues

Important Files Changed

Filename Overview
src/cortex-cli/src/exec_cmd/autonomy.rs Fixed critical security bug where risk level string was passed instead of command to is_read_only_command(), added comprehensive tests
src/cortex-cli/src/exec_cmd/runner.rs Added validation for conflicting --enabled-tools and --disabled-tools flags, properly passes command string to allows_risk()
src/cortex-cli/src/cli/handlers.rs Added validation for conflicting login auth methods, fixed whoami error handling, added support for 'last' as session ID, fixed config get/unset error codes
src/cortex-cli/src/dag_cmd/helpers.rs Added duplicate task ID validation using HashSet, improved null-safety with modern Rust patterns (is_some_and, Some(x) else)
src/cortex-cli/src/debug_cmd/handlers/config.rs Fixed detection of both config.toml and config.json, ensured --diff --json outputs pure JSON without mixed text
src/cortex-cli/src/plugin_cmd.rs Added validation for conflicting --enabled/--disabled flags, empty plugin names, and JSON error output for plugin show; uses std::process::exit(1) instead of bail!()
src/cortex-cli/src/alias_cmd.rs Added JSON error output for alias show when alias doesn't exist; uses std::process::exit(1) instead of bail!()
src/cortex-cli/src/upgrade_cmd.rs Fixed invalid channel handling to return error code, implemented GitHub URL to raw content conversion for --changelog

Sequence Diagram

sequenceDiagram
    participant User
    participant ExecCli
    participant EventLoop
    participant AutonomyLevel
    participant ReadOnlyCheck

    User->>ExecCli: cortex exec --auto read-only
    ExecCli->>ExecCli: validate conflicting tool flags
    ExecCli->>EventLoop: start execution loop
    EventLoop->>EventLoop: receive ExecApprovalRequest
    Note over EventLoop: Extract command and risk level
    EventLoop->>AutonomyLevel: allows_risk(risk_level, command_str)
    AutonomyLevel->>ReadOnlyCheck: is_read_only_command(command_str)
    Note over ReadOnlyCheck: FIXED: Now receives actual command<br/>instead of risk level string
    ReadOnlyCheck->>ReadOnlyCheck: Extract first word from command
    ReadOnlyCheck->>ReadOnlyCheck: Check against whitelist
    ReadOnlyCheck-->>AutonomyLevel: true/false
    AutonomyLevel-->>EventLoop: permission granted/denied
    alt Permission Denied
        EventLoop->>User: Error: Command not allowed
    else Permission Granted
        EventLoop->>EventLoop: auto-approve command
        EventLoop->>User: Execute command
    end
Loading

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

8 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@echobt echobt closed this Feb 3, 2026
@echobt echobt reopened this Feb 3, 2026
…lidation

Address Greptile review feedback by actually calling the allows_risk() method
in the exec command approval flow. Previously, the security fix only added the
method but did not integrate it into the command execution path.

Changes:
- Replace simple AutonomyLevel::ReadOnly check with allows_risk(risk, command)
- Extract risk_level from sandbox_assessment if available
- Pass actual command string to validate read-only commands properly
- Provide clearer error messages including risk level and autonomy mode
@echobt echobt force-pushed the fix/cortex-cli-issue-batch-1 branch from 6d3b2f6 to d3dea9c Compare February 3, 2026 14:37
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

8 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +407 to +413
if args.json {
let error = serde_json::json!({
"error": format!("Plugin '{}' is not installed", args.name)
});
println!("{}", serde_json::to_string_pretty(&error)?);
// Exit with error code but don't duplicate error message via bail!()
std::process::exit(1);
Copy link

Choose a reason for hiding this comment

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

using std::process::exit(1) here bypasses proper error handling and resource cleanup. The mcp_cmd/handlers.rs:133 uses bail!() after printing JSON, which is the correct pattern

Suggested change
if args.json {
let error = serde_json::json!({
"error": format!("Plugin '{}' is not installed", args.name)
});
println!("{}", serde_json::to_string_pretty(&error)?);
// Exit with error code but don't duplicate error message via bail!()
std::process::exit(1);
if args.json {
let error = serde_json::json!({
"error": format!("Plugin '{}' is not installed", args.name)
});
println!("{}", serde_json::to_string_pretty(&error)?);
}
bail!("Plugin '{}' is not installed.", args.name);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cortex-cli/src/plugin_cmd.rs
Line: 407:413

Comment:
using `std::process::exit(1)` here bypasses proper error handling and resource cleanup. The `mcp_cmd/handlers.rs:133` uses `bail!()` after printing JSON, which is the correct pattern

```suggestion
        if args.json {
            let error = serde_json::json!({
                "error": format!("Plugin '{}' is not installed", args.name)
            });
            println!("{}", serde_json::to_string_pretty(&error)?);
        }
        bail!("Plugin '{}' is not installed.", args.name);
```

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

Comment on lines +247 to +253
if args.json {
let error = serde_json::json!({
"error": format!("Alias '{}' does not exist", args.name)
});
println!("{}", serde_json::to_string_pretty(&error)?);
// Exit with error code but don't duplicate error message via bail!()
std::process::exit(1);
Copy link

Choose a reason for hiding this comment

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

using std::process::exit(1) here bypasses proper error handling and resource cleanup. The mcp_cmd/handlers.rs:133 uses bail!() after printing JSON, which is the correct pattern

Suggested change
if args.json {
let error = serde_json::json!({
"error": format!("Alias '{}' does not exist", args.name)
});
println!("{}", serde_json::to_string_pretty(&error)?);
// Exit with error code but don't duplicate error message via bail!()
std::process::exit(1);
if args.json {
let error = serde_json::json!({
"error": format!("Alias '{}' does not exist", args.name)
});
println!("{}", serde_json::to_string_pretty(&error)?);
}
bail!("Alias '{}' does not exist.", args.name);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cortex-cli/src/alias_cmd.rs
Line: 247:253

Comment:
using `std::process::exit(1)` here bypasses proper error handling and resource cleanup. The `mcp_cmd/handlers.rs:133` uses `bail!()` after printing JSON, which is the correct pattern

```suggestion
            if args.json {
                let error = serde_json::json!({
                    "error": format!("Alias '{}' does not exist", args.name)
                });
                println!("{}", serde_json::to_string_pretty(&error)?);
            }
            bail!("Alias '{}' does not exist.", args.name);
```

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

Comment on lines +352 to +354
return format!("{}/raw/main/CHANGELOG.md", repo_part)
.replace("github.com", "raw.githubusercontent.com")
.replace("/raw/", "/");
Copy link

Choose a reason for hiding this comment

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

the /raw/ replacement on line 354 removes the /raw/ that was just added on line 352, resulting in an incorrect URL format. The correct raw URL format should be raw.githubusercontent.com/user/repo/main/CHANGELOG.md (without /raw/)

Suggested change
return format!("{}/raw/main/CHANGELOG.md", repo_part)
.replace("github.com", "raw.githubusercontent.com")
.replace("/raw/", "/");
return format!("{}/main/CHANGELOG.md", repo_part)
.replace("github.com", "raw.githubusercontent.com");
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cortex-cli/src/upgrade_cmd.rs
Line: 352:354

Comment:
the `/raw/` replacement on line 354 removes the `/raw/` that was just added on line 352, resulting in an incorrect URL format. The correct raw URL format should be `raw.githubusercontent.com/user/repo/main/CHANGELOG.md` (without `/raw/`)

```suggestion
            return format!("{}/main/CHANGELOG.md", repo_part)
                .replace("github.com", "raw.githubusercontent.com");
```

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

@echobt echobt merged commit a31b9e1 into main Feb 3, 2026
15 checks passed
@echobt echobt deleted the fix/cortex-cli-issue-batch-1 branch February 3, 2026 14:56
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.

1 participant