Skip to content
Back to skills

497 2026 01 27 17 41 42 Insert Before Stream Fix 52612d98

ASecurity

self.mount(widget, before=self._current_stream) elif self._insertion_anchor is not None: self.mount(widget, before=self._insertion_anchor) else: self.mount(widget) if self._auto_scroll: self.scroll_end(animate=False) ``` **Acceptance Test:** Tool panel arriving after `end_stream()` appears before the finalized agent response, not at bottom. **Dependencies:** Task 1 **Milestone:** M2 --- **Summary:** Reset anchor when starting a new stream to prevent stale positioning. **Files:** `src/tunacode...

  • 9 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added June 1, 2026
devopspythongotestinggitapi

Works with

  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned June 1, 2026

npx -y skills add tools-only/X-Skills --skill 497-2026-01-27_17-41-42_insert-before-stream-fix_52612d98 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 497 2026 01 27 17 41 42 Insert Before Stream Fix 52612d98?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 497 2026 01 27 17 41 42 Insert Before Stream Fix 52612d98
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-497-2026-01-27-17-41-42-insert-before-stream-fix-5/badge)](https://www.skillsdirectory.com/skills/tools-only-497-2026-01-27-17-41-42-insert-before-stream-fix-5)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
title: "Insert Before Stream Race Condition Fix – Plan"
phase: Plan
date: "2026-01-27_17-41-42"
owner: "claude-opus-4-5"
parent_research: "memory-bank/research/2026-01-27_insert-before-stream-investigation.md"
git_commit_at_plan: "5efc5423"
tags: [plan, ui, streaming, race-condition, coding]
---

## Goal

- Fix the race condition where tool panels append to the end instead of inline when tools complete after stream cancellation or end.

## Non-Goals

- No deployment/ops changes
- No observability/metrics additions
- No new dependencies

## Scope & Assumptions

**In Scope:**
- Fix `insert_before_stream()` to correctly position tool panels even after stream ends
- Track insertion point when stream ends so late-arriving tool panels insert correctly
- Handle cancel scenario where tools complete post-cancel

**Out of Scope:**
- Tool cancellation signal propagation (separate concern)
- Throttling/batching of tool panels (LOW priority per research)

**Assumptions:**
- Textual's `mount(before=widget)` works correctly when widget exists
- Tool panels arrive via `post_message()` which is async (confirmed in research)
- Single-threaded event loop; no concurrent message handling

## Deliverables

- Modified `ChatContainer` class with insertion point tracking
- Updated `insert_before_stream()` to use tracked position
- Updated `end_stream()` and `cancel_stream()` to preserve insertion context

## Readiness

- Research complete with flow analysis and root cause identified
- Code locations verified in `chat.py` (lines 137-268)
- No external dependencies required

## Milestones

- **M1:** Insertion point tracking infrastructure
- **M2:** Fix `insert_before_stream()` logic
- **M3:** Basic test scenario (manual verification)

## Work Breakdown (Tasks)

### Task 1: Add insertion anchor tracking to ChatContainer
**Summary:** Track the last widget before which tool panels should insert, persisting after stream ends.

**Files:** `src/tunacode/ui/widgets/chat.py`

**Changes:**
1. Add `_insertion_anchor: Widget | None` attribute in `__init__`
2. In `start_stream()`: set `_insertion_anchor = None` (panels go before stream widget)
3. In `end_stream()`: capture the finalized `_current_stream` widget as `_insertion_anchor` BEFORE setting `_current_stream = None`
4. In `cancel_stream()`: capture widget reference before removal, set `_insertion_anchor` to next sibling if exists

**Acceptance Test:** After calling `end_stream()`, `_insertion_anchor` holds reference to the finalized message widget.

**Dependencies:** None
**Milestone:** M1

---

### Task 2: Update insert_before_stream() to use insertion anchor
**Summary:** Modify logic to insert before anchor when stream is None but anchor exists.

**Files:** `src/tunacode/ui/widgets/chat.py`

**Changes:**
```python
def insert_before_stream(self, renderable: RenderableType) -> None:
    widget = Static(renderable)
    widget.add_class("chat-message")

    if self._current_stream is not None:
        # Active stream: insert before streaming widget
        self.mount(widget, before=self._current_stream)
    elif self._insertion_anchor is not None:
        # Stream ended: insert before the finalized message
        self.mount(widget, before=self._insertion_anchor)
    else:
        # No context: append
        self.mount(widget)

    if self._auto_scroll:
        self.scroll_end(animate=False)
```

**Acceptance Test:** Tool panel arriving after `end_stream()` appears before the finalized agent response, not at bottom.

**Dependencies:** Task 1
**Milestone:** M2

---

### Task 3: Clear insertion anchor on new request
**Summary:** Reset anchor when starting a new stream to prevent stale positioning.

**Files:** `src/tunacode/ui/widgets/chat.py`

**Changes:**
1. In `start_stream()`: set `_insertion_anchor = None` at the start
2. In `clear()`: set `_insertion_anchor = None`

**Acceptance Test:** New request does not use stale anchor from previous request.

**Dependencies:** Task 1
**Milestone:** M2

---

### Task 4: Handle cancel scenario insertion anchor
**Summary:** On cancel, preserve insertion context for late-arriving tool panels.

**Files:** `src/tunacode/ui/widgets/chat.py`

**Changes:**
1. In `cancel_stream()`: before removing `_current_stream`, find its next sibling
2. Set `_insertion_anchor` to next sibling (or None if last child)
3. Alternative: Keep `_current_stream` hidden instead of removing? (research: simpler to just track position)

**Design Decision:** Track position via index, not sibling reference. Siblings can be fragile.

Better approach:
```python
def cancel_stream(self) -> None:
    if self._current_stream is not None:
        # Find the index of the stream widget
        children = list(self.children)
        try:
            idx = children.index(self._current_stream)
            # Anchor is widget that will be at this position after removal
            if idx > 0:
                self._insertion_anchor = children[idx - 1]
        except ValueError:
            pass
        self._current_stream.remove()
        self._current_stream = None
    self.remove_class("streaming")
```

Wait - this anchors to widget BEFORE, but we want to insert AFTER it. Let me reconsider.

**Revised approach:** Use `after=` parameter for mount instead:

```python
def cancel_stream(self) -> None:
    if self._current_stream is not None:
        children = list(self.children)
        try:
            idx = children.index(self._current_stream)
            if idx > 0:
                self._insertion_after_anchor = children[idx - 1]
        except ValueError:
            pass
        self._current_stream.remove()
        self._current_stream = None
    self.remove_class("streaming")
```

Then in `insert_before_stream()`:
```python
elif self._insertion_after_anchor is not None:
    self.mount(widget, after=self._insertion_after_anchor)
```

**Acceptance Test:** Tool panel arriving after `cancel_stream()` appears at the position where streaming was occurring.

**Dependencies:** Task 2
**Milestone:** M2

---

### Task 5: Manual verification
**Summary:** Verify fix with real tool calls in TUI.

**Files:** None (manual testing)

**Steps:**
1. Start tunacode TUI
2. Run request that triggers tool (e.g., "read file X")
3. Wait for tool panel to appear inline during streaming
4. Cancel mid-stream (Ctrl+C or escape)
5. Observe: any late tool panels should NOT appear at bottom

**Acceptance Test:** Visual confirmation that tool panels maintain correct ordering in all scenarios.

**Dependencies:** Tasks 1-4
**Milestone:** M3

## Risks & Mitigations

| Risk | Likelihood | Impact | Mitigation |
|------|------------|--------|------------|
| Textual `after=` behavior differs from expected | Low | Medium | Verify Textual docs; test in isolation |
| Removed widget reference causes issues | Low | Low | Clear anchor when widget removed by other means |
| Stale anchor persists across sessions | Low | Low | Clear in `clear()` and `start_stream()` |

## Test Strategy

- **Task 2 acceptance:** Create unit test that mounts ChatContainer, starts stream, ends stream, then calls `insert_before_stream()` and verifies widget order.

Single test file: `tests/ui/test_chat_container.py`

## References

- Research doc: `memory-bank/research/2026-01-27_insert-before-stream-investigation.md`
- Code: `src/tunacode/ui/widgets/chat.py:244-261` (current implementation)
- Textual mount docs: https://textual.textualize.io/api/widget/#textual.widget.Widget.mount

## Final Gate

- **Plan path:** `memory-bank/plan/2026-01-27_17-41-42_insert-before-stream-fix.md`
- **Milestone count:** 3
- **Tasks ready for coding:** 5

**Next command:** `/context-engineer:execute "memory-bank/plan/2026-01-27_17-41-42_insert-before-stream-fix.md"`

Files in this skill

  • README.md1.1 KB
  • skill.md7.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.