vxcontrol/pentagi · error

failed to put terminal log (read file content)

Error message

failed to put terminal log (read file content): %w

What it means

After successfully reading the file contents via readFileFromContainer, ReadFile records the styled content into the terminal transcript with PutMsg (TermlogTypeStdout). This error wraps a failure of that final logging write; the file was read, but the transcript could not be updated, so the read is reported as failed to preserve log integrity.

Solutions

  1. Check database connectivity and health first
  2. Truncate or size-limit content before PutMsg for large files
  3. Sanitize non-UTF8 content before logging
  4. Retry after the DB recovers; the content itself was read successfully

Example fix

// before
_, err = t.tlp.PutMsg(ctx, database.TermlogTypeStdout, styledContent, ...)
// after
styledContent = truncateString(strings.ToValidUTF8(styledContent, "?"), 50000)
_, err = t.tlp.PutMsg(ctx, database.TermlogTypeStdout, styledContent, ...)
Defensive patterns

Strategy: fallback

Validate before calling

info, err := os.Stat(localPathOrKnownSize)
if err == nil && info.Size() > maxLoggableBytes {
    return errors.New("file too large to log; read via direct copy instead")
}

Try / catch

content, err := term.ReadFile(ctx, flowID, path)
if err != nil && strings.Contains(err.Error(), "failed to put terminal log (read file content)") {
    // content was read; failure was persistence — retry or log-and-continue
    log.Warn("stdout termlog write failed for file content", "err", err)
}

Prevention

When it happens

Trigger: PutMsg(ctx, database.TermlogTypeStdout, styledContent, ...) fails when persisting the file content — DB connection failure, oversized payload (large file content), or encoding constraints in the termlog table.

Common situations: Reading very large files whose content exceeds storage limits; PostgreSQL outage or pool exhaustion; binary files producing invalid UTF-8 content.

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/bb3781862ff1c035. Report an issue: GitHub.

Appendix: source

Thrown at backend/pkg/tools/terminal.go:353

	escapedPath := strings.ReplaceAll(path, "'", "'\"'\"'")
	catCommand := fmt.Sprintf("cat '%s'", escapedPath)
	// Format read file command with styling
	styledCommand := fmt.Sprintf("%s $ %s%s%s%s", cwd, ansiColorInputCmd, catCommand, ansiColorReset, ansiLineTerminator)
	_, err := t.tlp.PutMsg(ctx, database.TermlogTypeStdin, styledCommand, t.containerID, t.taskID, t.subtaskID)
	if err != nil {
		return "", fmt.Errorf("failed to put terminal log (read file cmd): %w", err)
	}

	content, err := t.readFileFromContainer(ctx, flowID, path)
	if err != nil {
		return "", err
	}

	// Style file content output
	styledContent := fmt.Sprintf("%s%s%s%s", ansiColorSystemMsg, content, ansiColorReset, ansiLineTerminator)
	_, err = t.tlp.PutMsg(ctx, database.TermlogTypeStdout, styledContent, t.containerID, t.taskID, t.subtaskID)
	if err != nil {
		return "", fmt.Errorf("failed to put terminal log (read file content): %w", err)
	}

	return content, nil
}

// readFileFromContainer copies path out of the flow's container and returns
// its content. It performs no terminal-log writes, so callers that need the
// content only as an intermediate step (e.g. EditFile, before reapplying a
// diff and writing back) don't echo a spurious "cat" transcript entry.
func (t *terminal) readFileFromContainer(ctx context.Context, flowID int64, path string) (string, error) {
	containerName := PrimaryTerminalName(t.tenantPrefix, flowID)

	isRunning, err := t.dockerClient.IsContainerRunning(ctx, t.containerLID)
	if err != nil {
		return "", fmt.Errorf("runtime verification failed: %w", err)
	}
	if !isRunning {
		return "", fmt.Errorf("container runtime is not operational")

View on GitHub (pinned to ea665308ba)