wavetermdev/waveterm · error

failed to get terminal scrollback: %w

Error message

failed to get terminal scrollback: %w

What it means

The term_get_scrollback tool's ToolAnyCallback wraps any failure from getTermScrollbackOutput — which resolves the widget id to a full block id and issues TermGetScrollbackLinesCommand over the block's fe route — with this error. The root cause (block not found, no controller, fe route unavailable, RPC timeout after 5s) is preserved with %w; unwrap it to diagnose.

Source

Thrown at pkg/aiusechat/tools_term.go:210

		},
		ToolAnyCallback: func(input any, toolUseData *uctypes.UIMessageDataToolUse) (any, error) {
			parsed, err := parseTermGetScrollbackInput(input)
			if err != nil {
				return nil, err
			}

			lineEnd := parsed.LineStart + parsed.Count
			output, err := getTermScrollbackOutput(
				tabId,
				parsed.WidgetId,
				wshrpc.CommandTermGetScrollbackLinesData{
					LineStart:   parsed.LineStart,
					LineEnd:     lineEnd,
					LastCommand: false,
				},
			)
			if err != nil {
				return nil, fmt.Errorf("failed to get terminal scrollback: %w", err)
			}
			return output, nil
		},
	}
}

type TermCommandOutputToolInput struct {
	WidgetId string `json:"widget_id"`
}

func parseTermCommandOutputInput(input any) (*TermCommandOutputToolInput, error) {
	result := &TermCommandOutputToolInput{}

	if input == nil {
		return nil, fmt.Errorf("widget_id is required")
	}

	inputBytes, err := json.Marshal(input)

View on GitHub (pinned to a4447c1563)

Solutions

  1. Unwrap the error to distinguish id-resolution failure from RPC failure
  2. Verify the widget_id is a valid 8-character prefix of an existing terminal block in the current tab
  3. List current widgets/tabs and retry with a fresh widget_id if the terminal was closed or restarted
  4. Retry shortly after terminal creation — the block frontend route may not be registered yet
  5. Check that the target block is actually a terminal widget with an active controller

Example fix

// before
output, err := getTermScrollbackOutput(tabId, parsed.WidgetId, rpcData)
if err != nil { return nil, fmt.Errorf("failed to get terminal scrollback: %w", err) }
// after
output, err := getTermScrollbackOutput(tabId, parsed.WidgetId, rpcData)
if err != nil {
    return nil, fmt.Errorf("failed to get terminal scrollback for widget %s: %w", parsed.WidgetId, err)
} // then: check widget exists via block list before retrying
Defensive patterns

Strategy: try-catch

Validate before calling

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
fullBlockId, err := wcore.ResolveBlockIdFromPrefix(ctx, tabId, widgetId)
if err != nil {
    return fmt.Errorf("widget %s not found in tab %s: %w", widgetId, tabId, err)
}

Try / catch

output, err := getTermScrollbackOutput(tabId, widgetId, rpcData)
if err != nil {
    if isTimeoutErr(err) {
        // fe route may not be ready yet; retry with backoff
    }
    return fmt.Errorf("term_get_scrollback failed for %s: %w", widgetId, err)
}

Prevention

When it happens

Trigger: term_get_scrollback invoked with a widget_id that cannot be resolved via wcore.ResolveBlockIdFromPrefix (bad prefix, wrong tab, block closed), or TermGetScrollbackLinesCommand failing: terminal block has no frontend controller, block route not connected, or the 5s timeout expires.

Common situations: AI agent using a widget id from a previous session after the terminal was closed/recreated; passing an id that belongs to a different tab; terminal pane still initializing so the fe route isn't registered yet; running headless where the block frontend isn't running.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/c8bbac808a124c7c. Report an issue: GitHub.