Skip to content

CEP 37: loosen custom_metadata from dict[str, str] #163

Description

@jezdez

CEP 37 specifies metadata.custom_metadata as dict[str, str], free-form key-value string pairs. The current conda-lock implementation enforces this via a StrictModel.

The string constraint is fine for flat annotations (a git SHA, a build ID, a commit URL), but it breaks down as soon as anyone wants to stash a structured payload there.

Concrete example: in conda-lockfiles (conda/conda-lockfiles#132) I'm recording user-requested MatchSpec strings so that exported lockfiles carry user intent, not just the pinned solve. That's a list[str]. The spec forces me to JSON-encode it and embed it as a single string:

custom_metadata:
  created_by: conda-lockfiles 0.1.x
  requested_specs: '["python_abi", "python >=3.11"]'

versus what the YAML actually wants to be:

custom_metadata:
  created_by: conda-lockfiles 0.1.x
  requested_specs:
    - python_abi
    - python >=3.11

The JSON-string form is less diffable, less greppable, and hides structure from anyone casually reading the lockfile. It also means every consumer has to know which keys are JSON-encoded and which aren't.

What I'd like to see

Widen the type to something like dict[str, str | int | bool | list | dict] or dict[str, Any] (effectively "any YAML-scalar-or-container value"). That keeps the intent (free-form user metadata) and removes the string wrapper workaround.

Lockfile writers that don't want to generate structured values can continue to write flat strings and nothing changes for them. Consumers need to handle more value types, but arguably they already need to: conda-lock's LockMeta.__or__ merges custom_metadata dicts today, which would produce surprising results with JSON-string collision anyway.

Why now

CEP 37 is accepted but very recent (March 2026) and conda-lock's implementation is the only one that meaningfully enforces it. Catching this before a second implementation bakes in the same constraint seems worthwhile.

Alternatives considered

  • Invent a sibling block. CEP 37 already lists git_metadata, inputs_metadata, time_metadata as structured cousins of custom_metadata. I could propose a new structured block per use case, but that explodes the spec and misses the point: custom_metadata is the escape hatch, it should actually work as one.
  • Keep JSON-strings as the convention. Works, ships today, ugly forever.

Prior art

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions