vxcontrol/pentagi · error

failed to put terminal log (edit file cmd): %w

Error message

failed to put terminal log (edit file cmd): %w

What it means

After a successful edit, EditFile logs a styled success line to the terminal log via t.tlp.PutMsg (termlog stdin entry). This error wraps that logging call failing — the edit itself succeeded and the file is already updated, but recording the action in the termlog store failed. It typically indicates a database/persistence problem in the terminal log pipeline rather than anything wrong with the file.

Source

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

	current, err := t.readFileFromContainer(ctx, flowID, path)
	if err != nil {
		return "", fmt.Errorf("failed to read current content of %s before editing: %w", path, err)
	}

	newContent, hunksApplied, err := ApplyUnifiedDiff(current, diffText)
	if err != nil {
		return "", fmt.Errorf("failed to apply diff to %s: %w", path, err)
	}

	if err := t.writeFileToContainer(ctx, flowID, path, newContent); err != nil {
		return "", fmt.Errorf("failed to write edited content of %s: %w", path, err)
	}

	successMsg := fmt.Sprintf("Applied %d diff hunk(s) to %s (%d -> %d bytes)", hunksApplied, path, len(current), len(newContent))
	styledMsg := fmt.Sprintf("%s%s%s%s", ansiColorSystemMsg, successMsg, ansiColorReset, ansiLineTerminator)
	if _, err := t.tlp.PutMsg(ctx, database.TermlogTypeStdin, styledMsg, t.containerID, t.taskID, t.subtaskID); err != nil {
		return "", fmt.Errorf("failed to put terminal log (edit file cmd): %w", err)
	}

	return successMsg, nil
}

// PrimaryTerminalName returns the docker container name for a flow's primary
// terminal, namespaced by the configured tenant.
//
//	"pentagi-terminal-1"       (single instance)
//	"acme-pentagi-terminal-1"  (TENANT_ID=acme)
//
// The tenant goes in FRONT of the well-known prefix on purpose: the installer's
// volume garbage collector force-removes anything matching
// HasPrefix("pentagi-terminal-") && HasSuffix("-data"), so a leading tenant
// segment keeps one tenant's objects outside another tenant's sweep. A tenant
// segment placed after the prefix would stay inside it and be destroyed.
func PrimaryTerminalName(tenantPrefix string, flowID int64) string {
	return fmt.Sprintf("%s%s%d", tenantPrefix, PrimaryTerminalNamePrefix, flowID)

View on GitHub (pinned to ea665308ba)

Solutions

  1. Check backend logs and Postgres health (connection count, `SELECT 1`) for the underlying cause
  2. Retry the EditFile action only if you must see the termlog entry — the file edit already took effect, so re-running will apply a diff against already-edited content; prefer verifying with ReadFile instead
  3. Increase DB connection pool limits / fix connectivity if pool exhaustion is the cause
  4. Consider degrading gracefully: treat termlog write failure as non-fatal so file edits are not reported as errors
  5. Use a non-canceled context (the edit already succeeded, so an upstream cancellation shouldn't fail the whole call)

Example fix

// before
if _, err := t.tlp.PutMsg(ctx, database.TermlogTypeStdin, styledMsg, t.containerID, t.taskID, t.subtaskID); err != nil {
    return "", fmt.Errorf("failed to put terminal log (edit file cmd): %w", err)
}
// after
logCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 5*time.Second)
if _, err := t.tlp.PutMsg(logCtx, database.TermlogTypeStdin, styledMsg, t.containerID, t.taskID, t.subtaskID); err != nil {
    cancel()
    t.logger.Warn().Err(err).Str("path", path).Msg("edit succeeded but termlog write failed")
    return successMsg, nil // file edit already applied; don't fail the tool call
}
cancel()
Defensive patterns

Strategy: try-catch

Try / catch

msg, err := term.EditFile(ctx, flowID, path, diff)
if err != nil && strings.Contains(err.Error(), "failed to put terminal log") {
    // The file edit already succeeded; verify with ReadFile instead of retrying the edit.
    current, rerr := term.ReadFile(ctx, flowID, "", path)
    _ = current; _ = rerr
}

Prevention

When it happens

Trigger: PutMsg failing because the PostgreSQL termlog insert errored: DB down, connection pool exhausted, context canceled/deadline exceeded mid-insert, or a failed transaction.

Common situations: Database restart or network blip between backend and Postgres during a flow; context timeout from an upstream request canceled by the client while the write was already committed; pool saturation under heavy agent concurrency.

Related errors


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