vxcontrol/pentagi · error

invalid ToolcallStatus: %s

Error message

invalid ToolcallStatus: %s

What it means

ToolcallStatus tracks a tool call's lifecycle: received, running, finished, failed. ToolcallStatus.Valid() rejects any other string with this error, and the GORM Validate callback enforces the whitelist whenever a Toolcall row is inserted or updated.

Source

Thrown at backend/pkg/server/models/toolcalls.go:32

	ToolcallStatusRunning  ToolcallStatus = "running"
	ToolcallStatusFinished ToolcallStatus = "finished"
	ToolcallStatusFailed   ToolcallStatus = "failed"
)

func (s ToolcallStatus) String() string {
	return string(s)
}

// Valid is function to control input/output data
func (s ToolcallStatus) Valid() error {
	switch s {
	case ToolcallStatusReceived,
		ToolcallStatusRunning,
		ToolcallStatusFinished,
		ToolcallStatusFailed:
		return nil
	default:
		return fmt.Errorf("invalid ToolcallStatus: %s", s)
	}
}

// Validate is function to use callback to control input/output data
func (s ToolcallStatus) Validate(db *gorm.DB) {
	if err := s.Valid(); err != nil {
		db.AddError(err)
	}
}

// Toolcall is model to contain tool call information
// nolint:lll
type Toolcall struct {
	ID              uint64         `form:"id" json:"id" validate:"min=0,numeric" gorm:"type:BIGINT;NOT NULL;PRIMARY_KEY;AUTO_INCREMENT"`
	CallID          string         `form:"call_id" json:"call_id" validate:"required" gorm:"type:TEXT;NOT NULL"`
	Status          ToolcallStatus `form:"status" json:"status" validate:"valid,required" gorm:"type:TOOLCALL_STATUS;NOT NULL;default:'received'"`
	Name            string         `form:"name" json:"name" validate:"required" gorm:"type:TEXT;NOT NULL"`
	Args            string         `form:"args" json:"args" validate:"required" gorm:"type:JSON;NOT NULL"`

View on GitHub (pinned to ea665308ba)

Solutions

  1. Restrict all transitions to received, running, finished, failed using the models.ToolcallStatus* constants from backend/pkg/server/models/toolcalls.go.
  2. Map outcome-specific states (timeout, cancelled) onto ToolcallStatusFailed with a reason stored in the result field, not as new status strings.
  3. Default new Toolcall structs to models.ToolcallStatusReceived (matches the DB default 'received') so the zero value never reaches Valid().
  4. Validate at the executor boundary — call Valid() before each Create/Update so a bad status fails fast with context.

Example fix

// before
call.Status = models.ToolcallStatus("success")
// after
call.Status = models.ToolcallStatusFinished // "finished"
Defensive patterns

Strategy: validation

Validate before calling

func isValidToolcallStatus(v string) bool {
	switch models.ToolcallStatus(v) {
	case models.ToolcallStatusReceived, models.ToolcallStatusRunning,
		models.ToolcallStatusFinished, models.ToolcallStatusFailed:
		return true
	}
	return false
}

Type guard

func asToolcallStatus(v string) (models.ToolcallStatus, bool) {
	s := models.ToolcallStatus(v)
	return s, s.Valid() == nil
}

Try / catch

if err := status.Valid(); err != nil {
	return fmt.Errorf("illegal toolcall status %q: %w", status, err)
}

Prevention

When it happens

Trigger: Updating a tool call record with a status outside the four-value set (e.g. 'pending', 'success', 'error', 'timeout'); agent orchestration code building statuses dynamically; deserializing tool call state from JSON produced by another system with a different status vocabulary.

Common situations: Synonym drift ('success' vs 'finished', 'error' vs 'failed') when wiring new tool executors; timeouts implemented as a distinct status string instead of setting failed; leftover zero-value ('') Toolcall.Status on structs created without defaulting.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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