feat(cli): allow entering edit mode from CLI#12716
feat(cli): allow entering edit mode from CLI#12716foxfirecodes wants to merge 2 commits intogitbutlerapp:masterfrom
Conversation
|
@foxfirecodes is attempting to deploy a commit to the GitButler Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Pull request overview
Adds a new legacy-only but edit-mode CLI subcommand to enter/inspect/finish/cancel edit mode (mirroring the desktop UI “Edit commit” flow), plus a small desktop-side mitigation for an edit-mode exit race.
Changes:
- Introduces
edit-modeCLI command withstatus|finish|cancel [--force], wiring it through args parsing, command dispatch, and metrics. - Updates legacy
statusto show edit-mode status and delegate to resolve status when the edited commit is conflicted. - Adds CLI tests + updates CLI reference docs; adds a frontend guard against a worktree-changes/edit-mode race.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/but/tests/but/command/mod.rs | Registers new legacy edit_mode test module. |
| crates/but/tests/but/command/edit_mode.rs | Adds CLI tests covering enter/status/finish/cancel flows. |
| crates/but/src/utils/metrics.rs | Emits metrics event for the new EditMode subcommand. |
| crates/but/src/lib.rs | Dispatches Subcommands::EditMode to legacy handler. |
| crates/but/src/command/legacy/status/mod.rs | Improves edit-mode status handling (fallback to resolve status if conflicted). |
| crates/but/src/command/legacy/resolve.rs | Exposes conflict helpers for reuse by edit-mode flow. |
| crates/but/src/command/legacy/mod.rs | Exposes new legacy edit_mode module. |
| crates/but/src/command/legacy/edit_mode.rs | Implements the but edit-mode command behavior and output. |
| crates/but/src/command/help.rs | Adds edit-mode to grouped help list (shown only if subcommand exists). |
| crates/but/src/args/mod.rs | Adds EditMode clap subcommand + wires args module. |
| crates/but/src/args/metrics.rs | Adds EditMode to metrics command-name enum. |
| crates/but/src/args/edit_mode.rs | Defines edit-mode subcommands and flags. |
| crates/but/skill/references/reference.md | Documents but edit-mode usage + workflow. |
| apps/desktop/src/lib/mode/modeService.ts | Catches a specific edit-mode exit race during worktree change event handling. |
| // listener was unsubscribed. Silently ignore that specific race. | ||
| const message = error instanceof Error ? error.message : String(error); | ||
| if (!message.includes("Expected to be in edit mode")) { | ||
| throw error; | ||
| } |
There was a problem hiding this comment.
This error handling relies on a substring match ("Expected to be in edit mode") to detect the race. That’s brittle to future backend error-message changes and can mask legitimate failures if the message ever changes format. Consider switching to a structured error signal (e.g. backend error code/type) or proactively checking operating_mode before invoking; at minimum, when this condition is hit, set finished = true and/or call unsubscribe() immediately so repeated worktree_changes events don’t keep triggering failing invokes.
| // listener was unsubscribed. Silently ignore that specific race. | |
| const message = error instanceof Error ? error.message : String(error); | |
| if (!message.includes("Expected to be in edit mode")) { | |
| throw error; | |
| } | |
| // listener was unsubscribed. Silently ignore that specific race, | |
| // and stop listening so we don't keep invoking a failing command. | |
| const message = error instanceof Error ? error.message : String(error); | |
| if (message.includes("Expected to be in edit mode")) { | |
| finished = true; | |
| unsubscribe(); | |
| return; | |
| } | |
| throw error; |
8195eac to
36305ac
Compare
|
Thanks a lot for contributing! Could you squash commits to two maybe, one for the CLI changes, and one for the frontend fix? @codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 237d3b4564
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const message = error instanceof Error ? error.message : String(error); | ||
| if (!message.includes("Expected to be in edit mode")) { |
There was a problem hiding this comment.
Read object error messages before race-condition filtering
This filter only inspects error.message when the thrown value is an Error instance, but our backend invoke path can throw plain object-shaped errors (e.g. ReduxError), in which case String(error) becomes "[object Object]". That means the intended "Expected to be in edit mode" race gets rethrown instead of ignored, so the UI still surfaces the same error when edit mode is exited from another client. Please extract message from object errors before falling back to String(error).
Useful? React with 👍 / 👎.
237d3b4 to
7c166a2
Compare
7c166a2 to
56ad7b6
Compare
|
squashed and updated the frontend to use |
| let mode = gitbutler_operating_modes::operating_mode(ctx); | ||
| if let gitbutler_operating_modes::OperatingMode::Edit(_metadata) = mode { | ||
| if let gitbutler_operating_modes::OperatingMode::Edit(metadata) = mode { | ||
| // In edit mode, show the conflict resolution status |
There was a problem hiding this comment.
The comment here says edit mode always shows the conflict resolution status, but show_edit_mode_status() now conditionally shows either resolve status (for conflicted commits) or edit-mode status (for non-conflicted commits). Updating the comment would prevent confusion when reading this early-return path.
| // In edit mode, show the conflict resolution status | |
| // In edit mode, show either conflict resolution status (for conflicted commits) | |
| // or the general edit-mode status for non-conflicted commits. |
|
|
||
| fn current_branch_name(env: &Sandbox) -> anyhow::Result<String> { | ||
| let repo = env.open_repo()?; | ||
| repo.rev_parse_single("HEAD") |
There was a problem hiding this comment.
repo.rev_parse_single("HEAD")? is only used for its error side-effect, but its return value is ignored. Consider assigning it to _ (or removing it) so it’s clear this is an intentional validation step and avoids an “unused result” warning if warnings are tightened in tests.
| repo.rev_parse_single("HEAD") | |
| let _ = repo | |
| .rev_parse_single("HEAD") |
| .assert() | ||
| .success() | ||
| .stderr_eq(str![""]); | ||
|
|
There was a problem hiding this comment.
The new behavior where but status in edit mode shows edit-mode status (or resolve status for conflicted commits) isn’t exercised in these tests. Adding an assertion that plain but status succeeds after entering edit mode would help catch regressions in the status/operating-mode integration.
| env.but("status") | |
| .assert() | |
| .success() | |
| .stderr_eq(str![""]); |
this is a new subcommand to allow you to enter and exit edit mode via the CLI just like you can in the GUI
in certain situations it's possible for the desktop app to receive a worktree_update event BEFORE EditMode has unmounted (and the listener has been unsubscribed). this results in an eroneous error message about expecting to be in edit mode
56ad7b6 to
7f5d19b
Compare
|
Thanks a lot! Then I'd think that @krlvi would be able to review this PR and/or give product-related directions. |
this PR was written by Claude Code with assistance and review from myself
🧢 Changes
this PR introduces a new
edit-modesubcommand that lets you enter edit mode via the CLI. this is meant to mirror the current functionality in the GUI where you can right click on a commit and click "Edit commit" to enter edit mode.☕️ Reasoning
while i do enjoy using the GUI, i would like to be able to allow my AI agents to use edit mode directly, and sometimes its just easier to use a CLI. seeing as the functionality was all there and simply missing a dedicated command to interact with it, i took the opportunity to implement this.
the reason why i named this
edit-modeinstead ofeditis to avoid conflicts with the existing hiddeneditsubcommandthe structure of both the code & the output is meant to align with what already exists in the resolve mode command. i did not extract shared code between the resolve and edit-mode commands but if you want me to do that i can do so, most of the code is basically the same. that being said the actual amount of code involved is pretty small to begin with.
there is a small frontend change here as well to solve what seemed to me to be a race condition. if you exit edit mode (or resolve mode) from the CLI, sometimes the app would show errors like this:
it seems that the app would sometimes receive the
worktree_changesevent before it had unmounted theEditModeUI due to the mode changing, resulting in the error message above. im open to suggestions on a better solution, but this seems like a reasonable approach for a simple race condition like this