gotify/server · error
sort key is not unique
Error message
sort key is not unique
What it means
handleApplicationError maps gorm.ErrDuplicatedKey to this 400 message. It is returned when an INSERT/UPDATE violates a unique constraint — in this codebase, the applications table enforces uniqueness on the sort key column, so two applications cannot share the same sort key.
Source
Thrown at api/application.go:563
name := gen()
if !exist(imgDir + name) {
return name
}
}
}
func ValidApplicationImageExt(ext string) bool {
switch strings.ToLower(ext) {
case ".gif", ".png", ".jpg", ".jpeg":
return true
default:
return false
}
}
func handleApplicationError(ctx *gin.Context, err error) {
if errors.Is(err, gorm.ErrDuplicatedKey) {
ctx.AbortWithError(400, errors.New("sort key is not unique"))
} else {
ctx.AbortWithError(500, err)
}
}
View on GitHub (pinned to 14bfc25627)
Solutions
- Send a unique sort key (or omit it if the API can default/append to the end)
- Query existing sort keys first and pick max+1 (or a gap) before creating
- Retry with a different key if racing with concurrent creations
- Add server-side logic to auto-assign the next free sort key instead of trusting client input
Example fix
// before
POST /applications {"name":"App","sort_key":1} // 1 already used
// after
const max = Math.max(...apps.map(a => a.sort_key), 0);
POST /applications {"name":"App","sort_key": max + 1} Defensive patterns
Strategy: validation
Validate before calling
const keys = apps.map(a => a.sort_key);
const nextKey = Math.max(0, ...keys) + 1;
await api.post('/applications', {name, sort_key: nextKey}); Try / catch
try {
await api.post('/applications', payload);
} catch (e) {
if (e.response?.status === 400 && e.response?.data?.includes('sort key is not unique')) {
payload.sort_key = Date.now(); // or recompute a free key and retry once
} else throw e;
} Prevention
- Never hardcode sort keys (0/1) for multiple records
- Fetch existing sort keys and compute max+1 before creating
- Handle 400 'sort key is not unique' by recomputing and retrying, since races are possible
- Prefer server-side auto-assignment of sort keys over client-supplied values
When it happens
Trigger: CreateApplication called with a SortKey value that already exists on another application row (unique index hit, GORM translates the driver's duplicate-key error to gorm.ErrDuplicatedKey).
Common situations: Clients hardcoding sort values like 0 or 1 for every new application; concurrent requests picking the same sort key; imports/migrations that reuse existing sort keys; after a unique index was newly added and legacy rows already collide.
Related errors
- cannot delete internal application
- file with key 'file' must be present
- file must be an image
- invalid file extension
- invalid id
AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05).
Data as JSON: /api/errors/aa2dedd3385637af.
Report an issue: GitHub.