fix(git): stream push output to avoid spurious 30s timeout (#963)#1531
fix(git): stream push output to avoid spurious 30s timeout (#963)#1531ousamabenyounes wants to merge 1 commit into
Conversation
📊 Automated PR Analysis
SummaryFixes a critical timeout issue where Review Checklist
Linked issues: #963 Analyzed automatically by wshm · This is an automated analysis, not a human review. |
689e2e4 to
f579fa8
Compare
|
Hello @ousamabenyounes , thanks for addressing this. The fix is not correct because loose of filtering, to fix this we need to use run_streaming + FilterMode::Streaming existing infrastructure that has been added in 0.37.0 for streaming commands. |
f579fa8 to
a402997
Compare
|
Thanks for the steer @aeppling — rewritten on top of
Six new unit tests in
|
|
Hey @ousamabenyounes , good follow-up, the rewrite correctly uses run_streaming + FilterMode::Streaming as requested and the filtering intent is right. Architectural issueGitPushStreamFilter implements StreamFilter directly, but stream.rs already has a pattern for this: BlockStreamFilter<H: BlockHandler>. The problem is BlockStreamFilter cannot be reused here because its default behavior is DROP (only collected blocks are emitted), while git push needs the opposite: default KEEP, suppress only noise. The fix would be to add LineStreamFilter<H: LineHandler> to stream.rs as the counterpart to BlockStreamFilter , default KEEP, suppress only noise ; then rewrite GitPushStreamFilter as a LineHandler. Mirror BlockHandler conventions for the trait methods. This will allow for futur stream command to reuse this behavior. Additional fixes:
Details
|
## Problem (rtk-ai#963) `rtk git push` reportedly times out: users see `bash tool terminated command after exceeding timeout 30000 ms` while plain `git push` to the same remote completes fine. P1-critical because every Claude Code git push goes through rtk. ## Root cause `run_push` used `cmd.stdin(Stdio::inherit()).output()`. `Command::output()` captures both stdout and stderr until the child exits. Git push prints its progress (`Counting objects` / `Compressing objects` / `Writing objects`) to stderr and may prompt for SSH passphrases or HTTPS credentials. With stderr captured, Claude Code's bash tool saw zero output for 30+ seconds and killed the command — exactly the 30000 ms message in the issue. ## Fix Rewrite `run_push` on top of the streaming infrastructure that already exists for this exact purpose (`stream::run_streaming` + `FilterMode::Streaming`, added in 0.37.0). Add a counterpart to `BlockStreamFilter<H: BlockHandler>` in `src/core/stream.rs`: `LineStreamFilter<H: LineHandler>`. Where `BlockStreamFilter` defaults to DROP and emits only collected blocks, `LineStreamFilter` defaults to KEEP and lets handlers opt into dropping noise. Trait surface mirrors `BlockHandler`: - `should_skip(&mut self, line: &str) -> bool` — default false - `observe_line(&mut self, line: &str)` — default no-op - `format_summary(&self, exit_code, raw) -> Option<String>` This lets future streaming commands reuse the line-oriented pattern. `GitPushLineHandler` then becomes a tiny `LineHandler` impl: - `should_skip` drops the high-volume progress phases (Enumerating / Counting / Compressing / Writing objects, Delta compression, Total) and blank lines. - `observe_line` captures the up-to-date sentinel and the first ref update target (e.g. `master`) for the summary. - `format_summary` emits `ok <ref>` / `ok (up-to-date)` / `ok` on success; nothing on failure (raw error lines already flowed through). Stdin is inherited (`StdinMode::Inherit`) so SSH passphrase and HTTPS credential prompts still reach the user. Tracking now records the real raw output and the filtered output. ## Test plan - [x] `cargo fmt --all -- --check` - [x] `cargo clippy --all-targets -- -D warnings` — clean - [x] `cargo test --all` — 1880 passed, 0 failed, 6 ignored - Six unit tests cover the push handler: progress-prefix drop, up-to-date summary, remote message passthrough, no-summary-on-failure, first-ref-wins, and token-savings (>=60% on a representative payload). - Four unit tests cover the new `LineStreamFilter` trait: default-keep-all, skip-drops-matching, summary-propagates-exit-code, observe-only-called-for-kept-lines. ## Notes - Behaviour change: users now see git's native output line-by-line (with progress phases stripped) plus a final `ok <ref>` summary, instead of just the compact summary. This matches plain `git push` more closely and is what the issue reporter expects. - No regression for other filters: `run_pull`, `run_fetch`, `run_clone` are untouched; only `run_push` is modified. Closes rtk-ai#963 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- _Vibe Coded by Ousama Ben Younes_ _Developed With Ora Studio (Claude Code)_ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
a402997 to
be51783
Compare
|
All points addressed in be51783. Architectural — Added ```rust Default is KEEP — handlers opt into dropping noise via `should_skip`. `GitPushLineHandler` is now a small `LineHandler` impl (no direct `StreamFilter` plumbing), and future line-oriented streaming commands can reuse the pattern. Added 4 unit tests in `stream.rs` covering: default-keep-all, skip-drops-matching, summary-propagates-exit-code, observe-only-called-for-kept-lines. Additional fixes:
`cargo fmt`, `cargo clippy --all-targets -- -D warnings`, and `cargo test --all` (1880 passed, 0 failed) green locally. Re-ready for review. |
Problem (#963)
`rtk git push` reportedly times out: users see `bash tool terminated command after exceeding timeout 30000 ms` while plain `git push` to the same remote completes fine. P1-critical because every Claude Code git push goes through rtk.
Root cause
`run_push` used `cmd.stdin(Stdio::inherit()).output()`. `Command::output()` captures both stdout and stderr until the child exits.
Git push prints its progress (`Counting objects` / `Compressing objects` / `Writing objects`) to stderr and may prompt for SSH passphrases or HTTPS credentials. With stderr captured nothing reached the terminal until the process finished, so:
The original code traded interactivity for a one-line compact summary (`ok master`). Push output is small — a handful of lines — so the saving was negligible while the cost (timeout, hidden prompts) was severe.
Fix
Inherit all three streams and use `cmd.status()` instead of `.output()`. Progress and auth prompts now flow through in real time, the bash tool keeps seeing output, and the exit code propagates via the existing `exit_code_from_status` helper. Tracking still records the invocation; raw/filtered token counts deliberately collapse to 0 because we don't capture.
Test plan
Notes
Closes #963
🤖 Generated with Claude Code
Vibe Coded by Ousama Ben Younes
Developed With Ora Studio (Claude Code)