Skip to content

feat(lease): add --all flag, default to creating all leases#337

Merged
jdx merged 4 commits intomainfrom
feat/lease-create-all
Mar 8, 2026
Merged

feat(lease): add --all flag, default to creating all leases#337
jdx merged 4 commits intomainfrom
feat/lease-create-all

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Mar 8, 2026

Summary

  • fnox lease create --all creates leases for all configured backends sequentially
  • fnox lease create (no backend name) defaults to --all behavior
  • fnox lease (no subcommand) defaults to lease create --all
  • Secrets are resolved once upfront and shared across all backends
  • Partial failures are reported per-backend but don't stop others; fails only if all backends fail

Test plan

  • All 16 lease bats tests pass
  • All cargo tests pass
  • Manual: configure multiple lease backends and run fnox lease to verify all are created

🤖 Generated with Claude Code


Note

Medium Risk
Changes core lease-creation CLI semantics and introduces multi-backend execution/error aggregation, which could affect scripts and failure handling across backends.

Overview
Updates the lease CLI so fnox lease with no subcommand now defaults to creating leases for all configured backends, and fnox lease create accepts an optional [BACKEND_NAME] plus a new -a/--all flag (mutually exclusive with a backend name).

Refactors lease creation to resolve secrets once up-front and then create leases sequentially across backends, collecting and reporting per-backend failures before returning a combined error; output formatting is tweaked (success prefix, indented/masked creds, JSON now includes backend). Docs/usage specs and bats tests are updated to match the new CLI behavior/output.

Written by Cursor Bugbot for commit a8fd891. This will update automatically on new commits. Configure here.

- `fnox lease create --all` creates leases for all configured backends
- `fnox lease create` (no backend name) defaults to --all behavior
- `fnox lease` (no subcommand) defaults to `lease create --all`
- Secrets are resolved once upfront and shared across all backends
- Partial failures are reported but don't stop other backends

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the fnox lease command by introducing a more flexible and user-friendly approach to creating ephemeral credentials. It allows users to create leases for all configured backends with a single command, either explicitly via a new --all flag or implicitly by omitting a backend name or subcommand. This change simplifies the workflow for managing multiple lease configurations, improves performance by resolving secrets once, and provides more resilient error reporting for batch operations.

Highlights

  • Enhanced Lease Creation: The fnox lease create command now supports an --all flag to create leases for all configured backends sequentially. If no backend name is specified, it defaults to --all behavior.
  • Simplified Default Behavior: Running fnox lease without any subcommand now defaults to lease create --all, streamlining the process of creating all necessary leases.
  • Optimized Secret Resolution: Secrets are now resolved once upfront and shared across all backends, improving efficiency when creating multiple leases.
  • Robust Error Handling: Partial failures during multi-lease creation are reported per-backend, allowing other leases to proceed. The command only fails if all backends encounter an error.
  • Code Refactoring: The lease creation logic has been refactored into run_all and create_single methods for better organization and reusability, and credential masking logic was extracted into a dedicated function.
Changelog
  • src/commands/lease.rs
    • Modified LeaseCommand to accept an optional subcommand, enabling default behavior when no subcommand is provided.
    • Updated LeaseCreateCommand to include an optional backend_name and a new all boolean flag.
    • Implemented default behavior for fnox lease (no subcommand) to create all leases.
    • Refactored LeaseCreateCommand::run to delegate to run_all for multiple leases or create_single for a specific backend.
    • Introduced run_all function to iterate through and create leases for all configured backends, handling individual failures gracefully.
    • Created create_single function to encapsulate the logic for creating a single lease.
    • Adjusted _temp_env_guard usage to align with the new secret resolution flow.
    • Enhanced shell output for lease creation to include a success indicator and the backend name.
    • Added the backend name to the JSON output for lease creation.
    • Extracted credential masking logic into a new mask_credential helper function.
  • test/lease_command.bats
    • Updated a test assertion to be more general for lease creation output, matching 'created' instead of 'Lease created'.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 8, 2026

Greptile Summary

This PR adds --all / -a support to fnox lease create, makes BACKEND_NAME optional (defaulting to all-backends behavior), and lets fnox lease with no subcommand behave as fnox lease create --all. Secrets are resolved once and shared across all backends, with per-backend error messages printed and an aggregated error returned if any fail.

Key changes:

  • LeaseCreateCommand.backend_name changed from String to Option<String>; --all flag added with conflicts_with = "backend_name"
  • LeaseCommand.subcommand made Option; None arm constructs a default LeaseCreateCommand and delegates to run()
  • create_single() extracted from the old run() body; run_all() iterates all configured backends, collects errors, and returns aggregated failure
  • mask_credential() helper extracted; shell output reformatted to show backend name and checkmark; backend key added to JSON output
  • subcommand_required removed from fnox lease in the KDL schema and generated docs

