Skip to content

Fix shell integration review regressions - #2466

Merged
austinywang merged 4 commits into
mainfrom
issue-2414-review-followups
Apr 1, 2026
Merged

austinywang merged 4 commits into
mainfrom
issue-2414-review-followups

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Apr 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • stop the parent shell PR poller before nested shells start so stale report_pr updates cannot keep mutating the same panel
  • keep bash prompt timing on the same clock for ports_kick throttling so explicit kicks still suppress repeated prompt scans
  • extend the shell integration regression harness with nested-shell poller shutdown and ports-throttle scenarios

Validation

  • Did not run local tests per repo policy
  • zsh -n Resources/shell-integration/cmux-zsh-integration.zsh
  • bash -n Resources/shell-integration/cmux-bash-integration.bash
  • python3 -m py_compile tests/test_issue_1138_sidebar_pr_polling.py

Supersedes #2453.
Closes #2414.


Summary by cubic

Fixes shell integration review regressions. Stops PR polling (bash/zsh) and the zsh HEAD watcher before nested shells, keeps ports_kick throttling, preserves PR cache, and tightens bash nested-shell handling. Addresses #2414.

  • Bug Fixes
    • On preexec, halt the PR poll loop in bash and zsh; in zsh also stop the HEAD watcher and don’t restart it when a nested shell is detected.
    • Detect nested shells from the command string in both shells (handles env/exec and nix develop/shell). In bash, pass $BASH_COMMAND into the preexec hook via PS0 for reliable detection.
    • Preserve cached PR results across ordinary preexecs by splitting “halt” (no cache clear) from “stop” (clears cache). Keep ports_kick throttling by setting _CMUX_PORTS_LAST_RUN via _cmux_now.
    • Extend the regression harness and harden bash nested-shell coverage with nested_shell_stops_parent_poll, ports_kick_throttle, and preexec_preserves_pr_cache.

Written for commit 00b4810. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes

    • More robust stopping and restarting of PR polling and git HEAD watches when launching commands or entering nested shells.
    • Ports-kick timestamping and throttling improved to avoid duplicate port-scan triggers.
    • Pre-execution handling now halts background polling promptly while preserving PR cache when appropriate.
  • Tests

    • Added regression scenarios covering nested-shell behavior, poll/head shutdown, ports-kick throttling, and PR-cache preservation.

@vercel

vercel Bot commented Apr 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Apr 1, 2026 8:16am

@coderabbitai

coderabbitai Bot commented Apr 1, 2026 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9d304ca9-f9e6-4361-9527-0030e9a97961

📥 Commits

Reviewing files that changed from the base of the PR and between 8ee5228 and 00b4810.

📒 Files selected for processing (2)
  • Resources/shell-integration/cmux-bash-integration.bash
  • tests/test_issue_1138_sidebar_pr_polling.py

📝 Walkthrough

Walkthrough

Preexec hooks in Bash and Zsh now halt the PR poll loop immediately after ports-kick (Bash uses "$(_cmux_now)" for ports timestamp). Zsh adds a nested-shell detector and halts both PR poll loop and git-HEAD watch; tests exercise nested-shell termination, ports-kick throttling, and PR cache preservation.

Changes

Cohort / File(s) Summary
Bash integration
Resources/shell-integration/cmux-bash-integration.bash
_cmux_ports_kick sets _CMUX_PORTS_LAST_RUN to "$(_cmux_now)" (replacing $SECONDS). _cmux_bash_preexec_hook now forwards "$BASH_COMMAND" to _cmux_preexec_command, which halts the PR poll loop immediately after ports-kick and skips further work for nested-shell launches.
Zsh integration
Resources/shell-integration/cmux-zsh-integration.zsh
Introduced _cmux_halt_pr_poll_loop to separate killing/reset from cache clearing; callers updated to use it when replacing an existing poller. Added _cmux_command_starts_nested_shell to detect nested shells. _cmux_preexec halts PR poll loop and stops git-HEAD watch after ports-kick and returns early when a nested shell is detected (avoids restarting HEAD watch).
Tests
tests/test_issue_1138_sidebar_pr_polling.py
Added three scenarios (nested_shell_stops_parent_poll, ports_kick_throttle, preexec_preserves_pr_cache) and updated _shell_command()/_run_case() to inject and assert preexec behavior: nested-shell termination of parent poll/HEAD watcher, ports-kick throttling, and PR cache preservation across preexec-triggered commands.

Sequence Diagram(s)

