---
title: Short enum names are the JSON wire form, through one derived codec
canonical: https://skift.stead.ing/docs/adrs/008-short-enum-names-on-the-json-wire/
site: skift
---

# Short enum names are the JSON wire form, through one derived codec

## Context and Problem Statement

Proto enum values carry their enum's name as a prefix, so `Priority` holds `PRIORITY_P2`. protojson puts that spelling on the wire, and every other skift surface — the `--priority p2` flag, the display, `docs/domain-model.md`, the MCP tool schemas — says `p2`. An agent that read the CLI help and the domain model was taught the short form everywhere it looked, then had to write the long one in the one place it writes JSON by hand (`AGENT_REVIEW.md` §3). The same gap exists for `Status`, `AssigneeKind`, `EdgeKind`, `DropReason`, `ReleaseReason`, and for every enum added later.

## Decision Drivers

- The one door should say what every other surface says; a client learns skift from its CLI and its docs
- A rule that has to be restated per enum will be forgotten by the next enum
- The proto definitions are right as they are: prefixed values are what generated-code scoping needs
- Bytes matter at agent scale, but learnability matters more

## Considered Options

1. Leave the wire canonical and translate in the MCP and CLI layers only
2. Rename the proto values to the short form
3. One JSON codec, driven by protoreflect, on the Connect handlers and in the CLI

## Decision Outcome

Chosen option: **one JSON codec**, in `internal/codec`, registered on the Connect handlers under the `json` name and used for every JSON the CLI and the MCP server read or write. Connect is the only door (ADR-002), so a codec there is the whole wire.

The codec walks a JSON document beside the descriptor of the message it encodes:

- **Accepting**: for any enum field, the canonical name (`PRIORITY_P2`), the name minus the enum's own prefix (`P2`), and that lowercased (`p2`). The prefix is the enum's name in SCREAMING_SNAKE plus an underscore, derived from the descriptor, so there is no per-enum table.
- **Emitting**: the short lowercase form, and only when it decodes back to the same value.
- **Refusing**: a string that names no value of its enum, rather than letting protojson's `DiscardUnknown` turn a misspelt priority into an absent field and so into the default.

Fields keep their proto names, which is what the CLI already printed and what the domain model uses.

The proto definitions do not change, and skift has no other client, so the wire contract could be changed rather than versioned.

### Consequences

- **Good**: One JSON vocabulary across the CLI, the MCP tools, the docs and the wire; an enum added to the schema is covered the day it is added.
- **Good**: A misspelt enum is an error naming the values that would have worked, instead of a silent default.
- **Good**: Roughly nine bytes saved per enum field, which at patch scale is real but is the smaller win.
- **Bad**: The JSON on the wire is no longer canonical proto-JSON, so a generic protobuf tool pointed at skift will not read a response's enums. Existing spellings are still accepted, so only emission differs.
- **Neutral**: The codec is a JSON round trip on top of protojson. The binary protocol, which is what the CLI actually speaks, is untouched.

## Rejected Options

### Translate in the MCP and CLI layers only

Smaller and reversible, and it fixes it for the callers who suffer today.

- **Good**: The wire stays canonical proto-JSON for any future client.
- **Bad**: The asymmetry survives at the door, so the next interface has to remember to translate. With no other clients, there is nothing to protect.

### Rename the proto values to the short form

- **Good**: One spelling everywhere, with no codec at all.
- **Bad**: Unprefixed values collide across enums in the generated code of most languages, and buf's lint rules reject them. A JSON encoding is not a reason to make the schema worse.

## More Information

Implemented in `internal/codec`; `AGENT_REVIEW.md` §3 carries the measurements and the decision to do this at the codec. Related: ADR-002 (Connect RPC is the only door).
