Skip to content

Make sidebar PR lookups event-driven - #2453

Merged
austinywang merged 3 commits into
mainfrom
issue-2414-event-driven-pr-lookups
Apr 1, 2026
Merged

austinywang merged 3 commits into
mainfrom
issue-2414-event-driven-pr-lookups

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Apr 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • replace unconditional sidebar PR polling with per-panel cache files and force-signal files in the zsh/bash shell integrations
  • cache both PR and no-PR results so idle workspaces reuse prior state, while branch changes and explicit git/gh activity can trigger immediate reprobes
  • extend the existing shell integration regression harness to cover cached idle behavior and cached no-PR lookups

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

Closes #2414.


Summary by cubic

Make sidebar PR lookups event-driven with per-panel caching, a reprobe signal, and a persistent idle poll loop to keep badges fresh while reducing gh calls. Restores predictable idle refresh cadence and reduces UI churn, addressing #2414.

  • New Features

    • Keep the PR poll loop alive between prompts; wake early on branch changes, per-panel reprobe signals, and a zsh Git HEAD watcher.
    • Cache PR and no-PR state per panel and reuse it between prompts; optional debug logging to /tmp/cmux-pr-debug.log.
  • Bug Fixes

    • Clear stale PR state and signal files on context changes and when stopping the loop; handle transient gh failures and timeouts without stopping the poller.
    • Fix zsh timer handling by falling back to $SECONDS when EPOCHSECONDS is unset; add _cmux_now in bash for consistent timing.

Written for commit a00b46c. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Per-panel PR state caching (including “no PR” results) for faster, more accurate PR badges.
    • Force-probe signal to refresh PR information on demand.
    • Optional debug logging for PR detection troubleshooting.
  • Improvements

    • Polling optimized to wake early on demand and use a monotonic timestamp for prompt timing.
    • Prompt lifecycle now clears stale PR state when context (path/branch/HEAD) changes.

@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 5:27am

@coderabbitai

coderabbitai Bot commented Apr 1, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aa2667a9-0b15-4612-96bf-77c81fcd49b8

📥 Commits

Reviewing files that changed from the base of the PR and between 3bc9b75 and a00b46c.

📒 Files selected for processing (1)
  • Resources/shell-integration/cmux-zsh-integration.zsh
🚧 Files skipped from review as they are similar to previous changes (1)
  • Resources/shell-integration/cmux-zsh-integration.zsh

📝 Walkthrough

Walkthrough

Adds per-panel on-disk PR caching, a per-panel force-probe signal, monotonic timestamping, and prompt/poll-loop coordination to reduce redundant gh queries and enable immediate probes when git context changes.

Changes

Cohort / File(s) Summary
Bash Shell Integration
Resources/shell-integration/cmux-bash-integration.bash
Added _cmux_now() (monotonic timestamps); new globals _CMUX_PR_LAST_BRANCH, _CMUX_PR_NO_PR_BRANCH, _CMUX_PR_DEBUG; implemented per-panel on-disk PR cache (/tmp/cmux-pr-cache-$CMUX_PANEL_ID.*) with helpers to compute/clear/write cache; added per-panel force-signal file (/tmp/cmux-pr-force-$CMUX_PANEL_ID) and logic to wake poll loop early; extended _cmux_report_pr_for_path(repo_path, force_probe) to read/write cache and avoid redundant UI updates; threaded force_probe through probe lifecycle; changed prompt/poll/preexec control flow to signal or restart probes and clear cache on context changes.
Zsh Shell Integration
Resources/shell-integration/cmux-zsh-integration.zsh
Mirrors bash changes: per-panel PR state vars and _CMUX_PR_DEBUG; cache helpers (_cmux_pr_cache_prefix, _cmux_pr_cache_clear, etc.); per-panel force-signal helpers (_cmux_pr_force_signal_path, _cmux_pr_request_probe); _cmux_pr_debug_log; _cmux_report_pr_for_path(..., force_probe) updated to consume cache and write “none” sentinel for no-PR results; poll loop updated to wake on signal and delete it before probing; stop/start poll functions clear cache and remove signals; preexec/precmd adjustments to avoid stopping the poll loop and to request immediate probes via signal when appropriate.

Sequence Diagram

sequenceDiagram
    participant Shell as Shell / Prompt Hook
    participant Cache as On-Disk Cache
    participant Signal as Force-Signal File
    participant Poll as PR Poll Loop
    participant GitHub as GitHub (gh)

    Shell->>Cache: Check cached branch/result/timestamp
    alt Cache valid & fresh
        Cache-->>Shell: Return cached PR result
    else Cache missing/stale or context changed
        Shell->>Signal: Create per-panel force signal
        Signal-->>Poll: Poll loop detects signal file
        Poll->>Signal: Delete signal file
        Poll->>GitHub: Run `gh pr view` / `gh pr list`
        GitHub-->>Poll: PR result / "no pull requests"
        Poll->>Cache: Write result + timestamp (or "none")
        Poll-->>Shell: Update panel UI/state
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • #1139: Extends the same PR polling infrastructure (start/stop loop, _cmux_report_pr_for_path, probe execution); closely related to on-disk caching and force-probe additions.
  • #1444: Modifies _cmux_stop_pr_poll_loop behavior; related to cache/signal cleanup introduced here.
  • #671: Changes shell-integration poll lifecycle and signaling for immediate probes; overlaps with the force-probe/poll-loop coordination in this change.

Poem

🐰 Soft paws on keys I tap,

Signals beep—no endless flap.
Cache tucked warm in /tmp's nest,
Probes now wake when change is best,
I nibble bugs and hop to rest. 🥕

🚥 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 'Make sidebar PR lookups event-driven' accurately captures the primary change: converting from unconditional polling to event-driven PR discovery with caching.
Description check ✅ Passed The PR description covers the main change, provides validation steps, and includes a detailed summary. However, it does not include a demo video, testing details, or checklist items as specified in the template.
Linked Issues check ✅ Passed The PR implementation aligns with all primary coding objectives from #2414: event-driven triggers on branch/repo changes, per-panel PR caching with TTLs, persistent idle polling, forced probe signals, debug logging, and cache clearing on context changes.
Out of Scope Changes check ✅ Passed All changes are in scope: modifications to shell integrations implement the event-driven PR lookup strategy with caching, per-panel signal files, debug logging, and persistent polling as specified in #2414.

✏️ 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-event-driven-pr-lookups

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.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 47c50458ba

ℹ️ 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".

Comment on lines 814 to +818
elif (( _CMUX_PR_FORCE )); then
should_restart_pr_poll=1
if [[ -n "$_CMUX_PR_POLL_PID" ]] && kill -0 "$_CMUX_PR_POLL_PID" 2>/dev/null; then
should_signal_pr_probe=1
else
should_restart_pr_poll=1

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 Badge Force PR reprobe after bash gh command completes

In bash, a cached no-PR result can now remain stale after explicit gh actions (for example gh pr create) because _cmux_prompt_command only triggers an immediate probe when _CMUX_PR_FORCE is set, and this path does not get set for bash command executions. Since the poll loop is no longer restarted on every preexec, the loop keeps re-emitting cached none until the cache TTL expires (default 300s), so the sidebar can stay incorrect for several minutes right after the user creates/updates a PR.

Useful? React with 👍 / 👎.

@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.

🧹 Nitpick comments (3)
Resources/shell-integration/cmux-zsh-integration.zsh (1)

659-667: Minor: Inconsistent output method for TSV result.

Line 663 uses printf for the TSV result while lines 660-662 use print -r --. This works but is inconsistent with the rest of the file.

♻️ Use print -r consistently
     if [[ -n "$prefix" ]]; then
         print -r -- "$branch" >| "$branch_file"
         print -r -- "$repo_path" >| "$repo_file"
         print -r -- "$now" >| "$timestamp_file"
