Skip to content

fix(tui): add 2s timeout to all clipboard subprocess calls#334

Merged
bitflicker64 merged 2 commits into
bitflicker64:mainfrom
nayanraj864-cmyk:fix/clipboard-timeout-294
Jul 19, 2026
Merged

fix(tui): add 2s timeout to all clipboard subprocess calls#334
bitflicker64 merged 2 commits into
bitflicker64:mainfrom
nayanraj864-cmyk:fix/clipboard-timeout-294

Conversation

@nayanraj864-cmyk

@nayanraj864-cmyk nayanraj864-cmyk commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes TUI freeze caused by clipboard daemon hangs (issue #294) by replacing unbounded subprocess.Popen calls with subprocess.run(timeout=2.0) across all platforms. Also removes duplicate commit-fetching logic in detect_late_night_chaotic_sessions that would have caused a KeyError on project_id and adds deduplication for chunked queries.

Changes

Core

  • termstory/tui.py: Replaced all subprocess.Popen + communicate() calls with subprocess.run(..., timeout=2.0, check=True) in copy_to_clipboard method
  • termstory/tui.py: Changed exception handling from bare Exception to specific types (subprocess.CalledProcessError, subprocess.TimeoutExpired, OSError)
  • termstory/tui.py: Linux clipboard loop now skips hung tools and tries the next one instead of blocking
  • termstory/tui.py: Updated docstring to document the 2-second timeout bound and fallback behavior

Insights

  • termstory/insights.py: Removed duplicate commit-fetching code block that would have caused KeyError on project_id
  • termstory/insights.py: Added seen_commits set to deduplicate rows appearing in multiple chunk queries when >250 chaotic sessions share the same project
  • termstory/insights.py: Fixed indentation in comment

Tests

  • tests/test_tui.py: Updated test_tui_copy_to_clipboard to mock subprocess.run instead of Popen to match new API
  • tests/test_tui.py: Added test_tui_copy_to_clipboard_timeout to verify TimeoutExpired is handled and OSC 52 fallback fires without hanging

Architecture / Flow

flowchart TD
    A["copy_to_clipboard called"] --> B{"Platform?"}
    B -->|darwin| C["subprocess.run pbcopy timeout=2.0"]
    B -->|Linux| D["Loop: xclip, xsel, wl-copy"]
    D --> E["subprocess.run cmd timeout=2.0"]
    E -->|Success| F["Break"]
    E -->|TimeoutExpired / FileNotFoundError / CalledProcessError| G["Continue to next tool"]
    B -->|win32| H["subprocess.run clip timeout=2.0"]

    C --> I["OSC 52 fallback"]
    F --> I
    G --> I
    H --> I

    I --> J["super().copy_to_clipboard"]
    J --> K["Done"]
Loading

Fixes

Testing

Run the clipboard tests to verify timeout handling and fallback behavior:

pytest tests/test_tui.py::test_tui_copy_to_clipboard -v
pytest tests/test_tui.py::test_tui_copy_to_clipboard_timeout -v

Notes

The OSC 52 fallback (super().copy_to_clipboard) always runs regardless of subprocess timeout or failure, ensuring clipboard operations work over SSH terminals where system clipboard daemons may be unavailable or hung. The 2-second timeout prevents indefinite UI thread blocking while still allowing reasonable time for clipboard daemons to respond.

…r64#294)

Replace Popen+communicate (no timeout) with subprocess.run(timeout=2.0)
across macOS (pbcopy), Linux (xclip/xsel/wl-copy) and Windows (clip).

* On TimeoutExpired the exception is caught at the specific type instead
  of bare Exception, so the OSC 52 fallback always runs.
* The Linux loop narrows its per-tool handler to
  (FileNotFoundError, CalledProcessError, TimeoutExpired) so a hung tool
  is skipped and the loop moves to the next candidate.
* Outer handler narrows from bare Exception to
  (CalledProcessError, TimeoutExpired, OSError).
* Docstring documents the 2-second bound and fallback behaviour.
* test_tui_copy_to_clipboard updated to use subprocess.run mock.
* test_tui_copy_to_clipboard_timeout added: monkeypatches subprocess.run
  to raise TimeoutExpired and asserts OSC 52 fallback fires without hang.

Closes bitflicker64#294
@git-miku git-miku Bot added the needs-triage Awaiting human triage — run /triage to mark as triaged label Jul 19, 2026
@git-miku git-miku Bot added size/M PR changes ~109 lines area/tui area/tui kind/tests kind/tests labels Jul 19, 2026
@git-miku

git-miku Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Tip

👋 Hey @nayanraj864-cmyk — Miku's on it. Here are the most useful commands for this PR:

💬 Command What it does
:miku /review 🔍 Line-by-line review
:miku /review-sc ✏️ Inline suggested changes
:miku /security 🛡️ Security scan
:miku /perf ⚡ Performance notes
:miku /test 🧪 Test suggestions
:miku /summarize 📝 TL;DR

Note

🎓 This repo is part of ECSOC26. Run :miku /ecsoc to add the ECSOC26 label to this PR.

Note

⭐ Star this repo to unlock all commands. Say :miku /help for the full list.

📖 All commands
🤖 AI-powered — 14 commands
Command Description Works on
:miku /ask <question> Answer a question about the codebase Issues & PRs
:miku /review Full code review (auto-fetches diff) PRs only
:miku /review-sc Post inline suggested-change comments PRs only
:miku /security OWASP security review (injection, auth, crypto…) PRs only
:miku /perf Performance & efficiency analysis PRs only
:miku /docs Generate docstrings for changed code PRs only
:miku /test Suggest unit tests for changed code PRs only
:miku /roast Humorous but constructive code roast 🔥 PRs only
:miku /checklist Smart diff-aware PR checklist PRs only
:miku /summarize TL;DR of this issue or PR Issues & PRs
:miku /explain <target> Explain a file, function, or concept Issues & PRs
:miku /changelog Draft a CHANGELOG entry PRs only
:miku /estimate Story-point effort estimate Issues only
:miku /chat <message> General conversation Issues & PRs
🔧 Issue & PR management — 18 commands
Command Description Works on
:miku /assign [@user] Assign to yourself or another user Issues & PRs
:miku /unassign [@user] Remove yourself or another user Issues & PRs
:miku /label <name> Add a label Issues & PRs
:miku /remove-label <name> Remove a label Issues & PRs
:miku /close [reason] Close this issue or PR Issues & PRs
:miku /reopen Reopen a closed issue or PR Issues & PRs
:miku /approve [message] Submit an approving review on a PR PRs only
:miku /retitle <new title> Change the title Issues & PRs
:miku /duplicate #N Mark as duplicate of #N and close Issues & PRs
:miku /milestone [name] Set or list open milestones Issues & PRs
:miku /ship [version] Mark PR as shipped / deployed PRs only
:miku /lock [reason] Lock conversation (resolved / off-topic / spam) Issues & PRs
:miku /unlock Unlock conversation Issues & PRs
:miku /cc @user1 [@user2] Request reviews PRs only
:miku /lgtm [cancel] Signal "Looks Good To Me" Issues & PRs
:miku /hold [cancel] Block merge; :miku /hold cancel releases it Issues & PRs
:miku /wip Toggle WIP (converts PR to draft) Issues & PRs
:miku /merge Merge this PR (CI + approval required) PRs only
🎉 Community & utility — 9 commands
Command Description Works on
:miku /vote Open a 👍 👎 🚀 reaction poll Issues & PRs
:miku /kudos @user [msg] Give public kudos to a contributor Issues & PRs
:miku /stats Repo stars, forks, top contributors Issues & PRs
:miku /remind <dur> <msg> Schedule a reminder (e.g. 2h, 30m, 1d) Issues & PRs
:miku /size Show PR size breakdown PRs only
:miku /ping [@user ...] Ping contributors with an AI message Issues & PRs
:miku /joke Tell a programming joke Issues & PRs
:miku /shrug Add ¯_(ツ)_/¯ label Issues & PRs
:miku /help Show this message Issues & PRs

@greptile-apps

greptile-apps Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a TUI freeze (issue #294) by replacing all unbounded subprocess.Popen + communicate calls in copy_to_clipboard with subprocess.run(timeout=2.0), and also removes a pre-existing duplicate code block in insights.py that would have caused a KeyError on project_id if any late-night chaotic sessions were detected.

  • termstory/tui.py: All three OS paths (macOS pbcopy, Linux xclip/xsel/wl-copy, Windows clip) now use subprocess.run with a 2-second timeout; check=True lets CalledProcessError surface and be caught; the OSC 52 fallback via super().copy_to_clipboard always runs in a separate try block.
  • termstory/insights.py: Removes a redundant first step-4/step-5 block (which lacked seen_commits deduplication and would have raised KeyError on the second pass after del session["project_id"]), leaving only the correct implementation.
  • tests/test_tui.py: Updates the existing clipboard test to mock subprocess.run and adds test_tui_copy_to_clipboard_timeout to verify the OSC 52 fallback fires even when the subprocess times out.

Confidence Score: 4/5

Safe to merge with one known edge case in the Linux clipboard fallback chain worth reviewing before shipping to users without standard clipboard tools.

The timeout fix and insights.py cleanup are both sound. The Linux inner exception tuple does not cover all OSError subclasses — a PermissionError on a partially-installed clipboard tool can escape the inner handler and abort the fallback chain, so xsel and wl-copy may never be attempted. This is a real edge case on constrained Linux environments.

termstory/tui.py — specifically the inner exception tuple in the Linux clipboard loop at line 2154

Important Files Changed

Filename Overview
termstory/tui.py Replaces Popen+communicate with subprocess.run(timeout=2.0) across macOS, Linux, and Windows clipboard paths; the Linux inner exception tuple is narrower than OSError which can silently abort the tool fallback chain on permission-style errors
termstory/insights.py Removes a duplicate, buggy step 4+5 block (which would have caused a KeyError on project_id after deletion) and keeps only the correct implementation with seen_commits deduplication; also removes the now-unused project_ids_needing_commits variable
tests/test_tui.py Updates existing clipboard test to mock subprocess.run instead of Popen and adds a new timeout test; the updated test no longer asserts the correct text was passed as input to the subprocess

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[copy_to_clipboard called] --> B[strip_ansi from text]
    B --> C{sys.platform?}
    C -- darwin --> D[subprocess.run pbcopy timeout=2.0]
    D -- success --> G
    D -- exception --> H[log debug]
    H --> G
    C -- linux --> E[Loop: xclip then xsel then wl-copy]
    E --> E1[subprocess.run cmd timeout=2.0]
    E1 -- success --> G
    E1 -- FileNotFoundError or CalledProcessError or TimeoutExpired --> E2{more tools?}
    E2 -- yes --> E1
    E2 -- no --> G
    E1 -- OSError non-FileNotFoundError --> H
    C -- win32 --> F[subprocess.run clip timeout=2.0]
    F -- success --> G
    F -- exception --> H
    G[super copy_to_clipboard OSC 52 fallback always runs]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[copy_to_clipboard called] --> B[strip_ansi from text]
    B --> C{sys.platform?}
    C -- darwin --> D[subprocess.run pbcopy timeout=2.0]
    D -- success --> G
    D -- exception --> H[log debug]
    H --> G
    C -- linux --> E[Loop: xclip then xsel then wl-copy]
    E --> E1[subprocess.run cmd timeout=2.0]
    E1 -- success --> G
    E1 -- FileNotFoundError or CalledProcessError or TimeoutExpired --> E2{more tools?}
    E2 -- yes --> E1
    E2 -- no --> G
    E1 -- OSError non-FileNotFoundError --> H
    C -- win32 --> F[subprocess.run clip timeout=2.0]
    F -- success --> G
    F -- exception --> H
    G[super copy_to_clipboard OSC 52 fallback always runs]
Loading

Reviews (2): Last reviewed commit: "fix(insights): remove duplicate Step 4+5..." | Re-trigger Greptile

Comment thread termstory/tui.py
Comment thread tests/test_tui.py
…_commits variable

The main branch had a bad merge state from PR bitflicker64#333: detect_late_night_chaotic_sessions
contained two complete copies of the bulk-commit-fetch (Step 4) and the session
association loop (Step 5). The first Step 5 deleted project_id from each session dict,
then the second Step 4 tried to access it → KeyError: 'project_id'.

Fix: collapse to a single clean Step 4 (with seen_commits dedup) and single Step 5.
Also removes the dead project_ids_needing_commits set that was never read.
@git-miku git-miku Bot added the area/ai area/ai label Jul 19, 2026
@nayanraj864-cmyk

Copy link
Copy Markdown
Contributor Author

i solve this issue review and merge it @bitflicker64

@nayanraj864-cmyk

Copy link
Copy Markdown
Contributor Author

:miku /ecsoc

@git-miku git-miku Bot added the ECSoC26 label Jul 19, 2026
@git-miku

git-miku Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

miku /ecsoc

🏷️ Added ECSOC26.

@bitflicker64

Copy link
Copy Markdown
Owner

/rewrite

@git-miku

git-miku Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

miku /rewrite

✅ PR description rewritten! Check the PR body above.

Preview

Summary

Fixes TUI freeze caused by clipboard daemon hangs (issue #294) by adding 2-second timeouts to all subprocess clipboard operations across macOS, Linux, and Windows. Also removes duplicate commit-fetching logic in detect_late_night_chaotic_sessions that would have caused a KeyError on project_id and adds deduplication for chunked queries.

Changes

Core

  • termstory/tui.py: Replaced all subprocess.Popen + communicate() calls with subprocess.run(..., timeout=2.0, check=True) in copy_to_clipboard
  • termstory/tui.py: Changed exception handling from bare Exception to specific types (subprocess.CalledProcessError, subprocess.TimeoutExpired, OSError)
  • termstory/tui.py: Linux clipboard loop now skips hung tools and tries the next one instead of blocking
  • termstory/tui.py: Updated docstring to document the 2-second timeout bound and fallback behavior

Insights

  • termstory/insights.py: Removed duplicate commit-fetching code block that would have caused KeyError on project_id
  • termstory/insights.py: Added seen_commits set to deduplicate rows appearing in multiple chunk queries when >250 chaotic sessions share the same project
  • termstory/insights.py: Fixed indentation in comment

Tests

  • tests/test_tui.py: Updated test_tui_copy_to_clipboard to mock subprocess.run instead of Popen to match new API
  • tests/test_tui.py: Added test_tui_copy_to_clipboard_timeout to verify TimeoutExpired is handled and OSC 52 fallback fires without hanging

Architecture / Flow

flowchart TD
    A[copy_to_clipboard called] --> B{Platform?}
    B -->|darwin| C[subprocess.run pbcopy timeout=2.0]
    B -->|linux| D[Loop: xclip, xsel, wl-copy]
    D --> E[subprocess.run cmd timeout=2.0]
    E -->|success| F[Break]
    E -->|TimeoutExpired/FileNotFoundError/CalledProcessError| G[Continue to next tool]
    B -->|win32| H[subprocess.run clip timeout=2.0]
    
    C --> I[OSC 52 fallback]
    F --> I
    G --> I
    H --> I
    
    I --> J[super().copy_to_clipboard]
    J --> K[Done]
Loading

Fixes

Testing

Run the clipboard tests to verify timeout handling and fallback behavior:

pytest tests/test_tui.py::test_tui_copy_to_clipboard -v
pytest tests/test_tui.py::test_tui_copy_to_clipboard_timeout -v

Notes

The OSC 52 fallback (super().copy_to_clipboard) always runs regardless of subprocess timeout or failure, ensuring clipboard operations work over SSH terminals where system clipboard daemons may be unavailable or hung. The 2-second timeout prevents indefinite UI thread blocking while still allowing reasonable time for clipboard daemons to respond.

@bitflicker64

Copy link
Copy Markdown
Owner

/lgtm /approve /triage

@git-miku git-miku Bot added the lgtm This PR has been approved by a maintainer label Jul 19, 2026

@git-miku git-miku Bot left a comment

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.

Approved via Miku.

@git-miku git-miku Bot added triage/triaged This PR has been triaged by a maintainer and removed needs-triage Awaiting human triage — run /triage to mark as triaged labels Jul 19, 2026
@git-miku

git-miku Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

miku /lgtm

✅ LGTM! Label added by @bitflicker64.

Use :miku /lgtm cancel to retract.


miku /approve

✅ Approved! 🎉


miku /triage

✅ Marked as triaged (triage/triaged applied, needs-triage removed).

@bitflicker64

Copy link
Copy Markdown
Owner

/rewrite

@git-miku

git-miku Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

miku /rewrite

✅ PR description rewritten! Check the PR body above.

Preview

Summary

Fixes TUI freeze caused by clipboard daemon hangs (issue #294) by replacing unbounded subprocess.Popen calls with subprocess.run(timeout=2.0) across all platforms. Also removes duplicate commit-fetching logic in detect_late_night_chaotic_sessions that would have caused a KeyError on project_id and adds deduplication for chunked queries.

Changes

Core

  • termstory/tui.py: Replaced all subprocess.Popen + communicate() calls with subprocess.run(..., timeout=2.0, check=True) in copy_to_clipboard method
  • termstory/tui.py: Changed exception handling from bare Exception to specific types (subprocess.CalledProcessError, subprocess.TimeoutExpired, OSError)
  • termstory/tui.py: Linux clipboard loop now skips hung tools and tries the next one instead of blocking
  • termstory/tui.py: Updated docstring to document the 2-second timeout bound and fallback behavior

Insights

  • termstory/insights.py: Removed duplicate commit-fetching code block that would have caused KeyError on project_id
  • termstory/insights.py: Added seen_commits set to deduplicate rows appearing in multiple chunk queries when >250 chaotic sessions share the same project
  • termstory/insights.py: Fixed indentation in comment

Tests

  • tests/test_tui.py: Updated test_tui_copy_to_clipboard to mock subprocess.run instead of Popen to match new API
  • tests/test_tui.py: Added test_tui_copy_to_clipboard_timeout to verify TimeoutExpired is handled and OSC 52 fallback fires without hanging

Architecture / Flow

flowchart TD
    A[copy_to_clipboard called] --> B{{Platform?}}
    B -->|darwin| C[subprocess.run pbcopy timeout=2.0]
    B -->|linux| D[Loop: xclip, xsel, wl-copy]
    D --> E[subprocess.run cmd timeout=2.0]
    E -->|success| F[Break]
    E -->|TimeoutExpired/FileNotFoundError/CalledProcessError| G[Continue to next tool]
    B -->|win32| H[subprocess.run clip timeout=2.0]
    
    C --> I[OSC 52 fallback]
    F --> I
    G --> I
    H --> I
    
    I --> J[super().copy_to_clipboard]
    J --> K[Done]
Loading

Fixes

Testing

Run the clipboard tests to verify timeout handling and fallback behavior:

pytest tests/test_tui.py::test_tui_copy_to_clipboard -v
pytest tests/test_tui.py::test_tui_copy_to_clipboard_timeout -v

Notes

The OSC 52 fallback (super().copy_to_clipboard) always runs regardless of subprocess timeout or failure, ensuring clipboard operations work over SSH terminals where system clipboard daemons may be unavailable or hung. The 2-second timeout prevents indefinite UI thread blocking while still allowing reasonable time for clipboard daemons to respond.

@bitflicker64

Copy link
Copy Markdown
Owner

/LABEL good-pr

@git-miku git-miku Bot added the good-pr good-pr label Jul 19, 2026
@git-miku

git-miku Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

miku /label

🏷️ Added label good-pr.

@bitflicker64
bitflicker64 merged commit 3946308 into bitflicker64:main Jul 19, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ai area/ai area/tui area/tui ECSoC26-L2 ECSoC26 good-pr good-pr kind/tests kind/tests lgtm This PR has been approved by a maintainer size/M PR changes ~109 lines triage/triaged This PR has been triaged by a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Medium] tui.py: clipboard Popen.communicate has no timeout — can freeze the TUI

2 participants