RepoOp.cid should be Option<CidLink> - causes deserialization failure on delete operations

did:plc:ttdrpj45ibqunmfhdsb4zdwq opened this Nov 19, 2025 4 comments
did:plc:ttdrpj45ibqunmfhdsb4zdwq opened Nov 19, 2025

com.atproto.sync.subscribeRepos defines cid on repoOp as nullable

"repoOp": {
   "type": "object",
      "nullable": [
        "cid"
       ],

however in the generated rust code, its not an Option

generated rust code (crates/jacquard-api/src/com_atproto/sync/subscribe_repos.rs line ~2082):

  pub struct RepoOp<'a> {
      pub action: jacquard_common::CowStr<'a>,
      pub cid: jacquard_common::types::cid::CidLink<'a>,  // here
      pub path: jacquard_common::CowStr<'a>,
      pub prev: Option<jacquard_common::types::cid::CidLink<'a>>,
  }

when a delete operation is processed, DAG-CBOR operations then fail to deserialize

minimal repro:

[package]
name = "jacquard-bug-repro"
version = "0.1.0"
edition = "2021"

[dependencies]
jacquard-api = { git = "https://tangled.org/@nonbinary.computer/jacquard", features = ["streaming"] }
jacquard-common = { git = "https://tangled.org/@nonbinary.computer/jacquard", features = ["websocket"] }
tokio = { version = "1", features = ["full"] }
n0-future = "0.3"
url = "2"
use jacquard_api::com_atproto::sync::subscribe_repos::{SubscribeRepos, SubscribeReposMessage};
use jacquard_common::xrpc::{SubscriptionClient, TungsteniteSubscriptionClient};
use n0_future::StreamExt;
use url::Url;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let pds_url = Url::parse("wss://bsky.network")?;
    println!("Connecting to {}...", pds_url);

    let client = TungsteniteSubscriptionClient::from_base_uri(pds_url);
    let params = SubscribeRepos::new().build();
    let stream = client.subscribe(&params).await?;

    println!("Connected! Waiting for a delete operation...\n");

    let (_sink, mut messages) = stream.into_stream();

    while let Some(result) = messages.next().await {
        match result {
            Ok(SubscribeReposMessage::Commit(commit)) => {
                println!("Commit from {}: {} ops", commit.repo, commit.ops.len());
            }
             Err(e) => {
                return Err(e.into());
            }
            _ => {}
        }
    }

    Ok(())
}

it should crash within seconds after processing a delete record

I had to manually RepoOp.cid to Option<CidLink<'a>>: in the generated code but i dont know how to fix the codegen thats way over my head

No activity yet.

cospan · schematic version control on atproto built on AT Protocol