fix(agents): prevent NOT NULL violation on promoted columns during update

Frontend sent null for empty emoji (emoji.trim() || null), which violated
the NOT NULL constraint on the promoted emoji column (migration 000037).
This caused all agent saves to fail with 500 when emoji was unset.

- Frontend: send empty string instead of null for emoji field
- Backend (PG + SQLite): add null-coercion for all promoted NOT NULL
  columns — TEXT (emoji, agent_description, thinking_level) coerce to "",
  INT (skill_nudge_interval, max_tokens) coerce to 0
This commit is contained in:
viettranx committed 2026-04-12 14:31:51 +07:00
1 parent 28d29ba046
commit fbfae1e618
3 files changed
+25 -7

No files matched your search

+11 -2
View File
@@ -190,8 +190,17 @@ func (s *PGAgentStore) Update(ctx context.Context, id uuid.UUID, updates map[str
}
// Coerce NOT NULL columns: null → default to prevent constraint violations.
if v, ok := updates["skill_nudge_interval"]; ok && v == nil {
updates["skill_nudge_interval"] = 0
// Promoted TEXT columns (migration 000037): null → empty string.
for _, col := range []string{"emoji", "agent_description", "thinking_level"} {
if v, ok := updates[col]; ok && v == nil {
updates[col] = ""
}
}
// Promoted INT columns: null → 0.
for _, col := range []string{"skill_nudge_interval", "max_tokens"} {
if v, ok := updates[col]; ok && v == nil {
updates[col] = 0
}
}
// NOT NULL JSONB columns: null → empty object.
for _, col := range []string{"chatgpt_oauth_routing", "reasoning_config", "workspace_sharing", "shell_deny_groups", "kg_dedup_config"} {
+12 -3
View File
@@ -133,9 +133,18 @@ func (s *SQLiteAgentStore) Update(ctx context.Context, id uuid.UUID, updates map
return nil
}
// Coerce NOT NULL int columns: null → default to prevent constraint violations.
if v, ok := updates["skill_nudge_interval"]; ok && v == nil {
updates["skill_nudge_interval"] = 0
// Coerce NOT NULL columns: null → default to prevent constraint violations.
// Promoted TEXT columns: null → empty string.
for _, col := range []string{"emoji", "agent_description", "thinking_level"} {
if v, ok := updates[col]; ok && v == nil {
updates[col] = ""
}
}
// Promoted INT columns: null → 0.
for _, col := range []string{"skill_nudge_interval", "max_tokens"} {
if v, ok := updates[col]; ok && v == nil {
updates[col] = 0
}
}
// Unset existing default before setting a new one (scoped to same tenant).
@@ -81,8 +81,8 @@ export function AgentOverviewTab({ agent, onUpdate, heartbeat, onManageCodexPool
tools_config: toolsEnabled
? { profile: tools.profile, allow: tools.allow, deny: tools.deny, alsoAllow: tools.alsoAllow, byProvider: tools.byProvider }
: {},
// Promoted fields sent at top level
emoji: emoji.trim() || null,
// Promoted fields sent at top level (NOT NULL columns — send "" not null)
emoji: emoji.trim(),
self_evolve: selfEvolve,
skill_evolve: skillEvolve,
skill_nudge_interval: skillEvolve ? skillNudgeInterval : 15,