← Blog

Streaming Claude Code: Real-Time Output for Dev Tools

2026-09-14 · 5 min read · SubToAPI Team

When you're building a coding assistant, IDE plugin, or terminal-based dev tool on top of Claude, streaming isn't optional — it's the difference between a tool that feels responsive and one that feels frozen. Code generation responses can run to hundreds or thousands of tokens, and waiting for the full response before showing anything makes even a fast model feel slow.

This article covers the specifics of streaming Claude's output in coding contexts: rendering partial code blocks correctly, handling tool calls mid-stream, and dealing with syntax highlighting on incomplete text. If you're building a CLI, a VS Code extension, or an internal dev tool that talks to Claude, this is the practical playbook.

Why coding tools need streaming specifically

Chat UIs can get away with a loading spinner. Coding tools can't, for a few reasons:

Setting up a streaming request

The mechanics are the same server-sent-events pattern used across Claude integrations, but coding tools have a few extra requirements around buffering and parsing.

async function streamCodeCompletion(prompt) {
  const response = await fetch("https://api.subtoapi.app/v1/messages", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model: "claude-sonnet-4-20250514",
      max_tokens: 4096,
      stream: true,
      messages: [{ role: "user", content: prompt }]
    })
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  let codeBuffer = "";

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split("\n");
    buffer = lines.pop();

    for (const line of lines) {
      if (!line.startsWith("data: ")) continue;
      const event = JSON.parse(line.slice(6));

      if (event.type === "content_block_delta" && event.delta?.text) {
        codeBuffer += event.delta.text;
        renderPartialCode(codeBuffer);
      }
    }
  }
}

The key detail here is codeBuffer — you accumulate the full text separately from rendering it, because syntax highlighters generally need complete lines (or complete tokens) to avoid flickering mid-word highlights.

Rendering partial code without flicker

A common mistake is re-highlighting the entire accumulated buffer on every delta. For a 300-line file, that means re-parsing 300 lines on every single token, which is slow and causes visible jank.

Better approach: highlight only complete lines, and render the current incomplete line as plain text until it closes with a newline.

function renderPartialCode(fullText) {
  const lines = fullText.split("\n");
  const completeLines = lines.slice(0, -1);
  const currentLine = lines[lines.length - 1];

  const highlighted = completeLines.map(highlightLine).join("\n");
  updateEditor(highlighted + "\n" + currentLine);
}

This keeps the highlighter's workload proportional to new content, not total content, and avoids the "everything re-paints every 50ms" problem that makes streamed code feel laggy even though tokens are arriving fast.

Handling tool calls in a streaming coding agent

If your tool lets Claude read files, run commands, or search a codebase, tool use events arrive inside the stream alongside text deltas. You need to detect content_block_start events with type: "tool_use" and switch your UI into a distinct "Claude is running a tool" state rather than trying to render tool JSON as if it were code output.

if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
  showToolIndicator(event.content_block.name);
}
if (event.type === "content_block_stop") {
  hideToolIndicator();
}

This matters more for coding tools than for chat apps, because a multi-step coding agent might interleave three or four tool calls with text in a single response, and users need visual cues for what's happening at each step — "reading config.js", "running tests", "writing patch" — instead of a single undifferentiated spinner.

Cancellation and interruption

Coding tools almost always need a stop button, since generating the wrong refactor for 30 seconds wastes real time. With fetch, this means wiring an AbortController into the request and calling .abort() on user cancellation:

const controller = new AbortController();
fetch(url, { signal: controller.signal, /* ...rest */ });

// on stop button click
controller.abort();

Handle the resulting AbortError gracefully — don't show it as a failure, just stop the stream and leave whatever code was generated so far in the editor, since partial output is often still useful for a developer to read or manually complete.

Where SubToAPI fits

If you're building this kind of tool on your own Claude access rather than a separate Anthropic API account, SubToAPI gives you an HTTPS endpoint (sub_live_... keys) that supports streaming, tool use, and usage metadata out of the box, so the code above works without changing providers. Setup takes a few minutes — see the quickstart or the full streaming reference for event-by-event details. Plans start at €9/month with a free trial at signup.

questions

Does streaming change what Claude generates, or just how fast it appears? Only delivery timing changes. The model generates the same tokens in the same order — streaming just sends each token as it's produced instead of waiting for the full response, which is why interrupted streams still leave valid, readable partial code.

Can I stream tool use and text output in the same response? Yes. A single streamed response can include multiple content blocks — text and tool calls — arriving in sequence. You need to branch your event handling on content_block_start type to render each kind correctly; see tool use for the full event structure.

Why does my streamed code look broken mid-generation? This is almost always a syntax-highlighting issue, not a data issue — highlighters choke on incomplete brackets or strings. Render only complete lines with highlighting and the current in-progress line as plain text, as shown above, to avoid this.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →