[BUG] merged/closed PRs linger as un-dismissable "Ready for Review" chips (stale `prs[].state`, broken deep-link)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
- CLI-merged/closed PRs stay
state: "OPEN"inprs[]for a long time (multiple sessions), re-rendering the chip. - The deep-link is broken for these stale entries (lands at end of current session), so the chip cannot be dismissed through the UI.
- The store does eventually reconcile to the real GitHub state, but only after a long, unpredictable delay.
What Should Happen?
- A PR's chip should clear promptly once the PR is merged or closed, regardless of how it was merged (app UI or
ghCLI). - Any chip should be dismissable independently of its deep-link, so a stale/orphaned chip can always be cleared.
- The deep-link should resolve to the PR's origin transcript spot, not the end of the current session.
Error Messages/Logs
On one session-state file, all 14 tracked PRs were stored as `OPEN` while every one was actually merged/closed on GitHub:
| PR | stored `state` (stale) | GitHub actual |
|----|------------------------|---------------|
| #83–#88, #90, #92–#96 | `OPEN` | `MERGED` |
| #89, #91 | `OPEN` | `CLOSED` |
A short time later the same file **self-corrected** — every entry flipped to `MERGED`/`CLOSED` matching GitHub (aggregate across all session files afterward: 82 `MERGED`, 4 `CLOSED`, **0 `OPEN`**). So the reconcile mechanism exists but fires too late; in the meantime the chips are stuck and un-dismissable.
Steps to Reproduce
- In a Claude Code session, open a PR with
gh pr create→ a chip appears;prs[]gets an entry withstate: "OPEN". - Merge (or close) that PR with
gh pr merge/gh pr close— i.e. outside the app. - Start a new session. The chip is still shown ("Ready for Review"), because the stored
stateis stillOPEN. - Click the chip to dismiss it → instead of landing at the transcript spot where the PR was created, it lands at the end of the current session. No dismiss/mark-reviewed control is reachable.
- The chip persists across further new sessions until a delayed re-sync eventually corrects
state.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.143
Platform
Other
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Other
Additional Information
Bug: merged/closed PRs linger as un-dismissable "Ready for Review" chips (stale prs[].state, broken deep-link)
Summary
A PR opened from a Claude Code session shows a "Ready for Review" chip at session start. When that PR is later merged or closed via the gh CLI (rather than through the app), its entry in the Cowork session-state store keeps state: "OPEN" for a long time — across multiple new sessions — so the chip keeps re-appearing. During that stale window the chip is effectively un-dismissable, because clicking it does not jump to its origin transcript; it jumps to the end of the current session, so the user never reaches a dismiss control. The state does eventually self-correct (a delayed re-sync flips it to MERGED/CLOSED), but the staleness window is far too long and there is no manual escape hatch in the meantime.
Environment
- App:
claude-desktop-unofficial1.18286.2-3.0.1(community Linux build of Claude Desktop / Cowork) - OS: Kali GNU/Linux Rolling (x86_64)
- Feature: Cowork "Ready for Review" PR chips
- The chip logic itself is a Claude Code / Cowork feature (Anthropic); this build just packages it — likely reproducible on the official macOS/Windows apps too when PRs are CLI-merged.
Where the state lives
Per-session JSON under:
~/.config/Claude/claude-code-sessions/<guid>/<guid>/local_<uuid>.json
Each file has a top-level prs[] array; each entry is:
{ "prNumber": 83, "url": "https://github.com/OWNER/REPO/pull/83",
"repo": "OWNER/REPO", "branch": "...", "baseRef": "main", "state": "OPEN" }
A "Ready for Review" chip is rendered for every entry whose state == "OPEN". There is no separate merged-state check at render time — the chip trusts the stored state.
Steps to reproduce
- In a Claude Code session, open a PR with
gh pr create→ a chip appears;prs[]gets an entry withstate: "OPEN". - Merge (or close) that PR with
gh pr merge/gh pr close— i.e. outside the app. - Start a new session. The chip is still shown ("Ready for Review"), because the stored
stateis stillOPEN. - Click the chip to dismiss it → instead of landing at the transcript spot where the PR was created, it lands at the end of the current session. No dismiss/mark-reviewed control is reachable.
- The chip persists across further new sessions until a delayed re-sync eventually corrects
state.
Expected
- A PR's chip should clear promptly once the PR is merged or closed, regardless of how it was merged (app UI or
ghCLI). - Any chip should be dismissable independently of its deep-link, so a stale/orphaned chip can always be cleared.
- The deep-link should resolve to the PR's origin transcript spot, not the end of the current session.
Actual
- CLI-merged/closed PRs stay
state: "OPEN"inprs[]for a long time (multiple sessions), re-rendering the chip. - The deep-link is broken for these stale entries (lands at end of current session), so the chip cannot be dismissed through the UI.
- The store does eventually reconcile to the real GitHub state, but only after a long, unpredictable delay.
Evidence (captured this session)
On one session-state file, all 14 tracked PRs were stored as OPEN while every one was actually merged/closed on GitHub:
| PR | stored state (stale) | GitHub actual |
|----|------------------------|---------------|
| #83–#88, #90, #92–#96 | OPEN | MERGED |
| #89, #91 | OPEN | CLOSED |
A short time later the same file self-corrected — every entry flipped to MERGED/CLOSED matching GitHub (aggregate across all session files afterward: 82 MERGED, 4 CLOSED, 0 OPEN). So the reconcile mechanism exists but fires too late; in the meantime the chips are stuck and un-dismissable.
Impact
- Persistent, misleading "you have PRs to review" chips for work that is already merged and shipped.
- No way to dismiss them during the stale window (broken deep-link), so they nag across every new session.
- Especially bad for agent/CLI-driven workflows, where PRs are routinely opened and merged via
gh— every such PR becomes a stuck chip until the delayed sync.
Requests
- Auto-clear on merge/close. These chips are safe to clear as soon as the PR is merged or closed — please reconcile
prs[].stateagainst GitHub promptly (on session start and/or via merge detection) and clear the chip automatically. The user would much rather they be cleared for them than have to deal with them manually. - Make the chip dismissable independently of its deep-link, so an orphaned/stale chip can always be cleared from the UI.
- Fix the deep-link so it lands at the PR's origin transcript location, not the end of the current session.
Workaround (until fixed)
With the desktop app fully quit, reconcile the stored states against GitHub (only flips entries GitHub reports as not-open; backs up first):
pgrep -f claude-desktop # must be empty
DIR=~/.config/Claude/claude-code-sessions/<guid>/<guid>
cp -r "$DIR" "$DIR.bak-$(date +%s)"
python3 - <<'PY'
import json, glob, os, subprocess
DIR=os.path.expanduser("~/.config/Claude/claude-code-sessions/<guid>/<guid>")
cache={}
def real(repo,n):
if (repo,n) in cache: return cache[(repo,n)]
try: s=subprocess.check_output(["gh","pr","view",str(n),"-R",repo,"--json","state","-q",".state"],text=True).strip()
except Exception: s=None
cache[(repo,n)]=s; return s
for f in glob.glob(os.path.join(DIR,"local_*.json")):
d=json.load(open(f)); ch=False
for p in d.get("prs",[]):
if p.get("state")=="OPEN" and p.get("repo") and p.get("prNumber"):
rs=real(p["repo"],p["prNumber"])
if rs and rs!="OPEN": p["state"]=rs; ch=True
if ch: json.dump(d,open(f,"w"),indent=2)
PY
# then relaunch the app