Issues found:

  • When --format json is used with multiple backends, each create_single() call independently println!s its own JSON object, producing concatenated top-level JSON objects that are not valid JSON. This will break any downstream tooling that parses the output.
  • The PR description states the command "fails only if all backends fail", but the implementation returns Err whenever errors is non-empty (i.e., when any backend fails). The description and the code are misaligned; one of them needs to be corrected.

Confidence Score: 3/5

  • Mostly safe to merge for interactive use, but the invalid JSON output for multi-backend runs is a functional regression for any tooling that consumes --format json.
  • The shell output path and single-backend path are unchanged in behavior and well-tested. However, the JSON multi-backend output is broken (multiple top-level JSON objects instead of an array), which is a silent regression. The error-aggregation semantics also contradict the PR description, creating ambiguity about intended behavior. These two issues prevent a confident merge for automation/scripting use-cases.
  • src/commands/lease.rs — specifically the OutputFormat::Json arm in create_single() and the errors.is_empty() aggregation logic in run_all().

Important Files Changed

Filename Overview
src/commands/lease.rs Core logic for lease creation refactored to support --all flag and optional backend name; two notable issues: JSON output is invalid for multi-backend runs, and error-aggregation behavior contradicts the PR description.
fnox.usage.kdl CLI schema updated: BACKEND_NAME made optional, -a/--all flag added to lease create, subcommand_required removed from lease — all consistent with the implementation.
docs/cli/commands.json Generated CLI documentation updated to reflect the new optional BACKEND_NAME argument, the new --all flag, and the removal of subcommand_required from lease.
test/lease_command.bats Assertion loosened from "Lease created" to "created" to match the new output format; no new tests for --all / multi-backend scenarios.
test/aws_sts.bats Same minor assertion update as lease_command.bats to match the new output string.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["fnox lease [create]"] --> B{subcommand?}
    B -- "None (fnox lease)" --> C["Construct LeaseCreateCommand\nbackend_name=None, all=true"]
    B -- "Create(cmd)" --> D["cmd.run()"]
    C --> D

    D --> E["Resolve secrets once\n(shared across all backends)"]
    E --> F{create_all?}

    F -- "all=true OR\nbackend_name=None" --> G{leases empty?}
    G -- "yes" --> H["Err: no backends configured"]
    G -- "no" --> I["run_all()"]

    F -- "backend_name=Some(n)" --> J["look up backend_name in config"]
    J -- "not found" --> K["Err: backend not found"]
    J -- "found" --> L["create_single()"]

    I --> M["loop over all backends"]
    M --> L
    L --> N{success?}
    N -- "Ok" --> O["output result\n(shell / json / env)"]
    N -- "Err" --> P["eprintln error\npush to errors vec"]
    O --> Q{more backends?}
    P --> Q
    Q -- "yes" --> M
    Q -- "no" --> R{errors empty?}
    R -- "yes" --> S["Ok(())"]
    R -- "no" --> T["Err: aggregated failures"]
Loading

Comments Outside Diff (2)

  1. src/commands/lease.rs, line 295-319 (link)

    JSON output produces invalid JSON for multiple backends

    When --all (or the default no-backend path) is used with --format json and there are multiple configured backends, create_single() is called once per backend, and each invocation independently calls println! with its own serde_json::to_string_pretty(&output) block. The result is multiple top-level JSON objects concatenated together, which is not valid JSON:

    {
      "backend": "backend1",
      ...
    }
    {
      "backend": "backend2",
      ...
    }
    

    Any downstream tooling that parses this with a strict JSON parser (e.g. jq, python -m json.tool, or a CI script) will fail with a parse error after the first object.

    A fix would be to collect all per-backend results in run_all and emit a single JSON array, or to emit one JSON object per line (NDJSON). The same concern applies to OutputFormat::Env when backends share credential key names — later backends will silently overwrite earlier exports in the output.

  2. src/commands/lease.rs, line 192-199 (link)

    PR description contradicts actual error-aggregation behavior

    The PR description states: "fails only if all backends fail". However, the implementation returns an error (Err) whenever errors is non-empty — i.e., if any backend fails, the overall command exits non-zero:

    if !errors.is_empty() {
        return Err(FnoxError::Config(format!(
            "{} of {} lease backends failed:\n{}",
            errors.len(),
            leases.len(),
            ...
        )));
    }

    This means a user with three backends where two succeed and one fails will get a non-zero exit code and may be surprised that the two successful leases were created silently. If the intent really is "fail only when all fail", the condition should be changed to errors.len() == leases.len(). If the intent is to fail on any error (which is defensible and probably better for CI), the PR description should be updated to reflect that.

Fix All in Claude Code

Last reviewed commit: a8fd891

Comment thread src/commands/lease.rs
Comment thread src/commands/lease.rs
Comment thread src/commands/lease.rs Outdated
Comment thread src/commands/lease.rs
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new feature allowing the creation of leases for all configured backends with --all, and sets this as the default behavior for fnox lease. The code is well-refactored to support this, with logic split into run_all and create_single functions, and user experience is improved with better output formatting. However, it introduces a potential command injection vulnerability in the Env output format. Furthermore, a regression exists where interactively provided environment variables are not cleaned up, potentially leading to credential leaks. To address these, it is recommended to quote credential keys in the shell output and ensure all temporary environment variables are properly cleared using the TempEnvGuard.

Comment thread src/commands/lease.rs
@@ -125,7 +210,6 @@ impl LeaseCreateCommand {
// TODO: unsafe set_var on a multi-threaded Tokio runtime is
// technically UB. Refactor to pass credentials explicitly.
unsafe { std::env::set_var(var, &value) };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

security-medium medium

The PR removed the code that adds interactive credentials to the TempEnvGuard. As a result, credentials entered interactively are no longer cleared from the process environment after the command finishes, which can lead to sensitive information remaining in the environment of a long-running process (like the fnox TUI if it uses this command). Interactively set environment variables are not being cleaned up because the _temp_env_guard from run() is not being used here. To fix this, you should pass the _temp_env_guard from run() down to this function (and run_all()) and register the variable with it, for example, by adding unsafe { std::env::set_var(var, &value) }; temp_env_guard.keys.push(var.to_string()); after updating the function signature to accept temp_env_guard: &mut TempEnvGuard.

autofix-ci Bot and others added 2 commits March 8, 2026 21:50
- Error when both backend_name and --all are specified (conflicts_with)
- Thread TempEnvGuard into create_single so interactive env vars are
  cleaned up on exit
- Return non-zero exit on partial failure so CI/scripts can detect it

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment thread src/commands/lease.rs
leases.len(),
errors.join("\n")
)));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fails on any backend error, not only all

Medium Severity

The run_all method returns an error whenever !errors.is_empty(), meaning it fails if any backend fails. The PR description states it "fails only if all backends fail," so the condition needs to check errors.len() == leases.len() instead. As written, a single backend failure causes the entire command to report failure even when other backends succeeded.

Fix in Cursor Fix in Web

@jdx jdx enabled auto-merge (squash) March 8, 2026 22:05
The output changed from "Lease created" to "✓ Lease 'name' created
(expires in Xm)", so match on "created" instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jdx jdx merged commit 368f5cc into main Mar 8, 2026
16 checks passed
@jdx jdx deleted the feat/lease-create-all branch March 8, 2026 22:40
jdx pushed a commit that referenced this pull request Mar 9, 2026
### 🚀 Features

- **(cloudflare)** add Cloudflare API token lease backend by
[@jdx](https://github.com/jdx) in
[#335](#335)
- **(fido2)** bump demand to v2, mask PIN during typing by
[@jdx](https://github.com/jdx) in
[#334](#334)
- **(init)** add -f as short alias for --force by
[@jdx](https://github.com/jdx) in
[#329](#329)
- **(lease)** add --all flag, default to creating all leases by
[@jdx](https://github.com/jdx) in
[#337](#337)
- **(lease)** add GitHub App installation token lease backend by
[@jdx](https://github.com/jdx) in
[#342](#342)

### 🐛 Bug Fixes

- **(config)** fix directory locations to follow XDG spec by
[@jdx](https://github.com/jdx) in
[#336](#336)
- **(exec)** use unix exec and exit silently on subprocess failure by
[@jdx](https://github.com/jdx) in
[#339](#339)
- **(fido2)** remove duplicate touch prompt by
[@jdx](https://github.com/jdx) in
[#332](#332)
- **(set)** write to lowest-priority existing config file by
[@jdx](https://github.com/jdx) in
[#331](#331)
- **(tui)** skip providers requiring interactive auth by
[@jdx](https://github.com/jdx) in
[#333](#333)

### 🛡️ Security

- **(ci)** retry lint step to handle transient pkl fetch failures by
[@jdx](https://github.com/jdx) in
[#341](#341)
- **(mcp)** add MCP server for secret-gated AI agent access by
[@jdx](https://github.com/jdx) in
[#343](#343)
- add guide for fnox sync by [@jdx](https://github.com/jdx) in
[#328](#328)

### 🔍 Other Changes

- share Rust cache across CI jobs by [@jdx](https://github.com/jdx) in
[#340](#340)
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