multica-ai/multica · warning

key is required

Error message

key is required

What it means

validateIssueMetadataKey rejects a metadata write whose URL-embedded key is the empty string. Issue metadata V1 is a flat key-value store addressed as PUT/DELETE .../metadata/{key}; an empty key means the request path ended in /metadata/ (or the router extracted nothing), which cannot name a value. The empty check runs before the pattern check and gives the distinct, actionable 'required' message.

Source

Thrown at server/internal/handler/issue_metadata.go:47

// any whole-blob overwrite would race with concurrent agent writes (see the
// design discussion on MUL-2017).
const (
	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)
	}

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Require a non-empty key in the client form/UI before enabling submit.
  2. Check key truthiness before building the request URL.
  3. Fix route wiring so the {key} path parameter is captured and non-empty (404/routing rather than handler-level empty).
  4. Trim whitespace and reject empty-after-trim keys.

Example fix

// before
await fetch(`${base}/issues/${id}/metadata/${key}`, {...}); // key may be ''

// after
if (!key || !key.trim()) throw new Error('metadata key is required');
await fetch(`${base}/issues/${id}/metadata/${encodeURIComponent(key.trim())}`, {...});
Defensive patterns

Strategy: validation

Validate before calling

function metadataUrl(base, issueId, key) {
  const k = String(key ?? '').trim();
  if (!k) throw new TypeError('metadata key is required');
  return `${base}/issues/${issueId}/metadata/${encodeURIComponent(k)}`;
}

Type guard

const isNonEmptyKey = (k) => typeof k === 'string' && k.trim().length > 0;

Prevention

When it happens

Trigger: PUT /issues/{id}/metadata/ (trailing slash, empty key segment); client building the URL as `${base}/metadata/${key}` with key undefined/empty in JS; a route misconfiguration forwarding /metadata directly to the handler without a key capture group.

Common situations: Frontend form allowing submit with an empty key field; template string with an unset variable producing .../metadata/; proxy/gateway rewriting that strips the last path segment.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/ec1ade1e98acbe98. Report an issue: GitHub.