charmbracelet/crush · error
message content is shorter than read bytes: %d < %d
Error message
message content is shorter than read bytes: %d < %d
What it means
RunNonInteractive streams partial message content to stdout, tracking how many bytes were already printed per message (messageReadBytes). When the final message content is shorter than the bytes already emitted — which would make content[readBytes:] panic — this error is returned instead. It indicates the message content changed/shrank between incremental updates, i.e. an internal consistency violation in message assembly.
Source
Thrown at internal/app/app.go:416
if errors.Is(result.err, context.Canceled) || errors.Is(result.err, agent.ErrRequestCancelled) {
slog.Debug("Non-interactive: agent processing cancelled", "session_id", sess.ID)
return nil
}
return fmt.Errorf("agent processing failed: %w", result.err)
}
return nil
case event := <-messageEvents:
msg := event.Payload
if msg.SessionID == sess.ID && msg.Role == message.Assistant && len(msg.Parts) > 0 {
stopSpinner()
content := msg.Content().String()
readBytes := messageReadBytes[msg.ID]
if len(content) < readBytes {
slog.Error("Non-interactive: message content is shorter than read bytes", "message_length", len(content), "read_bytes", readBytes)
return fmt.Errorf("message content is shorter than read bytes: %d < %d", len(content), readBytes)
}
part := content[readBytes:]
// Trim leading whitespace. Sometimes the LLM includes leading
// formatting and intentation, which we don't want here.
if readBytes == 0 {
part = strings.TrimLeft(part, " \t")
}
// Ignore initial whitespace-only messages.
if printed || strings.TrimSpace(part) != "" {
printed = true
fmt.Fprint(output, part)
}
messageReadBytes[msg.ID] = len(content)
}
case <-ctx.Done():
stopSpinner()View on GitHub (pinned to 7944b8e522)
Solutions
- Update crush to the latest version (streaming/assembly fixes land frequently)
- Note the provider/model used; try a different model to see if it's provider-specific
- File a bug with the provider name and SLOG_LEVEL=debug logs (message_length vs read_bytes)
- As a workaround, retry the run; it is usually transient per-response
Defensive patterns
Strategy: try-catch
Try / catch
err := app.RunNonInteractive(ctx, w, prompt, "", "", true, "", false)
if err != nil && strings.Contains(err.Error(), "shorter than read bytes") {
// internal invariant violation: retry once, then report bug
if rerr := app.RunNonInteractive(ctx, w, prompt, "", "", true, "", false); rerr != nil {
return fmt.Errorf("content mismatch persisted; report with debug logs: %w", rerr)
}
return nil
} Prevention
- Keep crush updated — streaming assembly bugs are fixed upstream
- Capture SLOG_LEVEL=debug logs when it happens (message_length vs read_bytes)
- Note provider/model; test an alternate model to isolate provider-specific quirks
When it happens
Trigger: The tracked readBytes for a message ID exceeds len(msg.Content().String()) on the final assistant message — e.g. message parts were replaced/compacted, content was re-rendered shorter, or a provider returned truncated content after partial streaming.
Common situations: Provider streaming quirks where an interim message is later replaced by shorter final content; bugs after provider protocol changes; reasoning/thinking blocks being dropped from content between updates.
Related errors
- failed to start agent processing stream: %w
- agent processing failed: %w
- message content is shorter than read bytes: %d < %d
- unsupported by the running server
- clipboard operations are not supported on this platform
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/caab45749e7feb69.
Report an issue: GitHub.