-        printf '%s\t%s\t%s\t%s\n' "pr" "$number" "$state" "$url" >| "$result_file"
+        print -r -- "pr\t$number\t$state\t$url" >| "$result_file"
         /bin/rm -f -- "$no_pr_branch_file" >/dev/null 2>&1 || true
     fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Resources/shell-integration/cmux-zsh-integration.zsh` around lines 659 - 667,
Replace the printf call that writes to "$result_file" with the same print
utility used elsewhere for consistency: use print -r -- to write a tab-separated
line containing "pr", $number, $state and $url (preserving the tabs between
fields) and keep the same output redirection (>| "$result_file"); update the
block around the prefix check that currently sets _CMUX_PR_LAST_BRANCH and
_CMUX_PR_NO_PR_BRANCH so it still removes "$no_pr_branch_file" and writes the
branch/repo/timestamp files unchanged.
Resources/shell-integration/cmux-bash-integration.bash (2)

495-519: Consider extracting duplicate cache-write logic.

The cache-write blocks at lines 495-501, 508-514, and 539-545 are nearly identical. While this works correctly, extracting a helper like _cmux_pr_write_cache "$branch" "$repo_path" "$result" would reduce duplication and make future maintenance easier.

♻️ Example helper extraction
_cmux_pr_write_cache() {
    local branch="$1" repo_path="$2" result="$3" is_no_pr="${4:-0}"
    local prefix="" now=""
    prefix="$(_cmux_pr_cache_prefix 2>/dev/null || true)"
    [[ -n "$prefix" ]] || return 0
    now="$(_cmux_now)"
    printf '%s\n' "$branch" >| "${prefix}.branch"
    printf '%s\n' "$repo_path" >| "${prefix}.repo"
    printf '%s\n' "$now" >| "${prefix}.timestamp"
    printf '%s\n' "$result" >| "${prefix}.result"
    if (( is_no_pr )); then
        printf '%s\n' "$branch" >| "${prefix}.no-pr-branch"
    else
        /bin/rm -f -- "${prefix}.no-pr-branch" >/dev/null 2>&1 || true
    fi
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Resources/shell-integration/cmux-bash-integration.bash` around lines 495 -
519, There are three duplicated cache-write blocks writing
branch/repo/timestamp/result/no-pr-branch; extract them into a helper like
_cmux_pr_write_cache and call it from each place (replace the repeated
printf/.../rm logic with a single call). The helper should accept parameters
(branch, repo_path, result, is_no_pr) and internally obtain prefix via
_cmux_pr_cache_prefix and timestamp via _cmux_now, write the four files
("${prefix}.branch", "${prefix}.repo", "${prefix}.timestamp",
"${prefix}.result") and set or remove "${prefix}.no-pr-branch" based on
is_no_pr; update call sites in the branches that use
_cmux_pr_output_indicates_no_pull_request, the earlier cache-write block, and
the later one before invoking _cmux_clear_pr_for_panel so behavior and variables
(branch, repo_path, result) remain identical.

339-347: Cache and signal files use predictable /tmp paths.

The cache files at /tmp/cmux-pr-cache-${CMUX_PANEL_ID}.* and signal file at /tmp/cmux-pr-force-${CMUX_PANEL_ID} are world-readable/writable by default. While the UUID-based naming makes prediction unlikely, on shared multi-user systems this could theoretically allow another user to:

  1. Read cached branch/repo paths (minor information disclosure)
  2. Create spurious signal files to trigger extra gh queries (minor DoS)

For a desktop terminal app this is likely acceptable, but consider using $XDG_RUNTIME_DIR (if available) or creating files with restrictive permissions if this ever targets server environments.

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

In `@Resources/shell-integration/cmux-bash-integration.bash` around lines 339 -
347, The temp paths returned by _cmux_pr_cache_prefix and
_cmux_pr_force_signal_path are world-accessible; change them to use a per-user
runtime directory (prefer $XDG_RUNTIME_DIR) or a locked per-user dir under /tmp
and ensure directory permissions are restrictive before returning file paths:
inside _cmux_pr_cache_prefix and _cmux_pr_force_signal_path, choose
dir="${XDG_RUNTIME_DIR:-/tmp/cmux-${UID}}" (or similar), create it with mkdir -p
-m 0700 if it doesn't exist, and return files under that dir (and ensure any
code that creates the actual cache/signal files uses umask 077 or creates files
with mode 600/700) so the cache and signal files are not
world-readable/writable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@Resources/shell-integration/cmux-bash-integration.bash`:
- Around line 495-519: There are three duplicated cache-write blocks writing
branch/repo/timestamp/result/no-pr-branch; extract them into a helper like
_cmux_pr_write_cache and call it from each place (replace the repeated
printf/.../rm logic with a single call). The helper should accept parameters
(branch, repo_path, result, is_no_pr) and internally obtain prefix via
_cmux_pr_cache_prefix and timestamp via _cmux_now, write the four files
("${prefix}.branch", "${prefix}.repo", "${prefix}.timestamp",
"${prefix}.result") and set or remove "${prefix}.no-pr-branch" based on
is_no_pr; update call sites in the branches that use
_cmux_pr_output_indicates_no_pull_request, the earlier cache-write block, and
the later one before invoking _cmux_clear_pr_for_panel so behavior and variables
(branch, repo_path, result) remain identical.
- Around line 339-347: The temp paths returned by _cmux_pr_cache_prefix and
_cmux_pr_force_signal_path are world-accessible; change them to use a per-user
runtime directory (prefer $XDG_RUNTIME_DIR) or a locked per-user dir under /tmp
and ensure directory permissions are restrictive before returning file paths:
inside _cmux_pr_cache_prefix and _cmux_pr_force_signal_path, choose
dir="${XDG_RUNTIME_DIR:-/tmp/cmux-${UID}}" (or similar), create it with mkdir -p
-m 0700 if it doesn't exist, and return files under that dir (and ensure any
code that creates the actual cache/signal files uses umask 077 or creates files
with mode 600/700) so the cache and signal files are not
world-readable/writable.

In `@Resources/shell-integration/cmux-zsh-integration.zsh`:
- Around line 659-667: Replace the printf call that writes to "$result_file"
with the same print utility used elsewhere for consistency: use print -r -- to
write a tab-separated line containing "pr", $number, $state and $url (preserving
the tabs between fields) and keep the same output redirection (>|
"$result_file"); update the block around the prefix check that currently sets
_CMUX_PR_LAST_BRANCH and _CMUX_PR_NO_PR_BRANCH so it still removes
"$no_pr_branch_file" and writes the branch/repo/timestamp files unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 01c346ea-1536-4203-999e-31bcec9cc92a

📥 Commits

Reviewing files that changed from the base of the PR and between 92d4fa6 and 47c5045.

📒 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

@greptile-apps

greptile-apps Bot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the unconditional "stop poll loop on every preexec, restart on every precmd" pattern with a persistent background loop that uses per-panel file-based caches (/tmp/cmux-pr-cache-{PANEL_ID}.*) and a force-signal file (/tmp/cmux-pr-force-{PANEL_ID}) to avoid redundant gh pr view calls on idle branches. Negative results ("no PR found") are now cached with the same TTL as positive hits, and explicit git/gh activity bypasses the cache via a signal that wakes the sleeping loop immediately.

Key changes:

  • Cache logic in _cmux_report_pr_for_path: reads five per-panel files on every probe; skips the gh network call when branch, repo path, and timestamp all match and cache_age < TTL (default 300 s).
  • Signal mechanism: _cmux_pr_request_probe touches the signal file; the poll loop checks it both at the top of each iteration and during its per-second sleep.
  • Preexec no longer stops the loop, eliminating stop/restart churn on every user command.
  • Regression harness extended with a no_pr_cached scenario and tightened prompt_helper_idle to assert exactly one gh call.

Issues found:

  • The zsh integration uses ${EPOCHSECONDS:-0} as a fallback in _cmux_report_pr_for_path and _cmux_pr_debug_log. When EPOCHSECONDS is unavailable, now is always 0, so cache_age computes to 0 on every call and the cache never expires — stale PR state would be shown indefinitely. The bash side correctly falls back to $SECONDS.
  • The debug log path /tmp/cmux-pr-debug.log is unscoped and world-writable; using ${UID} in the path would prevent symlink-based local file overwrites.
  • The prompt_helper_idle test now asserts gh_count == 1 exactly, which may be flaky on slow CI where the first probe subshell doesn't finish writing its cache before the second poll cycle runs.
  • _cmux_pr_cache_clear inside _cmux_start_git_head_watch's background subshell discards in-memory variable resets; only the file deletions persist to the parent shell.

Confidence Score: 4/5

Safe to merge after fixing the zsh EPOCHSECONDS fallback; everything else is minor polish or test hardening.

The zsh ${EPOCHSECONDS:-0} fallback is a real correctness defect: when EPOCHSECONDS is unavailable, cache_age is always 0 so the TTL guard is permanently satisfied and stale PR state is never refreshed. All other findings are P2. Once the fallback is corrected to :-$SECONDS the PR is solid.

Resources/shell-integration/cmux-zsh-integration.zsh — the EPOCHSECONDS fallback appears in at least two locations and needs to be updated to ${EPOCHSECONDS:-$SECONDS}.

Important Files Changed

Filename Overview
Resources/shell-integration/cmux-bash-integration.bash Adds per-panel file-based cache and force-signal mechanism; replaces unconditional poll restarts on preexec with in-place signaling. Cache write ordering is non-atomic (five separate printf writes) so a killed probe could leave a stale result paired with a fresh timestamp, though the window is tiny.
Resources/shell-integration/cmux-zsh-integration.zsh Mirrors bash changes, but uses ${EPOCHSECONDS:-0} as fallback in three places; when EPOCHSECONDS is unavailable the cache age always computes to 0, making the cache immortal and PR state permanently stale after the first probe. Needs ${EPOCHSECONDS:-$SECONDS} to match the bash strategy.
tests/test_issue_1138_sidebar_pr_polling.py Extends the regression harness with a no_pr_cached scenario and tightens prompt_helper_idle to assert exactly one gh call. The exact-equality check may be flaky on slow CI where the first probe doesn't finish writing its cache before the second poll cycle.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[precmd / prompt_command] --> B{PR poll loop running?}
    B -- No or PWD changed --> C[stop_pr_poll_loop\nkill bg process\nrm signal file\nclear cache files]
    C --> D[start_pr_poll_loop\nnew background loop]
    B -- Yes PR_FORCE=1 --> E[pr_request_probe\ntouch signal file]
    B -- Yes no force --> F[loop continues]
    D --> G[Poll Iteration]
    E --> G
    F --> G
    G --> H{Signal file present?}
    H -- Yes --> I[force_probe=1\nrm signal file]
    H -- No --> J[force_probe=0]
    I --> K[run_pr_probe_with_timeout]
    J --> K
    K --> L{Cache valid?\nsame branch+repo\nage less than TTL\nforce_probe=0}
    L -- Hit --> M[emit cached result\nno gh call]
    L -- Miss or forced --> N[gh pr view BRANCH]
    N --> O{gh result}
    O -- PR found --> P[write cache\nreport_pr to sidebar]
    O -- No PR or empty --> Q[write cache none\nclear_pr sidebar]
    O -- Transient error --> R[return 1\nkeep old badge\nno cache write]
    M --> S[sleep interval\nchecking signal every 1s]
    P --> S
    Q --> S
    R --> S
    S --> G
Loading

Reviews (1): Last reviewed commit: "Make sidebar PR lookups event-driven" | Re-trigger Greptile

[[ -n "$CMUX_PANEL_ID" ]] || return 0

local branch repo_slug="" gh_output="" gh_error="" err_file="" number state url status_opt="" gh_status
local now="${EPOCHSECONDS:-0}"

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.

P1 EPOCHSECONDS:-0 fallback causes cache to never expire

When EPOCHSECONDS is unavailable in zsh (or the zsh/datetime module is not loaded), now is set to 0. Since the cache file is then written with timestamp 0, every subsequent read computes cache_age = 0 - 0 = 0. The condition (( cache_age >= 0 && cache_age < cache_ttl )) is permanently true, so the cache never expires and stale PR state would be displayed indefinitely — no live refresh until a branch change forces a _cmux_pr_cache_clear.

The bash side correctly falls back to $SECONDS (which advances over time, making cache_age positive and eventually exceeding the TTL). The zsh side should do the same:

Suggested change
local now="${EPOCHSECONDS:-0}"
local now="${EPOCHSECONDS:-$SECONDS}"

$SECONDS is a POSIX shell special variable available in zsh even without zsh/datetime, so it's a safe fallback that preserves expiry semantics.

Comment on lines +357 to +360
}

_cmux_pr_cache_clear() {
local prefix=""

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.

P1 Same EPOCHSECONDS:-0 fallback in zsh debug log silently drops timing

The zsh _cmux_pr_debug_log also uses local now="${EPOCHSECONDS:-0}" (zsh file, same pattern). While the debug log is non-functional code, it's worth noting that the :-0 fallback in the zsh integration appears consistently in three places: _cmux_pr_debug_log, _cmux_report_pr_for_path, and _cmux_run_pr_probe_with_timeout (which reads $EPOCHSECONDS directly). All three should use ${EPOCHSECONDS:-$SECONDS} to match the bash fallback strategy.

Comment on lines +365 to +368
"${prefix}.repo" \
"${prefix}.result" \
"${prefix}.timestamp" \
"${prefix}.no-pr-branch" \

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.

P2 Debug log uses a shared, world-writable path

/tmp/cmux-pr-debug.log is written to without any per-user isolation. On multi-user systems this is a TOCTOU symlink risk: a local attacker could create /tmp/cmux-pr-debug.log -> /home/victim/.bashrc before the log is opened, causing >> to append to an arbitrary file. The same issue exists in the zsh integration.

Consider using a user-isolated path when debug mode is enabled:

printf '%s\tbranch=%s\tevent=%s\n' "$now" "$branch" "$event" >> "/tmp/cmux-pr-debug-${UID}.log"

This only matters when _CMUX_PR_DEBUG=1, so it isn't a default-path issue, but it's worth hardening before the debug flag gets documented.

Comment on lines +826 to +830
if [[ -n "$_CMUX_PR_POLL_PID" ]] && kill -0 "$_CMUX_PR_POLL_PID" 2>/dev/null; then
_cmux_pr_request_probe
else
_cmux_run_pr_probe_with_timeout "$watch_pwd" 1 || true
fi

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.

P2 Git HEAD watcher runs _cmux_pr_cache_clear inside a subshell — in-memory state is lost

_cmux_start_git_head_watch runs its body in { } >/dev/null 2>&1 &!, so every call inside it (including _cmux_pr_cache_clear and _cmux_clear_pr_for_panel) executes in a forked subshell. The file-based cache IS correctly deleted by the rm -f in _cmux_pr_cache_clear, but the in-memory resets (_CMUX_PR_LAST_BRANCH="", _CMUX_PR_NO_PR_BRANCH="") are thrown away when the subshell exits. The parent shell retains whatever stale values these variables held before the branch change.

In practice this doesn't cause a visible bug today because the parent's _cmux_precmd loop also calls _cmux_pr_cache_clear on the next prompt (when pr_context_changed is detected), but the in-memory state can be transiently wrong between a background HEAD change and the next prompt. The same pattern is present in the bash integration's git-head watcher path.

Comment on lines 365 to 370
if scenario == "prompt_helper_idle":
if gh_count < 2:
return (1, f"{shell}/{scenario}: expected idle polling to survive prompt helpers, saw {gh_count}")
if gh_count != 1:
return (1, f"{shell}/{scenario}: expected cached idle branch to hit gh once, saw {gh_count}")
if _report_line(1138) not in send_lines:
return (1, f"{shell}/{scenario}: missing report_pr payload\n" + "\n".join(send_lines))
return (0, f"{shell}/{scenario}: ok")

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.

P2 prompt_helper_idle expectation tightened from >= 2 to == 1 — flaky under slow CI

The new assertion gh_count != 1 requires exactly one gh call over a 3-second sleep with a 1-second poll interval. The first probe must complete, write its cache file, and return before the second poll cycle starts its check — otherwise the second poll sees an empty cache and makes another gh call, yielding gh_count == 2 and a test failure.

On a heavily loaded CI host the subshell probe may not finish within one interval, causing an intermittent failure. The older >= 2 guard was intentionally loose for this reason. Consider allowing gh_count <= 2 to tolerate one extra call on slow runners:

if gh_count < 1 or gh_count > 2:
    return (1, f"{shell}/{scenario}: expected 1-2 cached idle gh probes, saw {gh_count}")

@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.

2 issues found across 3 files

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:48">
P2: Use wall-clock epoch seconds as the fallback in `_cmux_now`; `$SECONDS` makes persisted cache timestamps invalid across shell processes.</violation>

<violation number="2" location="Resources/shell-integration/cmux-bash-integration.bash:467">
P2: Do not return immediately when cached PR emission fails; fall back to a live probe so malformed cache entries don't block PR updates.</violation>
</file>

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

_cmux_restore_scrollback_once

_cmux_now() {
printf '%s\n' "${EPOCHSECONDS:-$SECONDS}"

@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.

P2: Use wall-clock epoch seconds as the fallback in _cmux_now; $SECONDS makes persisted cache timestamps invalid across shell processes.

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

<comment>Use wall-clock epoch seconds as the fallback in `_cmux_now`; `$SECONDS` makes persisted cache timestamps invalid across shell processes.</comment>

<file context>
@@ -44,6 +44,10 @@ _cmux_restore_scrollback_once() {
 _cmux_restore_scrollback_once
 
+_cmux_now() {
+    printf '%s\n' "${EPOCHSECONDS:-$SECONDS}"
+}
+
</file context>
Suggested change
printf '%s\n' "${EPOCHSECONDS:-$SECONDS}"
if [[ -n "${EPOCHSECONDS:-}" ]]; then
printf '%s\n' "$EPOCHSECONDS"
else
/bin/date +%s
fi
Fix with Cubic

Comment thread Resources/shell-integration/cmux-bash-integration.bash Outdated

@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.

3 issues 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:425">
P2: Cached PR results are read but never used to short-circuit probes, so the loop still calls `gh pr view` every cycle instead of reusing cache.</violation>
</file>

<file name="tests/test_issue_1138_sidebar_pr_polling.py">

<violation number="1">
P2: The idle helper scenario now enforces repeated `gh` polling (`gh_count >= 2`), which conflicts with event-driven cached idle behavior and weakens/invalidates the intended regression coverage.</violation>
</file>

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

<violation number="1" location="Resources/shell-integration/cmux-zsh-integration.zsh:543">
P2: Cached PR results are not used: the "cache-refresh" path still always runs `gh pr view`, causing unnecessary polling and rate-limit risk.</violation>
</file>

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


_CMUX_PR_LAST_BRANCH="$cache_branch"
_CMUX_PR_NO_PR_BRANCH="$cache_no_pr_branch"
if [[ "$cache_branch" == "$branch" && -n "$cache_result" ]]; then

@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.

P2: Cached PR results are read but never used to short-circuit probes, so the loop still calls gh pr view every cycle instead of reusing cache.

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

<comment>Cached PR results are read but never used to short-circuit probes, so the loop still calls `gh pr view` every cycle instead of reusing cache.</comment>

<file context>
@@ -443,31 +416,17 @@ _cmux_report_pr_for_path() {
-
-    if [[ "$cache_no_pr_branch" == "$branch" ]] || [[ "$cache_result" == "none" ]]; then
-        cache_ttl="${_CMUX_PR_NO_PR_TTL:-300}"
+    if [[ "$cache_branch" == "$branch" && -n "$cache_result" ]]; then
+        _cmux_pr_debug_log "$branch" "cache-refresh"
     else
</file context>
Fix with Cubic


_CMUX_PR_LAST_BRANCH="$cache_branch"
_CMUX_PR_NO_PR_BRANCH="$cache_no_pr_branch"
if [[ "$cache_branch" == "$branch" && -n "$cache_result" ]]; then

@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.

P2: Cached PR results are not used: the "cache-refresh" path still always runs gh pr view, causing unnecessary polling and rate-limit risk.

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

<comment>Cached PR results are not used: the "cache-refresh" path still always runs `gh pr view`, causing unnecessary polling and rate-limit risk.</comment>

<file context>
@@ -562,31 +534,17 @@ _cmux_report_pr_for_path() {
-
-    if [[ "$cache_no_pr_branch" == "$branch" ]] || [[ "$cache_result" == "none" ]]; then
-        cache_ttl="${_CMUX_PR_NO_PR_TTL:-300}"
+    if [[ "$cache_branch" == "$branch" && -n "$cache_result" ]]; then
+        _cmux_pr_debug_log "$branch" "cache-refresh"
     else
</file context>
Fix with Cubic

@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.

Caution

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

⚠️ Outside diff range comments (1)
Resources/shell-integration/cmux-zsh-integration.zsh (1)

541-563: ⚠️ Potential issue | 🟠 Major

Cache is written but never used to skip gh calls.

The cache files are read at lines 536-539 and the branch match is logged at lines 543-547, but the function always proceeds to call gh pr view at line 558. The PR objectives state: "reuse recent results via TTLs/freshness windows" and mention _CMUX_PR_RESULT_TTL / _CMUX_PR_NO_PR_TTL defaults of 300s, but these variables aren't defined and there's no early-return logic when the cache is fresh.

Expected flow for cache hits:

  1. If cache_branch == branch AND cache timestamp is within TTL AND force_probe == 0 → return cached result without calling gh

Additionally, force_probe (line 500) is passed through but never used, since there's no TTL check for it to bypass.

🐛 Sketch of the missing cache-hit early return
+typeset -g _CMUX_PR_RESULT_TTL="${_CMUX_PR_RESULT_TTL:-300}"
+typeset -g _CMUX_PR_NO_PR_TTL="${_CMUX_PR_NO_PR_TTL:-300}"
+
 _cmux_report_pr_for_path() {
     local repo_path="$1"
     local force_probe="${2:-0}"
     # ... existing guards ...
     
     # After reading cache files (lines 536-539):
+    local cache_ts=0
+    [[ -r "$timestamp_file" ]] && cache_ts="$(<"$timestamp_file")"
+    
+    if (( ! force_probe )) && [[ "$cache_branch" == "$branch" && -n "$cache_result" ]]; then
+        local age=$(( now - cache_ts ))
+        local ttl=$_CMUX_PR_RESULT_TTL
+        [[ "$cache_result" == "none" ]] && ttl=$_CMUX_PR_NO_PR_TTL
+        if (( age < ttl )); then
+            _cmux_pr_debug_log "$branch" "cache-hit"
+            # Replay cached result without calling gh
+            if [[ "$cache_result" == "none" ]]; then
+                return 0  # No PR, already cleared
+            else
+                # Parse and send cached PR
+                local IFS=$'\t'
+                read -r _ number state url <<< "$cache_result"
+                # ... send report_pr ...
+            fi
+            return 0
+        fi
+    fi
     
     # ... existing gh pr view call ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Resources/shell-integration/cmux-zsh-integration.zsh` around lines 541 - 563,
The cache is read into variables like cache_branch/cache_result but never used
to skip the gh call; add TTL checks using _CMUX_PR_RESULT_TTL and
_CMUX_PR_NO_PR_TTL (default to 300 if unset), parse the cached timestamp (e.g.
cache_ts) and if cache_branch == branch and (now - cache_ts) <= appropriate TTL
and force_probe == 0 then log via _cmux_pr_debug_log and return the cached
output instead of running gh pr view; ensure force_probe (passed into the
function) bypasses the TTL so gh pr view runs when force_probe != 0, and keep
existing error file/gh_output logic unchanged for cache-miss paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@Resources/shell-integration/cmux-zsh-integration.zsh`:
- Around line 541-563: The cache is read into variables like
cache_branch/cache_result but never used to skip the gh call; add TTL checks
using _CMUX_PR_RESULT_TTL and _CMUX_PR_NO_PR_TTL (default to 300 if unset),
parse the cached timestamp (e.g. cache_ts) and if cache_branch == branch and
(now - cache_ts) <= appropriate TTL and force_probe == 0 then log via
_cmux_pr_debug_log and return the cached output instead of running gh pr view;
ensure force_probe (passed into the function) bypasses the TTL so gh pr view
runs when force_probe != 0, and keep existing error file/gh_output logic
unchanged for cache-miss paths.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f7abcaa2-15c3-4089-aa14-47d48b926350

📥 Commits

Reviewing files that changed from the base of the PR and between 47c5045 and 3bc9b75.

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

@avkrishnamurthy

avkrishnamurthy commented Apr 8, 2026 •

Copy link
Copy Markdown

Im not sure this is fixed @austinywang ? My gh graphql api rate limit is being saturated by cmux with 7 workspaces open, it uses ~2/second

This branch was successfully deployed

1 active deployment
Preview — a00b46ce 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

3 participants