vxcontrol/pentagi · error

failed to update tool call log failed result: %w

Error message

failed to update tool call log failed result: %w

What it means

UpdateLogFailed marks a tool call as failed via db.UpdateToolcallFailedResult. Same wrapping pattern as the success path: the thrown error is the raw database error wrapped with context, usually sql.ErrNoRows for an unknown/stale toolcall ID or a transient connection failure.

Source

Thrown at backend/pkg/controller/tclog.go:108

	w.pub.ToolCallLogUpdated(ctx, tc)

	return nil
}

func (w *flowToolCallLogWorker) UpdateLogFailed(
	ctx context.Context,
	id int64,
	result string,
	durationSeconds float64,
) error {
	tc, err := w.db.UpdateToolcallFailedResult(ctx, database.UpdateToolcallFailedResultParams{
		Result:          result,
		DurationSeconds: durationSeconds,
		ID:              id,
	})
	if err != nil {
		return fmt.Errorf("failed to update tool call log failed result: %w", err)
	}

	w.pub.ToolCallLogUpdated(ctx, tc)

	return nil
}

func (w *flowToolCallLogWorker) GetLog(ctx context.Context, id int64) (database.Toolcall, error) {
	tc, err := w.db.GetFlowToolcall(ctx, database.GetFlowToolcallParams{
		ID:     id,
		FlowID: w.flowID,
	})
	if err != nil {
		return database.Toolcall{}, fmt.Errorf("failed to get tool call log: %w", err)
	}

	return tc, nil
}

View on GitHub (pinned to ea665308ba)

Solutions

  1. Check errors.Is(err, sql.ErrNoRows) to detect a stale/unknown toolcall ID
  2. Make sure PutLog errors abort the tool execution path so UpdateLogFailed is never called with an invalid ID
  3. Verify the toolcall row still exists (SELECT via GetLog) before updating
  4. Inspect the wrapped error and DB logs for connection/constraint causes
  5. Treat as non-fatal and log if the row was already finished by a concurrent path

Example fix

// before
err := worker.UpdateLogFailed(ctx, id, errMsg, dur)
if err != nil { panic(err) }
// after
err := worker.UpdateLogFailed(ctx, id, errMsg, dur)
if errors.Is(err, sql.ErrNoRows) {
    log.Warn().Int64("id", id).Msg("toolcall missing; cannot record failure")
} else if err != nil {
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

func canRecordFailure(ctx context.Context, q database.Querier, flowID, id int64) error {
    _, err := q.GetFlowToolcall(ctx, database.GetFlowToolcallParams{ID: id, FlowID: flowID})
    return err // nil means the row exists and failure can be recorded
}

Type guard

func isNoRows(err error) bool { return errors.Is(err, sql.ErrNoRows) }

Try / catch

err := worker.UpdateLogFailed(ctx, id, errMsg, dur)
if isNoRows(err) {
    log.Warn().Int64("id", id).Msg("cannot record failure: toolcall row gone")
    return nil
}
if err != nil {
    return fmt.Errorf("record failure: %w", err)
}

Prevention

When it happens

Trigger: Recording failure for an ID that was never created (e.g. PutLog failed earlier and its error was swallowed), an ID from another flow, or a canceled context / broken connection during the UPDATE.

Common situations: Agent error-handling paths that report failure after a timeout while the DB write already failed upstream; rows cleaned by retention jobs before the failure result arrives.

Related errors


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