Skip to content

feat: implement shell integration for automatic cx fix error capture#744

Open
nailoo wants to merge 1 commit intocxlinux-ai:mainfrom
nailoo:js
Open

feat: implement shell integration for automatic cx fix error capture#744
nailoo wants to merge 1 commit intocxlinux-ai:mainfrom
nailoo:js

Conversation

@nailoo
Copy link

@nailoo nailoo commented Feb 24, 2026

This PR introduces native shell integration for Bash and Zsh to automatically capture command-line errors. This enables cx fix to analyze and resolve the last failed command without requiring the user to manually copy-paste the error message.

Key Features

  • Automated Error Capture: Hooks into shell execution to redirect stderr to a temporary buffer. On non-zero exit codes, the command and its error output are saved to ~/.cx/last_error.
  • cx setup shell Command: A new setup subcommand that automates the installation process by:
    • Embedding integration scripts into the binary.
    • Creating required local directories (~/.cx/shell/).
    • Appending source lines to ~/.bashrc and ~/.zshrc.
  • Zero-Config cx fix: Updated the shortcut command to automatically detect and read the last captured error if no arguments are provided.
  • Performance Optimized: Uses efficient redirection (exec 2> >(tee ...)) and shell hooks (PROMPT_COMMAND/precmd) to ensure no noticeable latency in terminal usage.
  • Compatibility: Verified compatibility with popular shell frameworks including Oh My Zsh and Powerlevel10k.

Technical Details

  • Added assets/shell/bash_integration.sh
  • Added assets/shell/zsh_integration.sh
  • Modified wezterm/src/cli/shortcuts.rs:
    • Implemented setup_shell logic.
    • Added update_rc_file helper for atomic .rc updates.
    • Integrated with existing FixCommand logic.

Acceptance Criteria Met

  • Bash integration captures stderr and exit code
  • Zsh integration captures stderr and exit code
  • cx setup shell installs integration automatically
  • cx fix reads captured error without manual input
  • BSL 1.1 headers included in all new scripts

close #712

Summary by CodeRabbit

Release Notes

  • New Features
    • Added shell integration for Bash and Zsh that automatically captures error details from failed commands and persists them for later inspection.
    • Introduced a setup command to enable and configure shell integration, which updates shell configuration files automatically.

Copilot AI review requested due to automatic review settings February 24, 2026 18:29
@nailoo nailoo requested a review from a team as a code owner February 24, 2026 18:29
@github-actions
Copy link
Contributor

CLA Verification Failed

The following contributors have not signed the Contributor License Agreement:

  • @nailoo (nailoo, rahulsinghnailoo@gmail.com)

How to Sign

  1. Read the CLA document
  2. Open a CLA signature request
  3. A maintainer will add you to the signers list
  4. Comment recheck on this PR to re-run verification

This check runs automatically. Maintainers can update .github/cla-signers.json to add signers.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @nailoo, 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 cx tool by integrating it directly with the user's shell environment. The primary goal is to streamline the error resolution workflow by automatically capturing command-line errors, allowing cx fix to operate without explicit user input. This change improves user experience by making the error diagnosis and fixing process more seamless and less manual.

Highlights

  • Automated Error Capture: Introduced native shell integration for Bash and Zsh to automatically capture command-line errors, redirecting stderr to a temporary buffer and saving failed commands and their error output to ~/.cx/last_error.
  • New Setup Command: Added a cx setup shell command that automates the installation of shell integration scripts, creates necessary local directories, and appends source lines to .bashrc and .zshrc.
  • Zero-Config cx fix: Updated the cx fix command to automatically detect and read the last captured error from ~/.cx/last_error if no arguments are provided, eliminating the need for manual error message copy-pasting.
  • Performance Optimized: Implemented efficient redirection using exec 2> >(tee ...) and shell hooks (PROMPT_COMMAND for Bash, precmd for Zsh) to ensure minimal latency during terminal usage.
  • Shell Compatibility: Verified compatibility with popular shell frameworks such as Oh My Zsh and Powerlevel10k.
Changelog
  • wezterm/src/cli/shortcuts.rs
    • Imported the std::io::Write trait for file operations.
    • Added a conditional check within SetupCommand::run to delegate to setup_shell when the 'shell' argument is provided.
    • Implemented the setup_shell method to manage the creation of .cx and .cx/shell directories, embed and write Bash and Zsh integration scripts, and update shell RC files.
    • Created the update_rc_file helper function to append source lines to .bashrc and .zshrc files, ensuring idempotency by checking for existing integration.
Activity
  • No human activity has occurred on this pull request yet.
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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 2026

📝 Walkthrough

Walkthrough

This PR introduces shell integration scripts for Bash and Zsh that automatically capture and persist error details (command, exit code, stderr) from failed commands in interactive shells. It includes a new setup command to install these integrations and update shell RC files.

Changes

Cohort / File(s) Summary
Shell Integration Scripts
assets/shell/bash_integration.sh, assets/shell/zsh_integration.sh
New shell scripts that capture stderr from failed commands using PROMPT_COMMAND (Bash) and precmd hook (Zsh) mechanisms. Both exclude "cx fix" commands and persist error details to ~/.cx/last_error. Stderr is tee'd to both terminal and ~/.cx/stderr.tmp.
CLI Setup Integration
wezterm/src/cli/shortcuts.rs
Adds a special "setup shell" path in SetupCommand that installs the shell integration scripts to ~/.cx/shell/ and updates ~/.bashrc and ~/.zshrc to source the integrations. Includes helper function to create or update RC files with source directives.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant SetupCmd as SetupCommand<br/>(CLI)
    participant FileSystem as File System
    participant ShellRC as Shell RC Files
    participant Shell as Bash/Zsh
    participant Integration as Integration<br/>Script

    User->>SetupCmd: cx setup shell
    SetupCmd->>FileSystem: Create ~/.cx/shell/
    SetupCmd->>FileSystem: Write bash_integration.sh
    SetupCmd->>FileSystem: Write zsh_integration.sh
    SetupCmd->>ShellRC: Update ~/.bashrc
    SetupCmd->>ShellRC: Update ~/.zshrc
    SetupCmd->>User: Print guidance

    User->>Shell: Start interactive shell
    Shell->>ShellRC: Source RC file
    ShellRC->>Integration: Source integration script
    Integration->>Shell: Register PROMPT_COMMAND/precmd

    User->>Shell: Execute command (fails)
    Shell->>Integration: Trigger error hook
    Integration->>FileSystem: Capture stderr to ~/.cx/stderr.tmp
    Integration->>FileSystem: Write ~/.cx/last_error with command/exit code/stderr
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Poem

🐰 A shell that remembers each stumble and fall,
Bash hooks and precmd catch errors for all,
In ~/.cx the echoes of trouble reside,
With setup so simple, no secrets to hide!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely captures the main change: implementing shell integration for automatic error capture in cx fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@sonarqubecloud
Copy link

Copy link
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 native shell integration for Bash and Zsh, enabling automatic capture of command-line errors for the cx fix command. The implementation includes new shell scripts for Bash and Zsh, which are embedded into the binary and deployed via a new cx setup shell command. This command also handles updating the user's .bashrc and .zshrc files. The cx fix command has been updated to automatically read the last captured error if no arguments are provided, enhancing user experience. The approach for capturing stderr using tee and integrating with shell hooks (PROMPT_COMMAND/precmd) is robust and well-considered.

}

let content = std::fs::read_to_string(&rc_path)?;
let source_line = format!("source ~/.cx/shell/{}", script_path.file_name().unwrap().to_str().unwrap());
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The use of unwrap() twice on script_path.file_name() and to_str() can lead to a panic if the file name is unexpectedly None or contains non-UTF-8 characters. While unlikely for the current script names, it's safer to handle these Option and Result types explicitly to prevent potential crashes.

Suggested change
let source_line = format!("source ~/.cx/shell/{}", script_path.file_name().unwrap().to_str().unwrap());
let source_line = format!("source ~/.cx/shell/{}", script_path.file_name().and_then(|s| s.to_str()).unwrap_or_default());

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

Caution

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

⚠️ Outside diff range comments (2)
wezterm/src/cli/shortcuts.rs (2)

241-243: ⚠️ Potential issue | 🟡 Minor

read_last_error lacks the empty-HOME guard that setup_shell has.

setup_shell (line 100) bails when HOME is empty. read_last_error silently proceeds, joining an empty string with .cx/last_error, creating a relative path that resolves against CWD rather than the user's home directory.

