multica-ai/multica · warning
key must match ^[a-zA-Z_][a-zA-Z0-9_.-]{0,63}$
Error message
key must match ^[a-zA-Z_][a-zA-Z0-9_.-]{0,63}$ What it means
validateIssueMetadataKey rejects keys that do not match ^[a-zA-Z_][a-zA-Z0-9_.-]{0,63}$: they must start with a letter or underscore, may continue with letters, digits, underscore, dot, or hyphen, and be 1..64 characters total. This keeps metadata keys safe for use as JSONB object members and prevents injection-ish or unbounded key names. The error message helpfully embeds the exact regex so the caller can self-correct.
Source
Thrown at server/internal/handler/issue_metadata.go:50
maxIssueMetadataKeys = 50
)
var issueMetadataKeyRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_.-]{0,63}$`)
// SetIssueMetadataKeyRequest carries the JSON value to write under the key
// named in the URL. Value is a RawMessage so we can preserve numeric vs.
// string typing through to PostgreSQL — once decoded into `any`, JSON
// numbers all collapse to float64 and we'd lose integer fidelity.
type SetIssueMetadataKeyRequest struct {
Value json.RawMessage `json:"value"`
}
func validateIssueMetadataKey(key string) error {
if key == "" {
return errors.New("key is required")
}
if !issueMetadataKeyRE.MatchString(key) {
return errors.New("key must match ^[a-zA-Z_][a-zA-Z0-9_.-]{0,63}$")
}
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 nilView on GitHub (pinned to 2c0912b6ec)
Solutions
- Normalize keys before sending: lowercase, replace disallowed chars with '_' or '-', trim to 64 chars, prefix with '_' if it starts with a digit or hyphen.
- Reserve the raw label as the metadata *value* and use a normalized slug as the key.
- Apply the same regex client-side and show it as inline validation.
- Reject non-ASCII input at the form layer or transliterate it.
Example fix
// before
const key = label; // e.g. "Due Date!"
await fetch(`${base}/issues/${id}/metadata/${key}`, ...);
// after
const key = label.trim().replace(/[^a-zA-Z0-9_.-]+/g, '_').replace(/^[^a-zA-Z_]/, '_$&').slice(0, 64);
await fetch(`${base}/issues/${id}/metadata/${encodeURIComponent(key)}`, ...); Defensive patterns
Strategy: validation
Validate before calling
const KEY_RE = /^[a-zA-Z_][a-zA-Z0-9_.-]{0,63}$/;
function normalizeMetadataKey(raw) {
let k = String(raw ?? '').trim()
.replace(/[^a-zA-Z0-9_.-]+/g, '_')
.replace(/^[0-9-]/, '_$&') // must start with letter or underscore
.slice(0, 64);
if (!KEY_RE.test(k)) throw new TypeError(`cannot normalize key: ${raw}`);
return k;
} Type guard
const isValidMetadataKey = (k) => typeof k === 'string' && /^[a-zA-Z_][a-zA-Z0-9_.-]{0,63}$/.test(k); Prevention
- Use slugs/normalized keys for storage; keep the human label as the value or in the UI layer only.
- Show the regex as inline validation in the key input field.
- Never derive keys from free text (emails, URLs, localized strings) without normalization.
When it happens
Trigger: PUT with key '123abc' (starts with digit), 'my key' (space), 'my/key' (slash), 'key!' (special char), 'a'.repeat(65) (too long), or '-flag' (starts with hyphen). Note dots and hyphens inside the key are fine.
Common situations: Using user-facing labels or free-text tags as metadata keys; embedding emails/URLs as keys ('user@x.com' fails on '@'); non-ASCII keys from localized UIs; long descriptive keys from LLM-generated metadata.
Related errors
- invalid runtime provider summary
- key is required
- value is required
- value cannot be null (use DELETE to remove a key)
- value must be a primitive: string, number, or bool
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/fb9026d7943b6d6a.
Report an issue: GitHub.