sequenceDiagram
    participant Shell as Shell (preexec)
    participant Ports as Ports Kicker
    participant PR as PR Poll Loop
    participant Head as Git-HEAD Watch
    participant Detector as Nested-Shell Detector

    Shell->>Ports: call _cmux_ports_kick
    Ports-->>Shell: set _CMUX_PORTS_LAST_RUN = "$(_cmux_now)"
    Shell->>PR: call _cmux_halt_pr_poll_loop
    PR-->>Shell: kill PID, clear poll state
    Shell->>Head: call _cmux_stop_git_head_watch
    Shell->>Detector: call _cmux_command_starts_nested_shell
    alt nested shell detected
        Detector-->>Shell: detected -> preexec exits early (do not restart Head)
    else not nested
        Shell->>Head: _cmux_start_git_head_watch
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I thump my foot, the poller takes a nap,
One ports-kick measured on a clock that maps.
If a nested shell comes hopping in to play,
The parent watcher folds and quietly stays.
I nibble logs and hum — the prompts are calm today. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix shell integration review regressions' clearly summarizes the main change—addressing shell integration issues—and is specific enough to convey the primary fix without unnecessary noise.
Description check ✅ Passed The PR description covers the required sections: a clear summary of changes with rationale, validation steps performed, and links to related issues. Although a demo video is not applicable for this shell-script fix, all other template sections are adequately addressed.
Linked Issues check ✅ Passed The PR addresses core objectives from #2414 by reducing unnecessary polling and making PR updates event-driven: it halts parent-shell PR polling before nested shells (event-driven), preserves PR cache to avoid redundant queries, and fixes ports-kick throttling consistency. The extended test scenarios validate these behaviors.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the linked issue objectives: bash/zsh shell-integration updates to halt PR polling and fix throttling, and corresponding test scenarios. No unrelated refactoring or feature additions are present.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-2414-review-followups

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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 3 files

@greptile-apps

greptile-apps Bot commented Apr 1, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two shell-integration regressions: it stops the parent-shell PR poller in preexec (both bash and zsh) so a nested shell cannot receive stale report_pr panel mutations, and it aligns bash's _cmux_ports_kick timestamp with the EPOCHSECONDS-based clock used by the prompt-command throttle check (previously $SECONDS, a shell-uptime counter, was used, making the 10-second throttle always evaluate to true and every prompt firing a ports kick). New regression tests cover both scenarios correctly.

Key changes:

  • cmux-bash-integration.bash: _CMUX_PORTS_LAST_RUN in _cmux_ports_kick changed from $SECONDS → $(_cmux_now), fixing a clock mismatch that disabled the ports-kick throttle entirely in bash.
  • cmux-bash-integration.bash & cmux-zsh-integration.zsh: _cmux_stop_pr_poll_loop added to _cmux_preexec_command / _cmux_preexec, stopping the poll loop before any nested shell can produce stale PR badge updates.
  • test_issue_1138_sidebar_pr_polling.py: Two new scenarios and corresponding _run_case verifiers added.

Issue found:

  • The nested_shell_stops_parent_poll test scenario is subject to a race condition: the poll loop is killed immediately after it starts, so gh may never be exec'd, yet the global if not gh_args_lines: check (which precedes all scenario-specific logic) requires at least one gh invocation. This can produce a false failure even when the feature works correctly.

Confidence Score: 4/5

Shell-script fixes are correct and low risk; the P1 test race condition should be resolved before merging to prevent intermittent CI false failures.

The production shell-integration code changes are small, well-scoped, and logically sound — the clock fix and poll-loop shutdown are genuine improvements. The score is 4 rather than 5 because of a real defect in the new test code: the nested_shell_stops_parent_poll scenario can fail the global gh_args_lines pre-check intermittently (or consistently on fast hardware) even when the application fix is working, which undermines the regression coverage the PR sets out to establish.

tests/test_issue_1138_sidebar_pr_polling.py — specifically the if not gh_args_lines: guard for the nested_shell_stops_parent_poll scenario.

Important Files Changed

Filename Overview
Resources/shell-integration/cmux-bash-integration.bash Two targeted fixes: _cmux_ports_kick now uses _cmux_now (matching the EPOCHSECONDS-based clock used by the prompt-command throttle check, replacing the mismatched $SECONDS), and _cmux_stop_pr_poll_loop is added to _cmux_preexec_command so the parent PR poller is killed before any nested shell runs.
Resources/shell-integration/cmux-zsh-integration.zsh Single-line addition of _cmux_stop_pr_poll_loop in _cmux_preexec, mirroring the bash fix; zsh already uses $EPOCHSECONDS natively in both _cmux_ports_kick and the _cmux_precmd throttle check, so no clock-alignment change is needed here.
tests/test_issue_1138_sidebar_pr_polling.py Adds nested_shell_stops_parent_poll and ports_kick_throttle scenarios with correct shell-side assertion logic, but the global if not gh_args_lines pre-check (which runs before scenario-specific logic) creates a race condition for nested_shell_stops_parent_poll: the poll loop can be SIGKILL'd before gh is ever exec'd, causing a false failure unrelated to the feature under test.

Sequence Diagram

sequenceDiagram
    participant Shell as Parent Shell
    participant PollLoop as PR Poll Loop (bg)
    participant GH as gh (subprocess)
    participant NestedShell as Nested Shell

    Shell->>Shell: _cmux_prompt_entry (precmd/prompt_command)
    Shell->>PollLoop: _cmux_start_pr_poll_loop (disown &)
    Note over PollLoop: starts immediately in background
    Shell->>Shell: capture _CMUX_PR_POLL_PID

    alt new behaviour (this PR)
        Shell->>Shell: _cmux_nested_shell_entry (_cmux_preexec)
        Shell->>PollLoop: _cmux_stop_pr_poll_loop → kill -KILL -pgid
        PollLoop--xGH: killed before gh may run (race window)
        Shell->>NestedShell: nested shell starts (no stale poller)
        NestedShell-->>Shell: nested shell exits
        Shell->>Shell: next _cmux_prompt_entry
        Shell->>PollLoop: _cmux_start_pr_poll_loop (fresh restart)
        PollLoop->>GH: gh pr view (fresh probe)
        GH-->>PollLoop: PR data
        PollLoop->>Shell: report_pr (correct panel)
    end
Loading

Comments Outside Diff (1)

  1. tests/test_issue_1138_sidebar_pr_polling.py, line 375-378 (link)

    P1 Race condition: nested_shell_stops_parent_poll may fail the global gh_args_lines pre-check

    The function _run_case unconditionally checks if not gh_args_lines: before any scenario-specific logic (lines 375–378). For nested_shell_stops_parent_poll, the poll loop is killed immediately after it is started — before it has time to fork the probe subshell, exec git, and then exec gh. Since gh may never be invoked, gh_args_log can be empty, causing the test to return a false failure:

    "zsh/nested_shell_stops_parent_poll: expected at least one gh invocation"
    

    even when the application code is working correctly (parent poll loop was successfully killed).

    The scenario only needs to verify that the parent poller PID is dead after _cmux_nested_shell_entry; it has no inherent requirement that gh was ever called. A minimal fix would be to guard the gh_args_lines checks with a per-scenario skip:

        scenarios_requiring_gh = {
            "prompt_helper_idle",
            "transient_same_context",
            "branch_switch_clear",
            "timeout_recovery",
            "explicit_branch_fallback",
            "initial_prompt_preserves_pr_badge",
            "ports_kick_throttle",
            # nested_shell_stops_parent_poll intentionally kills the poller before gh runs
        }
        if scenario in scenarios_requiring_gh:
            if not gh_args_lines:
                return (1, f"{shell}/{scenario}: expected at least one gh invocation")
            if any(not line.startswith("pr view ") for line in gh_args_lines):
                return (1, f"{shell}/{scenario}: expected gh pr view only\n" + "\n".join(gh_args_lines))

    Alternatively, adding a brief sleep 0.5 in the test script between _cmux_prompt_entry and _cmux_nested_shell_entry would give the poll loop enough time to invoke gh at least once, but that is slower and still technically racy on heavily loaded systems.

Reviews (1): Last reviewed commit: "Fix shell integration review regressions" | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/test_issue_1138_sidebar_pr_polling.py`:
- Line 298: The bash hook is missing nested-shell detection: update the bash
preexec path so _cmux_preexec_command accepts the command argument and calls
_cmux_command_starts_nested_shell "$cmd" (mirroring the zsh flow) before
reporting activity; ensure the _cmux_bash_preexec_hook forwards the command to
_cmux_preexec_command and that _cmux_nested_shell_entry is executed in the same
conditional flow so nested shells are detected and skipped the same way as in
zsh.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 76c072e6-fb25-48b7-b679-9cf3c926e28d

📥 Commits

Reviewing files that changed from the base of the PR and between 5ef91dd and 91aacb9.

📒 Files selected for processing (2)
  • Resources/shell-integration/cmux-zsh-integration.zsh
  • tests/test_issue_1138_sidebar_pr_polling.py

Comment thread tests/test_issue_1138_sidebar_pr_polling.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/test_issue_1138_sidebar_pr_polling.py`:
- Around line 264-268: The capture of _CMUX_GIT_HEAD_WATCH_PID is done after
calling _cmux_nested_shell_entry which makes the check a no-op; move the
assignment that reads _CMUX_GIT_HEAD_WATCH_PID to occur before invoking
_cmux_nested_shell_entry (the same way parent_pid is captured) so the script
checks the original watcher PID; ensure this change preserves subsequent calls
to _cmux_stop_git_head_watch() and the existing kill -0 check that verifies the
watcher is running.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 64c07c68-6395-400d-b4c0-07e14e2789c6

📥 Commits

Reviewing files that changed from the base of the PR and between 91aacb9 and 8ee5228.