🔧 Proposed fix
     fn read_last_error(&self) -> Result<String> {
         let home = std::env::var("HOME").unwrap_or_default();
+        if home.is_empty() {
+            return Ok(String::new());
+        }
         let error_file = std::path::Path::new(&home).join(".cx").join("last_error");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wezterm/src/cli/shortcuts.rs` around lines 241 - 243, read_last_error
currently uses std::env::var("HOME").unwrap_or_default() which allows an empty
HOME and builds a relative path; add the same empty-HOME guard as setup_shell:
get HOME with std::env::var("HOME") and if it is empty return an Err (or bail)
early instead of continuing, so that read_last_error does not join an empty home
to ".cx/last_error"; update the code that computes error_file (the
Path::new(&home).join(".cx").join("last_error")) to run only after the HOME
check and preserve the function's Result<String> error type when returning the
early error.

241-253: ⚠️ Potential issue | 🟠 Major

Clearing last_error before ask.run() completes causes permanent data loss on failure.

Line 248 zeroes the file immediately after reading, before the fix operation has a chance to succeed. If ask.run() subsequently fails (e.g., network unavailable, API error), the captured error context is gone and the user must repro the original failure.

Move the clear to after a successful run, or rename to a .bak file first.

🔧 Proposed fix
 impl FixCommand {
     pub fn run(&self) -> Result<()> {
         let error_text = if self.error.is_empty() {
-            self.read_last_error()?
+            self.read_last_error(false)?
         } else {
             self.error.join(" ")
         };

         if error_text.is_empty() {
             anyhow::bail!("No error to fix. Run a command first or provide an error message.");
         }

         let query = format!("fix this error: {}", error_text);
         let ask = AskCommand { /* ... */ };
-        ask.run()
+        let result = ask.run();
+        if result.is_ok() {
+            let _ = self.read_last_error(true); // consume/clear only on success
+        }
+        result
     }

-    fn read_last_error(&self) -> Result<String> {
+    fn read_last_error(&self, clear: bool) -> Result<String> {
         let home = std::env::var("HOME").unwrap_or_default();
         let error_file = std::path::Path::new(&home).join(".cx").join("last_error");
         if error_file.exists() {
             let content = std::fs::read_to_string(&error_file)?;
-            let _ = std::fs::write(&error_file, "");
+            if clear {
+                let _ = std::fs::write(&error_file, "");
+            }
             Ok(content.trim().to_string())
         } else {
             Ok(String::new())
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wezterm/src/cli/shortcuts.rs` around lines 241 - 253, read_last_error
currently wipes ~/.cx/last_error immediately after reading, risking permanent
loss if the subsequent fix (e.g., ask.run) fails; update read_last_error to only
read and return the content without clearing the file, and implement the
clearing/rotation logic in the caller that runs the fix (or add a new helper
like clear_last_error or rotate_last_error_to_bak) so the file is removed or
renamed to .bak only after ask.run (or the fix function) succeeds—alternatively,
rename the file to last_error.bak before running the fix and delete the .bak on
success (refer to read_last_error and the caller that invokes ask.run to place
the new clear/rename logic).
🧹 Nitpick comments (1)
wezterm/src/cli/shortcuts.rs (1)

98-154: New I/O functions use synchronous std::fs — async I/O is required by the coding guidelines.

setup_shell, update_rc_file, and read_last_error all block on disk I/O via std::fs::read_to_string, std::fs::write, and std::fs::create_dir_all. Per coding guidelines, Rust files must use async patterns for I/O operations (Tokio async/await).

🔧 Example conversion sketch
-    fn setup_shell(&self) -> Result<()> {
+    async fn setup_shell(&self) -> Result<()> {
         ...
-        std::fs::create_dir_all(&shell_dir)?;
-        std::fs::write(&bash_script, bash_content)?;
-        std::fs::write(&zsh_script, zsh_content)?;
+        tokio::fs::create_dir_all(&shell_dir).await?;
+        tokio::fs::write(&bash_script, bash_content).await?;
+        tokio::fs::write(&zsh_script, zsh_content).await?;
         ...
     }

-    fn update_rc_file(...) -> Result<()> {
+    async fn update_rc_file(...) -> Result<()> {
-        let content = std::fs::read_to_string(&rc_path)?;
+        let content = tokio::fs::read_to_string(&rc_path).await?;
         ...
     }

Based on coding guidelines: "Use async patterns for I/O operations" and "Use Tokio async/await patterns for Rust async operations."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wezterm/src/cli/shortcuts.rs` around lines 98 - 154, The functions
setup_shell, update_rc_file (and the referenced read_last_error) are using
blocking std::fs I/O and must be converted to async Tokio I/O; change
setup_shell and update_rc_file to async fn and update any callers to .await,
replace std::fs::create_dir_all/read_to_string/write/OpenOptions with
tokio::fs::create_dir_all, tokio::fs::read_to_string, tokio::fs::write and
tokio::fs::OpenOptions, and replace synchronous writeln! on an appended
std::fs::File with an async write using tokio::io::AsyncWriteExt (write_all)
after opening with OpenOptions::append(). Keep the same file paths and the
source_line construction, but ensure all filesystem calls are awaited and error
types still return Result by using anyhow or the crate's existing error
handling; also update the signature/types for update_rc_file to accept &Path or
&str consistently and adjust callers accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@assets/shell/bash_integration.sh`:
- Line 19: The assignment using local last_cmd=$(history 1 | sed 's/^[ ]*[0-9]*[
]*//') masks the subshell return value (SC2155); change it to declare the
variable first and then assign the subshell output to it (e.g., use local
last_cmd on one line, then set last_cmd=$(history 1 | sed 's/^[ ]*[0-9]*[ ]*//')
on the next) so the subshell's exit status isn't masked; refer to the variable
name last_cmd to locate the change.
- Around line 48-49: The current creation of the config directory using mkdir -p
"$HOME/.cx" leaves default permissions which may be world-readable; change the
creation to set restrictive permissions (700) and ensure existing directories
are corrected: use a creation command that sets mode (e.g., mkdir -m 0700 for
the "$HOME/.cx" directory) or call chmod 0700 after mkdir -p "$HOME/.cx", so any
files or error output stored there are not world-readable; update the code paths
that call mkdir -p "$HOME/.cx" accordingly and ensure permission correction runs
unconditionally for existing directories.
- Around line 51-54: The current global stderr redirection using "exec 2> >(tee
\"$HOME/.cx/stderr.tmp\" >&2)" creates an async flush race and can chain
multiple tee processes when the script is re-sourced; modify the stderr capture
logic around the exec and the error handler __cx_err_capture so it is idempotent
and waits briefly for the async tee to flush before checking stderr.tmp: add an
idempotency guard (e.g., a sentinel variable or check to avoid rebinding
exec/rehooking PROMPT_COMMAND), record the tee PID when you start the process so
you can avoid starting another or kill the orphan on re-source, and before
reading "$HOME/.cx/stderr.tmp" (inside __cx_err_capture or PROMPT_COMMAND) wait
briefly or wait on the tee PID to ensure it has flushed (a short sleep like
0.05s or a wait on the saved PID); ensure the logic references the same
stderr.tmp file name and properly cleans/rotates the PID sentinel to prevent
multiple chained tee processes.

In `@assets/shell/zsh_integration.sh`:
- Around line 13-19: In __cx_err_capture_precmd(), avoid masking return values
by separating declaration and assignment for local variables: declare local
exit_code first and then set exit_code=$? (instead of local exit_code=$?), and
similarly declare local last_cmd before assigning last_cmd=$(fc -ln -1); update
the __cx_err_capture_precmd function to use these two-step local declarations so
the exit status isn't lost.
- Line 47: Guard the stderr tee setup with the same re-source guard used in
bash: check and export __CX_STDERR_HOOKED so the exec 2> >(tee
"$HOME/.cx/stderr.tmp" >&2) line runs only once; before creating the temp file
ensure the directory "$HOME/.cx" exists (mkdir -p "$HOME/.cx") and harden
permissions with chmod 700 "$HOME/.cx"; leave the exec redirection target as
stderr.tmp but perform the directory creation and chmod prior to the exec and
set the __CX_STDERR_HOOKED flag after the setup.
- Line 22: The zsh conditional that checks last_cmd (the if using last_cmd =~
"cx fix") uses an unanchored pattern and will match any string containing "cx
fix"; change that condition to use an anchored regular expression that begins
with ^ and escapes the space so it only matches commands that start with "cx
fix" (i.e., replace the unanchored pattern in the if [[ "$last_cmd" =~ "cx fix"
]] test with an anchored ^cx\ fix pattern).

In `@wezterm/src/cli/shortcuts.rs`:
- Line 138: Replace the unwrap chain that builds source_line: instead of calling
script_path.file_name().unwrap().to_str().unwrap(), extract a UTF-8 file name
with script_path.file_name().and_then(|n| n.to_str()).ok_or_else(||
anyhow::anyhow!("invalid script path file name or non-UTF8 bytes: {:?}",
script_path))?, then build source_line with that string; update the surrounding
function to return anyhow::Result so the ? can propagate the error (or add
.context(...) from anyhow for clearer messages). Reference: source_line,
script_path.file_name(), to_str(), and make the function return anyhow::Result
to propagate failures rather than panicking.
- Around line 107-115: The code currently unconditionally uses std::fs::write to
write embedded bash_content and zsh_content into bash_script and zsh_script,
which will silently overwrite any user-modified files; update the logic in
shortcuts.rs to check for the files' existence (e.g., using Path::exists or
std::fs::metadata on bash_script and zsh_script) and if present either skip
writing and print a warning or require an explicit force option (e.g., a --force
bool flag parsed by the CLI) to overwrite; ensure you only call std::fs::write
when the file does not exist or when force is true, and surface a clear warning
message naming bash_script/zsh_script when skipping.

---

Outside diff comments:
In `@wezterm/src/cli/shortcuts.rs`:
- Around line 241-243: read_last_error currently uses
std::env::var("HOME").unwrap_or_default() which allows an empty HOME and builds
a relative path; add the same empty-HOME guard as setup_shell: get HOME with
std::env::var("HOME") and if it is empty return an Err (or bail) early instead
of continuing, so that read_last_error does not join an empty home to
".cx/last_error"; update the code that computes error_file (the
Path::new(&home).join(".cx").join("last_error")) to run only after the HOME
check and preserve the function's Result<String> error type when returning the
early error.
- Around line 241-253: read_last_error currently wipes ~/.cx/last_error
immediately after reading, risking permanent loss if the subsequent fix (e.g.,
ask.run) fails; update read_last_error to only read and return the content
without clearing the file, and implement the clearing/rotation logic in the
caller that runs the fix (or add a new helper like clear_last_error or
rotate_last_error_to_bak) so the file is removed or renamed to .bak only after
ask.run (or the fix function) succeeds—alternatively, rename the file to
last_error.bak before running the fix and delete the .bak on success (refer to
read_last_error and the caller that invokes ask.run to place the new
clear/rename logic).

---

Nitpick comments:
In `@wezterm/src/cli/shortcuts.rs`:
- Around line 98-154: The functions setup_shell, update_rc_file (and the
referenced read_last_error) are using blocking std::fs I/O and must be converted
to async Tokio I/O; change setup_shell and update_rc_file to async fn and update
any callers to .await, replace
std::fs::create_dir_all/read_to_string/write/OpenOptions with
tokio::fs::create_dir_all, tokio::fs::read_to_string, tokio::fs::write and
tokio::fs::OpenOptions, and replace synchronous writeln! on an appended
std::fs::File with an async write using tokio::io::AsyncWriteExt (write_all)
after opening with OpenOptions::append(). Keep the same file paths and the
source_line construction, but ensure all filesystem calls are awaited and error
types still return Result by using anyhow or the crate's existing error
handling; also update the signature/types for update_rc_file to accept &Path or
&str consistently and adjust callers accordingly.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fdf24d6 and 40dd105.

📒 Files selected for processing (3)
  • assets/shell/bash_integration.sh
  • assets/shell/zsh_integration.sh
  • wezterm/src/cli/shortcuts.rs

if [ $exit_code -ne 0 ]; then
if [ -s "$HOME/.cx/stderr.tmp" ]; then
# Get the last command from history
local last_cmd=$(history 1 | sed 's/^[ ]*[0-9]*[ ]*//')
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

Declare last_cmd separately to avoid masking the subshell's return value (SC2155).

🔧 Proposed fix
-            local last_cmd=$(history 1 | sed 's/^[ ]*[0-9]*[ ]*//')
+            local last_cmd
+            last_cmd=$(history 1 | sed 's/^[ ]*[0-9]*[ ]*//')
📝 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
local last_cmd=$(history 1 | sed 's/^[ ]*[0-9]*[ ]*//')
local last_cmd
last_cmd=$(history 1 | sed 's/^[ ]*[0-9]*[ ]*//')
🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 19-19: Declare and assign separately to avoid masking return values.

(SC2155)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/shell/bash_integration.sh` at line 19, The assignment using local
last_cmd=$(history 1 | sed 's/^[ ]*[0-9]*[ ]*//') masks the subshell return
value (SC2155); change it to declare the variable first and then assign the
subshell output to it (e.g., use local last_cmd on one line, then set
last_cmd=$(history 1 | sed 's/^[ ]*[0-9]*[ ]*//') on the next) so the subshell's
exit status isn't masked; refer to the variable name last_cmd to locate the
change.

Comment on lines +48 to +49
# Ensure .cx directory exists
mkdir -p "$HOME/.cx"
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

Create ~/.cx with restricted permissions to protect potentially sensitive error output.

Command stderr can contain credentials, tokens, or PII in error messages. The directory and its files should not be world-readable.

🛡️ Proposed fix
-mkdir -p "$HOME/.cx"
+mkdir -p "$HOME/.cx"
+chmod 700 "$HOME/.cx"

Alternatively, combine into one step (Linux mkdir -m):

-mkdir -p "$HOME/.cx"
+mkdir -p -m 700 "$HOME/.cx"
📝 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
# Ensure .cx directory exists
mkdir -p "$HOME/.cx"
# Ensure .cx directory exists
mkdir -p "$HOME/.cx"
chmod 700 "$HOME/.cx"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/shell/bash_integration.sh` around lines 48 - 49, The current creation
of the config directory using mkdir -p "$HOME/.cx" leaves default permissions
which may be world-readable; change the creation to set restrictive permissions
(700) and ensure existing directories are corrected: use a creation command that
sets mode (e.g., mkdir -m 0700 for the "$HOME/.cx" directory) or call chmod 0700
after mkdir -p "$HOME/.cx", so any files or error output stored there are not
world-readable; update the code paths that call mkdir -p "$HOME/.cx" accordingly
and ensure permission correction runs unconditionally for existing directories.

