henrygd/beszel · error

scanner error: %w

Error message

scanner error: %w

What it means

In collect, after streaming a collector command's output through a scanner, any non-EOF error from scanner.Err() is wrapped as 'scanner error: %w'. This means reading the collector's stdout failed mid-stream (I/O error on the pipe) rather than simply finding no valid data (that is errNoValidData).

Source

Thrown at agent/gpu.go:184

	if err := cmd.Start(); err != nil {
		return err
	}

	scanner := bufio.NewScanner(stdout)
	if c.buf == nil {
		c.buf = make([]byte, 0, c.bufSize)
	}
	scanner.Buffer(c.buf, bufio.MaxScanTokenSize)

	for scanner.Scan() {
		hasValidData := c.parse(scanner.Bytes())
		if !hasValidData {
			return errNoValidData
		}
	}

	if err := scanner.Err(); err != nil {
		return fmt.Errorf("scanner error: %w", err)
	}
	return cmd.Wait()
}

// getJetsonParser returns a function to parse the output of tegrastats and update the GPUData map
func (gm *GPUManager) getJetsonParser() func(output []byte) bool {
	// use closure to avoid recompiling the regex
	ramPattern := regexp.MustCompile(`RAM (\d+)/(\d+)MB`)
	gr3dPattern := regexp.MustCompile(`GR3D_FREQ (\d+)%`)
	tempPattern := regexp.MustCompile(`(?:tj|GPU)@(\d+\.?\d*)C`)
	// Orin Nano / NX do not have GPU specific power monitor
	// TODO: Maybe use VDD_IN for Nano / NX and add a total system power chart
	powerPattern := regexp.MustCompile(`(GPU_SOC|CPU_GPU_CV)\s+(\d+)mW|VDD_SYS_GPU\s+(\d+)/\d+`)

	// jetson devices have only one gpu so we'll just initialize here
	gpuData := &system.GPUData{Name: "GPU"}
	gm.GpuDataMap["0"] = gpuData

View on GitHub (pinned to b38fb7dafa)

Solutions

  1. Check the wrapped cause (%w) to distinguish bufio.ErrTooLong / killed process from pipe errors
  2. Increase the scanner buffer if lines are very long
  3. Ignore the error during intentional cancellation/shutdown of collectors
  4. Ensure the GPU tool binaries are stable and not being killed by OOM

Example fix

// before: treat all scanner errors as fatal
if err := scanner.Err(); err != nil {
    return fmt.Errorf("scanner error: %w", err)
}
// after: tolerate cancellation
if err := scanner.Err(); err != nil && ctx.Err() == nil {
    return fmt.Errorf("scanner error: %w", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure the collector command and context are sane before scanning
cmd := exec.CommandContext(ctx, bin, args...)
stdout, err := cmd.StdoutPipe()
if err != nil { return err }

Try / catch

if err := collect(); err != nil {
    if errors.Is(err, context.Canceled) || ctx.Err() != nil {
        return nil // intentional shutdown
    }
    var scanErr *fmt.wrapError
    if errors.As(err, &scanErr) && errors.Is(scanErr, bufio.ErrTooLong) {
        // increase scanner buffer and retry
    }
    return err
}

Prevention

When it happens

Trigger: The bufio.Scanner reading the collector command's stdout returns a read error — the command was killed (context canceled, OOM), the pipe broke, or a scanner token exceeded the buffer size limit.

Common situations: Agent shutdown or GPU_COLLECTOR change cancels the command's context mid-read; extremely long tegrastats/nvidia-smi lines exceeding the scanner buffer; system OOM killing the collector process.

Related errors


AI-assisted analysis of henrygd/beszel@b38fb7dafa (2026-08-31). Data as JSON: /api/errors/3acd87fe8488ad32. Report an issue: GitHub.