zed-industries/zed · error

incorrect proto inlay hint message: no resolve state in hint

Error message

incorrect proto inlay hint message: no resolve state in hint {message_hint:?}

What it means

Inlay hints are serialized into proto messages when exchanged between processes. proto_to_project_hint converts one back and requires the resolve_state field (resolved/resolvable state plus optional LSP data) to be present; a hint without it panics as 'incorrect proto inlay hint message'. The serializer always sets resolve_state, so receiving one without it indicates a mismatched or older proto definition on the peer, or a buggy/hand-built message.

Source

Thrown at crates/project/src/lsp_command.rs:3469

        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })
            .await?;

        let completions = message
            .completions
            .into_iter()
            .map(LspStore::deserialize_completion)
            .collect::<Result<Vec<_>>>()?;

        Ok(CoreCompletionResponse {
            completions,
            is_incomplete: !message.can_reuse,
        })
    }

    fn buffer_id_from_proto(message: &proto::GetCompletions) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

pub struct ParsedCompletionEdit {
    pub replace_range: Range<Anchor>,
    pub insert_range: Option<Range<Anchor>>,
    pub new_text: String,
}

pub(crate) fn parse_completion_text_edit(
    edit: &lsp::CompletionTextEdit,
    snapshot: &BufferSnapshot,
) -> Option<ParsedCompletionEdit> {
    let (replace_range, insert_range, new_text) = match edit {
        lsp::CompletionTextEdit::Edit(edit) => (edit.range, None, &edit.new_text),
        lsp::CompletionTextEdit::InsertAndReplace(edit) => {
            (edit.replace, Some(edit.insert), &edit.new_text)

View on GitHub (pinned to 9d272b0363)

Solutions

  1. Update both sides (client and server/collaborators) to matching versions so proto schemas and serialization agree
  2. When constructing proto::InlayHint yourself, always populate resolve_state - mirror what the to_proto serializer emits
  3. If you own the converter, return an anyhow error for missing resolve_state and skip the malformed hint instead of panicking
  4. Add proto round-trip tests (project -> proto -> project) for inlay hints to catch missing fields in CI

Example fix

// before: hand-built message missing required field
let hint = proto::InlayHint { label, position, resolve_state: None, ..Default::default() };
let hint = proto_to_project_hint(hint); // panics

// after: always set resolve_state
let hint = proto::InlayHint {
    label,
    position,
    resolve_state: Some(proto::InlayHintResolveState {
        state: proto::ResolveState::Resolved as i32,
        lsp_resolve_state: None,
    }),
    ..Default::default()
};
let hint = proto_to_project_hint(hint)?;
Defensive patterns

Strategy: validation

Validate before calling

// drop malformed hints before conversion instead of panicking
let hints: Vec<_> = message_hints
    .into_iter()
    .filter(|hint| hint.resolve_state.is_some())
    .collect();
let converted = hints
    .into_iter()
    .map(proto_to_project_hint)
    .collect::<anyhow::Result<Vec<_>>>()?;

Type guard

fn is_valid_proto_hint(hint: &proto::InlayHint) -> bool {
    hint.resolve_state.is_some()
}

Prevention

When it happens

Trigger: Decoding InlayHint messages whose resolve_state is None: a collaborator/remote peer built with a different version of the proto crate, a hand-rolled server omitting the field, or test code constructing proto::InlayHint directly without setting resolve_state.

Common situations: Version skew between editor and collaborator/server builds after an inlay-hints proto change; hand-written test fixtures; forks that add proto fields without populating required ones.

Related errors


AI-assisted analysis of zed-industries/zed@9d272b0363 (2026-08-20). Data as JSON: /api/errors/02cc587cb77102b4. Report an issue: GitHub.