Comment on lines +51 to +54
# Global stderr capture
# Note: we use 'tee' to ensure the user still sees the error on their screen.
# We redirect stderr (2) through tee which also writes to stderr.tmp
exec 2> >(tee "$HOME/.cx/stderr.tmp" >&2)
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

Two issues with exec 2> >(tee ...): async race and missing idempotency guard.

  1. Race condition: the tee process runs asynchronously. When PROMPT_COMMAND fires, tee may not have finished flushing all bytes to stderr.tmp, causing [ -s "$HOME/.cx/stderr.tmp" ] to see an empty or truncated file.
  2. Fd chain on re-source: there is no guard around line 54. If this script is sourced a second time (e.g., source ~/.bashrc mid-session), a new tee is chained through the previous one, producing double-writes to stderr.tmp and leaving orphaned background processes.
🔧 Proposed fix
+# Guard against double-hooking stderr
+if [[ -z "$__CX_STDERR_HOOKED" ]]; then
+    __CX_STDERR_HOOKED=1
     exec 2> >(tee "$HOME/.cx/stderr.tmp" >&2)
+fi

For the flush race, consider writing the PID of the tee process and briefly waiting on it before reading, or using a named pipe with blocking reads. A pragmatic short-term option that adds minimal latency:

__cx_err_capture() {
    local exit_code=$?
+   # Give async tee a moment to flush before reading
+   sleep 0.05
    if [ $exit_code -ne 0 ]; then
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/shell/bash_integration.sh` around lines 51 - 54, The current global
stderr redirection using "exec 2> >(tee \"$HOME/.cx/stderr.tmp\" >&2)" creates
an async flush race and can chain multiple tee processes when the script is
re-sourced; modify the stderr capture logic around the exec and the error
handler __cx_err_capture so it is idempotent and waits briefly for the async tee
to flush before checking stderr.tmp: add an idempotency guard (e.g., a sentinel
variable or check to avoid rebinding exec/rehooking PROMPT_COMMAND), record the
tee PID when you start the process so you can avoid starting another or kill the
orphan on re-source, and before reading "$HOME/.cx/stderr.tmp" (inside
__cx_err_capture or PROMPT_COMMAND) wait briefly or wait on the tee PID to
ensure it has flushed (a short sleep like 0.05s or a wait on the saved PID);
ensure the logic references the same stderr.tmp file name and properly
cleans/rotates the PID sentinel to prevent multiple chained tee processes.

Comment on lines +13 to +19
__cx_err_capture_precmd() {
local exit_code=$?

if [[ $exit_code -ne 0 ]]; then
if [[ -s "$HOME/.cx/stderr.tmp" ]]; then
# In Zsh, the last command is available via fc
local last_cmd=$(fc -ln -1)
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

Declare exit_code and last_cmd separately to avoid masking return values.

Same SC2155 pattern as the Bash counterpart.

🔧 Proposed fix
 __cx_err_capture_precmd() {
-    local exit_code=$?
+    local exit_code
+    exit_code=$?
     
     if [[ $exit_code -ne 0 ]]; then
         if [[ -s "$HOME/.cx/stderr.tmp" ]]; then
-            local last_cmd=$(fc -ln -1)
+            local last_cmd
+            last_cmd=$(fc -ln -1)
📝 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
__cx_err_capture_precmd() {
local exit_code=$?
if [[ $exit_code -ne 0 ]]; then
if [[ -s "$HOME/.cx/stderr.tmp" ]]; then
# In Zsh, the last command is available via fc
local last_cmd=$(fc -ln -1)
__cx_err_capture_precmd() {
local exit_code
exit_code=$?
if [[ $exit_code -ne 0 ]]; then
if [[ -s "$HOME/.cx/stderr.tmp" ]]; then
# In Zsh, the last command is available via fc
local last_cmd
last_cmd=$(fc -ln -1)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/shell/zsh_integration.sh` around lines 13 - 19, In
__cx_err_capture_precmd(), avoid masking return values by separating declaration
and assignment for local variables: declare local exit_code first and then set
exit_code=$? (instead of local exit_code=$?), and similarly declare local
last_cmd before assigning last_cmd=$(fc -ln -1); update the
__cx_err_capture_precmd function to use these two-step local declarations so the
exit status isn't lost.

local last_cmd=$(fc -ln -1)

# Don't capture if it was 'cx fix' or similar
if [[ "$last_cmd" =~ "cx fix" ]]; then
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

Unanchored pattern excludes more commands than intended — inconsistent with the Bash version.

"cx fix" matches any command containing "cx fix" (e.g., echo "cx fix guide" would be silently skipped). The Bash script correctly anchors with ^cx\ fix. Apply the same anchor here.

🔧 Proposed fix
-            if [[ "$last_cmd" =~ "cx fix" ]]; then
+            if [[ "$last_cmd" =~ ^cx[[:space:]]fix ]]; then
📝 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
if [[ "$last_cmd" =~ "cx fix" ]]; then
if [[ "$last_cmd" =~ ^cx[[:space:]]fix ]]; then
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/shell/zsh_integration.sh` at line 22, The zsh conditional that checks
last_cmd (the if using last_cmd =~ "cx fix") uses an unanchored pattern and will
match any string containing "cx fix"; change that condition to use an anchored
regular expression that begins with ^ and escapes the space so it only matches
commands that start with "cx fix" (i.e., replace the unanchored pattern in the
if [[ "$last_cmd" =~ "cx fix" ]] test with an anchored ^cx\ fix pattern).

mkdir -p "$HOME/.cx"

# Global stderr capture
exec 2> >(tee "$HOME/.cx/stderr.tmp" >&2)
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

Same async-tee race condition and missing re-source guard as bash_integration.sh line 54.

The same __CX_STDERR_HOOKED guard fix and permission hardening (chmod 700 "$HOME/.cx") described for the Bash script apply here as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/shell/zsh_integration.sh` at line 47, Guard the stderr tee setup with
the same re-source guard used in bash: check and export __CX_STDERR_HOOKED so
the exec 2> >(tee "$HOME/.cx/stderr.tmp" >&2) line runs only once; before
creating the temp file ensure the directory "$HOME/.cx" exists (mkdir -p
"$HOME/.cx") and harden permissions with chmod 700 "$HOME/.cx"; leave the exec
redirection target as stderr.tmp but perform the directory creation and chmod
prior to the exec and set the __CX_STDERR_HOOKED flag after the setup.

Comment on lines +107 to +115
let bash_script = shell_dir.join("bash_integration.sh");
let zsh_script = shell_dir.join("zsh_integration.sh");

// We embed the script contents here so they are available in the binary
let bash_content = include_str!("../../../assets/shell/bash_integration.sh");
let zsh_content = include_str!("../../../assets/shell/zsh_integration.sh");

std::fs::write(&bash_script, bash_content)?;
std::fs::write(&zsh_script, zsh_content)?;
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

cx setup shell silently overwrites user-customized integration scripts.

std::fs::write will clobber ~/.cx/shell/bash_integration.sh and ~/.cx/shell/zsh_integration.sh if the user has modified them. At minimum, check for existence and warn (or skip with a flag like --force).

🔧 Suggested change
-        std::fs::write(&bash_script, bash_content)?;
-        std::fs::write(&zsh_script, zsh_content)?;
+        for (path, content) in [(&bash_script, bash_content), (&zsh_script, zsh_content)] {
+            if path.exists() && !self.auto_confirm {
+                println!("  ⚠️  {} already exists, skipping (use --yes to overwrite)", path.display());
+            } else {
+                std::fs::write(path, content)?;
+            }
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wezterm/src/cli/shortcuts.rs` around lines 107 - 115, The code currently
unconditionally uses std::fs::write to write embedded bash_content and
zsh_content into bash_script and zsh_script, which will silently overwrite any
user-modified files; update the logic in shortcuts.rs to check for the files'
existence (e.g., using Path::exists or std::fs::metadata on bash_script and
zsh_script) and if present either skip writing and print a warning or require an
explicit force option (e.g., a --force bool flag parsed by the CLI) to
overwrite; ensure you only call std::fs::write when the file does not exist or
when force is true, and surface a clear warning message naming
bash_script/zsh_script when skipping.

}

let content = std::fs::read_to_string(&rc_path)?;
let source_line = format!("source ~/.cx/shell/{}", script_path.file_name().unwrap().to_str().unwrap());
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

Replace .unwrap() chains with proper anyhow error propagation.

file_name() can return None (path ending in ..) and to_str() returns None on non-UTF-8 bytes. Both currently panic. Per coding guidelines, errors must be propagated via Result/anyhow.

🔧 Proposed fix
-        let source_line = format!("source ~/.cx/shell/{}", script_path.file_name().unwrap().to_str().unwrap());
+        let file_name = script_path
+            .file_name()
+            .and_then(|n| n.to_str())
+            .ok_or_else(|| anyhow::anyhow!("Invalid script path: {:?}", script_path))?;
+        let source_line = format!("source ~/.cx/shell/{}", file_name);
📝 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
let source_line = format!("source ~/.cx/shell/{}", script_path.file_name().unwrap().to_str().unwrap());
let file_name = script_path
.file_name()
.and_then(|n| n.to_str())
.ok_or_else(|| anyhow::anyhow!("Invalid script path: {:?}", script_path))?;
let source_line = format!("source ~/.cx/shell/{}", file_name);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wezterm/src/cli/shortcuts.rs` at line 138, Replace the unwrap chain that
builds source_line: instead of calling
script_path.file_name().unwrap().to_str().unwrap(), extract a UTF-8 file name
with script_path.file_name().and_then(|n| n.to_str()).ok_or_else(||
anyhow::anyhow!("invalid script path file name or non-UTF8 bytes: {:?}",
script_path))?, then build source_line with that string; update the surrounding
function to return anyhow::Result so the ? can propagate the error (or add
.context(...) from anyhow for clearer messages). Reference: source_line,
script_path.file_name(), to_str(), and make the function return anyhow::Result
to propagate failures rather than panicking.

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 introduces native shell integration for Bash and Zsh to automatically capture command-line errors for CX Linux's AI-powered error resolution feature. The implementation adds shell hooks that redirect stderr to temporary files and save failed command information to ~/.cx/last_error, enabling the cx fix command to automatically analyze the last error without manual user input.

Changes:

  • Added automated shell error capture via PROMPT_COMMAND (Bash) and precmd hooks (Zsh) that save failed commands and their stderr output
  • Implemented cx setup shell command that embeds integration scripts in the binary and automatically updates ~/.bashrc and ~/.zshrc
  • Extended cx fix to automatically read captured errors from ~/.cx/last_error when invoked without arguments

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 16 comments.

File Description
wezterm/src/cli/shortcuts.rs Added setup_shell() method and update_rc_file() helper to install shell integration; integrated error reading in FixCommand
assets/shell/bash_integration.sh Bash shell integration using PROMPT_COMMAND hook and stderr redirection via process substitution
assets/shell/zsh_integration.sh Zsh shell integration using precmd hook with fc command history and stderr redirection

}

let content = std::fs::read_to_string(&rc_path)?;
let source_line = format!("source ~/.cx/shell/{}", script_path.file_name().unwrap().to_str().unwrap());
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The source_line uses a hardcoded path (~/.cx/shell/) which may not match the actual installation directory if HOME contains symlinks or if the user has a non-standard shell configuration. Consider using the full absolute path from shell_dir instead of reconstructing it, or at least use $HOME variable in the source line to make it more portable.

Suggested change
let source_line = format!("source ~/.cx/shell/{}", script_path.file_name().unwrap().to_str().unwrap());
let source_line = format!("source {}", script_path.display());

Copilot uses AI. Check for mistakes.
Comment on lines +98 to +154
fn setup_shell(&self) -> Result<()> {
let home = std::env::var("HOME").unwrap_or_default();
if home.is_empty() {
anyhow::bail!("HOME environment variable not set");
}
let cx_dir = std::path::Path::new(&home).join(".cx");
let shell_dir = cx_dir.join("shell");
std::fs::create_dir_all(&shell_dir)?;

let bash_script = shell_dir.join("bash_integration.sh");
let zsh_script = shell_dir.join("zsh_integration.sh");

// We embed the script contents here so they are available in the binary
let bash_content = include_str!("../../../assets/shell/bash_integration.sh");
let zsh_content = include_str!("../../../assets/shell/zsh_integration.sh");

std::fs::write(&bash_script, bash_content)?;
std::fs::write(&zsh_script, zsh_content)?;

// Update .bashrc
self.update_rc_file(&home, ".bashrc", &bash_script)?;
// Update .zshrc
self.update_rc_file(&home, ".zshrc", &zsh_script)?;

println!("\n✅ CX Terminal shell integration installed!");
println!("The integration will capture errors and allow 'cx fix' to read them automatically.");
println!("\nTo activate now, run:");
println!(" source ~/.bashrc # if using Bash");
println!(" source ~/.zshrc # if using Zsh");

Ok(())
}

fn update_rc_file(&self, home: &str, rc_name: &str, script_path: &std::path::Path) -> Result<()> {
let rc_path = std::path::Path::new(home).join(rc_name);
if !rc_path.exists() {
return Ok(()); // Skip if shell is not present
}

let content = std::fs::read_to_string(&rc_path)?;
let source_line = format!("source ~/.cx/shell/{}", script_path.file_name().unwrap().to_str().unwrap());

if content.contains(&source_line) {
println!(" ℹ️ Shell integration already present in {}", rc_name);
return Ok(());
}

let mut file = std::fs::OpenOptions::new()
.append(true)
.open(&rc_path)?;

writeln!(file, "\n# CX Terminal Shell Integration")?;
writeln!(file, "{}", source_line)?;
println!(" ✅ Added integration to {}", rc_name);

Ok(())
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The test coverage for new shell integration functionality (setup_shell, update_rc_file, and FixCommand.read_last_error) is missing. The codebase follows a pattern of including #[cfg(test)] blocks in CLI modules (see ask.rs, ask_context.rs, ask_patterns.rs, license.rs). Add unit tests to verify: 1) setup_shell creates directories and files correctly, 2) update_rc_file handles existing entries, missing files, and appends correctly, 3) read_last_error handles missing files and clears content after reading.

Copilot generated this review using guidance from organization custom instructions.
add-zsh-hook precmd __cx_err_capture_precmd

# Ensure .cx directory exists
mkdir -p "$HOME/.cx"
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

Same issue as bash_integration.sh: mkdir -p at line 44 will succeed even if $HOME is empty or unset. Add a guard check to validate $HOME before creating directories.

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +22
# Don't capture if it was 'cx fix' or similar
if [[ "$last_cmd" =~ ^cx\ fix ]]; then
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The check for 'cx fix' only matches commands that start with 'cx fix' (using ^cx\ fix regex in Bash). This means commands like 'sudo cx fix' or 'command cx fix' would still be captured, potentially creating an infinite loop or confusing behavior. Consider using a more comprehensive check that handles various command prefixes.

Suggested change
# Don't capture if it was 'cx fix' or similar
if [[ "$last_cmd" =~ ^cx\ fix ]]; then
# Don't capture if it was 'cx fix' or common prefixed variants (e.g. sudo cx fix)
if [[ "$last_cmd" =~ ^([[:space:]]*(sudo|command|env|nice|nohup)[[:space:]]+)*cx[[:space:]]+fix ]]; then

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +36
__cx_err_capture() {
local exit_code=$?

if [ $exit_code -ne 0 ]; then
if [ -s "$HOME/.cx/stderr.tmp" ]; then
# Get the last command from history
local last_cmd=$(history 1 | sed 's/^[ ]*[0-9]*[ ]*//')

# Don't capture if it was 'cx fix' or similar
if [[ "$last_cmd" =~ ^cx\ fix ]]; then
: > "$HOME/.cx/stderr.tmp"
return
fi

{
echo "Command: $last_cmd"
echo "Exit Code: $exit_code"
echo "--- Stderr ---"
cat "$HOME/.cx/stderr.tmp"
} > "$HOME/.cx/last_error"
fi
fi
# Clear for next command
: > "$HOME/.cx/stderr.tmp"
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

Race condition: The stderr.tmp and last_error files are accessed without any locking mechanism. If multiple shell sessions are running concurrently (e.g., in tmux/screen), they will all write to the same files, causing corrupted captures or lost errors. Consider using session-specific files (e.g., stderr.tmp.$$ or using $PPID) or implement file locking.

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +47
# Global stderr capture
exec 2> >(tee "$HOME/.cx/stderr.tmp" >&2)
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

Similar to the Bash version, this exec redirection using process substitution creates a persistent background process. In Zsh, this can cause the shell to hang on exit until the background process completes. Test the behavior with rapid shell exits (exit, logout, or Ctrl+D) to ensure the shell doesn't hang.

Suggested change
# Global stderr capture
exec 2> >(tee "$HOME/.cx/stderr.tmp" >&2)
# Global stderr capture without process substitution to avoid persistent background jobs
_cx_stderr_fifo="$HOME/.cx/stderr.fifo"
# Create FIFO if needed, removing any stale non-FIFO file
if [[ ! -p "$_cx_stderr_fifo" ]]; then
[[ -e "$_cx_stderr_fifo" ]] && rm -f "$_cx_stderr_fifo"
mkfifo "$_cx_stderr_fifo"
fi
# Start tee in the background, disowned so zsh does not wait on it at exit
tee "$HOME/.cx/stderr.tmp" <"$_cx_stderr_fifo" >&2 &!
# Redirect shell stderr into the FIFO for capture
exec 2>"$_cx_stderr_fifo"

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +22
# Don't capture if it was 'cx fix' or similar
if [[ "$last_cmd" =~ "cx fix" ]]; then
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The Zsh regex check uses a simple pattern match [[ "$last_cmd" =~ "cx fix" ]] which will match "cx fix" anywhere in the command string, not just at the beginning. This means commands like "echo cx fix" or comments containing "cx fix" would also be excluded from capture. Use a more precise pattern like [[ "$last_cmd" =~ (^|[[:space:]])cx[[:space:]]fix ]] or similar to match word boundaries.

Suggested change
# Don't capture if it was 'cx fix' or similar
if [[ "$last_cmd" =~ "cx fix" ]]; then
# Don't capture if the command was 'cx fix' (with optional args), not just any line containing that substring
if [[ "$last_cmd" =~ ^[[:space:]]*cx[[:space:]]+fix([[:space:]]|$) ]]; then

Copilot uses AI. Check for mistakes.
Comment on lines +40 to +44
if [[ ! "$PROMPT_COMMAND" =~ __cx_err_capture ]]; then
if [[ -z "$PROMPT_COMMAND" ]]; then
PROMPT_COMMAND="__cx_err_capture"
else
PROMPT_COMMAND="__cx_err_capture; $PROMPT_COMMAND"
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The PROMPT_COMMAND modification uses string matching to check if __cx_err_capture is already present, but this could have false positives if the user has a variable or function name containing this substring. Consider using a more robust check or setting a guard variable to prevent double-initialization.

Suggested change
if [[ ! "$PROMPT_COMMAND" =~ __cx_err_capture ]]; then
if [[ -z "$PROMPT_COMMAND" ]]; then
PROMPT_COMMAND="__cx_err_capture"
else
PROMPT_COMMAND="__cx_err_capture; $PROMPT_COMMAND"
if [[ -z "${__CX_ERR_CAPTURE_INSTALLED:-}" ]]; then
__CX_ERR_CAPTURE_INSTALLED=1
if [[ -z "${PROMPT_COMMAND:-}" ]]; then
PROMPT_COMMAND="__cx_err_capture"
else
case ";${PROMPT_COMMAND};" in
*";__cx_err_capture;"*)
# Already present as a standalone command; do nothing
;;
*)
PROMPT_COMMAND="__cx_err_capture; ${PROMPT_COMMAND}"
;;
esac

Copilot uses AI. Check for mistakes.
}

