multica-ai/multica · warning
value cannot be null (use DELETE to remove a key)
Error message
value cannot be null (use DELETE to remove a key)
What it means
validateIssueMetadataValue explicitly rejects JSON null as a metadata value. Flat KV metadata V1 stores only primitive scalars (string, number, bool); removal is expressed through the DELETE verb, not through nulling the key, so a null PUT would create an ambiguous 'key exists with no value' state. The error message points the caller at the correct verb.
Source
Thrown at server/internal/handler/issue_metadata.go:70
return nil
}
// validateIssueMetadataValue rejects anything other than a primitive JSON
// scalar. Null, arrays, and objects are not allowed — the V1 surface is
// flat KV. Removing a key uses DELETE, not a null value.
func validateIssueMetadataValue(raw json.RawMessage) error {
if len(raw) == 0 {
return errors.New("value is required")
}
var v any
if err := json.Unmarshal(raw, &v); err != nil {
return fmt.Errorf("value must be valid JSON: %w", err)
}
switch v.(type) {
case string, bool, float64:
return nil
case nil:
return errors.New("value cannot be null (use DELETE to remove a key)")
default:
return errors.New("value must be a primitive: string, number, or bool")
}
}
// parseIssueMetadata decodes the JSONB bytes from db.Issue.Metadata into a
// Go map suitable for response serialization. Empty or unparseable blobs
// degrade to an empty map — the DB CHECK guarantees object shape, so this
// path is only hit on rows somehow predating the migration. Shared with the
// service-layer broadcast rendering (service.IssueToMap) so both ways of
// describing an issue agree on what an unset bag looks like on the wire.
func parseIssueMetadata(raw []byte) map[string]any {
return util.JSONObjectOrEmpty(raw)
}
// parseMetadataFilterParam reads the `metadata` query parameter (a JSON
// object) and returns it as the JSONB filter blob passed to ListIssues /
// CountIssues / ListOpenIssues. Empty input means "no filter" and returnsView on GitHub (pinned to 2c0912b6ec)
Solutions
- To remove the key: call DELETE /issues/{id}/metadata/{key} instead of PUTting null.
- Map client-side null/undefined to 'do not send this key at all' (skip the request) or to empty string '' if you mean 'present but empty'.
- Strip null-valued entries before serializing the metadata payload.
- Add a client unit test that no metadata request body ever contains "value": null.
Example fix
// before
if (newValue === null) {
await fetch(url, {method: 'PUT', body: JSON.stringify({value: null})});
}
// after
if (newValue === null) {
await fetch(url, {method: 'DELETE'});
} Defensive patterns
Strategy: type-guard
Validate before calling
function metadataAction(key, value) {
if (value === null) {
return { method: 'DELETE', url: metadataUrl(key) };
}
return { method: 'PUT', url: metadataUrl(key), body: JSON.stringify({ value }) };
} Type guard
const isPrimitiveValue = (v) => v === null || typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean'; // note: null is a valid JS value but maps to DELETE, not to a PUT body
Prevention
- Translate null/undefined to DELETE at the client boundary instead of serializing it.
- Use a strict serializer that throws on null values in metadata payloads rather than emitting them.
- Document the DELETE-to-remove convention in the client SDK so contributors don't reinvent null-clearing.
When it happens
Trigger: PUT /issues/{id}/metadata/{key} with body {"value":null}. Common when client code sets a field to null to 'clear' it and serializes the whole object; or when a patch-style UI sends null for empty form fields.
Common situations: JSON serializers emitting null for unset fields (Golang pointers, TS nullable fields); CRUD clients reusing update semantics where null means clear; spreadsheet-style editors distinguishing empty ('') from missing (null) and sending null for missing.
Related errors
- key is required
- key must match ^[a-zA-Z_][a-zA-Z0-9_.-]{0,63}$
- value is required
- value must be a primitive: string, number, or bool
- invalid limit
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/5ea77c64bb27bba1.
Report an issue: GitHub.