') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Fix StreamTurnStream safety and I/O error handling by codesoda · Pull Request #54 · codesoda/bugatti-cli · GitHub
Skip to content

Fix StreamTurnStream safety and I/O error handling - #54

Merged
codesoda merged 2 commits into
mainfrom
issue-group/stream-iterator-safety
Jul 8, 2026
Merged

Fix StreamTurnStream safety and I/O error handling#54
codesoda merged 2 commits into
mainfrom
issue-group/stream-iterator-safety

Conversation

@codesoda

@codesodacodesoda commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

The unsafe raw-pointer aliasing (#21) and the panic! on stream I/O errors (#22) were already removed on main by the async runtime refactor (b3a9add): StreamTurnStream borrows its reader safely, and the read_line error arm returns ProviderError::StreamError and latches the stream as done. What was still missing were the regression tests those issues asked for — this PR adds them:

  • Genericize StreamTurnStream over R: AsyncBufRead + Unpin + Send so the stream can be driven by in-memory readers in tests (no behavior change in production, which still uses BufReader<ChildStdout>).
  • Add regression tests for EOF → SessionCrashed then done-latch, broken-pipe I/O error → StreamError then done-latch (via a FailingReader test double), and caller-level preservation of partial transcripts when the provider stream fails mid-step (execute_steps_provider_stream_io_error).

Step-level context for stream errors (raised in #22) is provided by the executor, which records each failure as a per-step StepResult::ProviderFailed, so the error message itself only needs to identify the provider stream.

Test evidence

  • RUSTFLAGS="-D warnings" cargo test — 247 tests passed.
  • cargo clippy --all-targets -- -D warnings — clean.

Closes#21
Closes#22

…t test assertion
Bounds belong on the impl where they're used; the broken-pipe test now
asserts only on our own error prefix since io::Error's Display text is
platform-dependent.
@codesoda
codesoda merged commit fd78b76 into mainJul 8, 2026
2 checks passed
@codesoda
codesoda deleted the issue-group/stream-iterator-safety branch July 8, 2026 07:32
codesoda added a commit that referenced this pull request Jul 8, 2026
Changelog entries for the init/doctor, layered config, per-test command
overrides, cross-platform release, provider color, update-check deadline,
subprocess lifecycle, stream safety, and tokio migration PRs (#54-#61).
Sign up for freeto 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.

Replace panic! with error return in StreamTurnIterator::next() Fix unsafe raw pointer in StreamTurnIterator

1 participant

@codesoda