📒 Files selected for processing (3)
  • Resources/shell-integration/cmux-bash-integration.bash
  • Resources/shell-integration/cmux-zsh-integration.zsh
  • tests/test_issue_1138_sidebar_pr_polling.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • Resources/shell-integration/cmux-zsh-integration.zsh
  • Resources/shell-integration/cmux-bash-integration.bash

Comment on lines +264 to +268
'parent_watch_pid="${_CMUX_GIT_HEAD_WATCH_PID:-}"\n'
'if [ -n "$parent_watch_pid" ] && kill -0 "$parent_watch_pid" 2>/dev/null; then\n'
' echo "parent HEAD watcher still running" >&2\n'
' exit 43\n'
'fi\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.

⚠️ Potential issue | 🟠 Major

HEAD watcher PID captured after preexec hook—check becomes a no-op.

_CMUX_GIT_HEAD_WATCH_PID is captured after _cmux_nested_shell_entry executes. Per the shell integration code, _cmux_stop_git_head_watch() clears _CMUX_GIT_HEAD_WATCH_PID="" after killing the process. If the preexec hook stops the watcher, this variable will be empty when captured, making the subsequent kill -0 check always pass—even if the watcher process was never actually stopped (or is still running as an orphan).

Capture the PID before calling _cmux_nested_shell_entry, matching the pattern used for parent_pid.

🐛 Proposed fix
             '_cmux_prompt_entry\n'
             'parent_pid="$_CMUX_PR_POLL_PID"\n'
+            'parent_watch_pid="${_CMUX_GIT_HEAD_WATCH_PID:-}"\n'
             '[ -n "$parent_pid" ] || { echo "missing parent poll pid" >&2; exit 41; }\n'
             '_cmux_nested_shell_entry\n'
             'sleep 1\n'
             'if kill -0 "$parent_pid" 2>/dev/null; then\n'
             '  echo "parent poller still running" >&2\n'
             '  exit 42\n'
             'fi\n'
-            'parent_watch_pid="${_CMUX_GIT_HEAD_WATCH_PID:-}"\n'
             'if [ -n "$parent_watch_pid" ] && kill -0 "$parent_watch_pid" 2>/dev/null; then\n'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_issue_1138_sidebar_pr_polling.py` around lines 264 - 268, The
capture of _CMUX_GIT_HEAD_WATCH_PID is done after calling
_cmux_nested_shell_entry which makes the check a no-op; move the assignment that
reads _CMUX_GIT_HEAD_WATCH_PID to occur before invoking _cmux_nested_shell_entry
(the same way parent_pid is captured) so the script checks the original watcher
PID; ensure this change preserves subsequent calls to
_cmux_stop_git_head_watch() and the existing kill -0 check that verifies the
watcher is running.

@austinywang
austinywang merged commit 388c30b into main Apr 1, 2026
17 checks passed

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Resources/shell-integration/cmux-bash-integration.bash">

<violation number="1" location="Resources/shell-integration/cmux-bash-integration.bash:893">
P1: Escaped quotes in the PS0 preexec hook cause `$BASH_COMMAND` to be expanded unquoted at runtime, breaking command parsing for nested-shell detection.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

if (( BASH_VERSINFO[0] > 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 4) )); then
if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 3) )); then
builtin readonly _CMUX_BASH_PS0='${ _cmux_bash_preexec_hook; }'
builtin readonly _CMUX_BASH_PS0='${ _cmux_bash_preexec_hook "$BASH_COMMAND"; }'

@cubic-dev-ai cubic-dev-ai Bot Apr 1, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Escaped quotes in the PS0 preexec hook cause $BASH_COMMAND to be expanded unquoted at runtime, breaking command parsing for nested-shell detection.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Resources/shell-integration/cmux-bash-integration.bash, line 893:

<comment>Escaped quotes in the PS0 preexec hook cause `$BASH_COMMAND` to be expanded unquoted at runtime, breaking command parsing for nested-shell detection.</comment>

<file context>
@@ -830,11 +888,11 @@ _cmux_install_prompt_command() {
+        if (( BASH_VERSINFO[0] > 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 4) )); then
         if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 3) )); then
-            builtin readonly _CMUX_BASH_PS0='${ _cmux_bash_preexec_hook; }'
+            builtin readonly _CMUX_BASH_PS0='${ _cmux_bash_preexec_hook "$BASH_COMMAND"; }'
         else
-            builtin readonly _CMUX_BASH_PS0='$(_cmux_bash_preexec_hook >/dev/null)'
</file context>
Fix with Cubic

This branch was successfully deployed

1 active deployment
Preview — 00b4810e Deployed Apr 1, 2026 by vercel[bot]
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.

GitHub PR lookups should be event-driven and avoid unnecessary gh queries

2 participants