let content = std::fs::read_to_string(&rc_path)?;
let source_line = format!("source ~/.cx/shell/{}", script_path.file_name().unwrap().to_str().unwrap());
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

Multiple unwrap() calls on Option types without proper error handling. If script_path.file_name() returns None (e.g., if the path ends with ".."), or if to_str() fails for non-UTF8 filenames, this will panic. Use proper error handling with ? operator and anyhow::Context for better error messages. For example: script_path.file_name().context("Invalid script path")?.to_str().context("Non-UTF8 filename")?

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +54
# We redirect stderr (2) through tee which also writes to stderr.tmp
exec 2> >(tee "$HOME/.cx/stderr.tmp" >&2)
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The exec redirection using process substitution (exec 2> >(tee ...)) creates a background process that persists. If the user exits the shell quickly, the background tee process might become orphaned or cause the shell to hang. This is a known issue with process substitution in exec redirects. Consider using a named pipe (mkfifo) or a more robust approach, or at minimum document this limitation and test exit behavior.

Suggested change
# We redirect stderr (2) through tee which also writes to stderr.tmp
exec 2> >(tee "$HOME/.cx/stderr.tmp" >&2)
# We redirect stderr (2) through a named pipe that tee reads from, which also writes to stderr.tmp.
if [[ -z "${__CX_STDERR_TEE_PID:-}" ]]; then
__cx_stderr_fifo="$HOME/.cx/stderr_fifo_$$"
# Create a per-shell FIFO for stderr
if [ ! -p "$__cx_stderr_fifo" ]; then
mkfifo "$__cx_stderr_fifo" 2>/dev/null || {
# If we cannot create the FIFO, skip installing global capture to avoid breaking the shell
return
}
fi
# Start tee in the background to read from the FIFO and write to both stderr.tmp and real stderr
tee "$HOME/.cx/stderr.tmp" >&2 <"$__cx_stderr_fifo" &
__CX_STDERR_TEE_PID=$!
# Ensure we clean up the FIFO and tee process when the shell exits
trap 'rm -f "$__cx_stderr_fifo"; kill "$__CX_STDERR_TEE_PID" 2>/dev/null || true' EXIT
# Redirect this shell's stderr to the FIFO
exec 2>"$__cx_stderr_fifo"
fi

Copilot uses AI. Check for mistakes.
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.

[FEATURE] Shell integration for cx fix error capture

2 participants