{"record":{"id":"90694fad77c37610","repo":"clockworklabs/SpacetimeDB","slug":"unable-to-decode-args","errorCode":null,"errorMessage":"unable to decode args","messagePattern":"unable to decode args","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/bindings/src/rt.rs","lineNumber":54,"sourceCode":"}\n\nimpl<T> IntoVec<T> for Option<T> {\n    fn into_vec(self) -> Vec<T> {\n        self.into_iter().collect()\n    }\n}\n\n/// The `sender` invokes `reducer` at `timestamp` and provides it with the given `args`.\n///\n/// Returns an invalid buffer on success\n/// and otherwise the error is written into the fresh one returned.\npub fn invoke_reducer<'a, A: Args<'a>>(\n    reducer: impl Reducer<'a, A>,\n    ctx: &ReducerContext,\n    args: &'a [u8],\n) -> Result<(), Box<str>> {\n    // Deserialize the arguments from a bsatn encoding.\n    let SerDeArgs(args) = bsatn::from_slice(args).expect(\"unable to decode args\");\n\n    reducer.invoke(ctx, args)\n}\n\npub fn invoke_procedure<'a, A: Args<'a>, Ret: IntoProcedureResult>(\n    procedure: impl Procedure<'a, A, Ret>,\n    ctx: &mut ProcedureContext,\n    args: &'a [u8],\n) -> ProcedureResult {\n    // Deserialize the arguments from a bsatn encoding.\n    let SerDeArgs(args) = bsatn::from_slice(args).expect(\"unable to decode args\");\n\n    let res = procedure.invoke(ctx, args);\n\n    res.to_result()\n}\n\n/// A trait for types representing the *execution logic* of a reducer.","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/eddf9f5014579a50d4b67630e28b6e15cad9c4af/crates/bindings/src/rt.rs#L36-L72","documentation":"SpacetimeDB's module runtime (crates/bindings/src/rt.rs) decodes a reducer's arguments from the BSATN wire format before invoking: bsatn::from_slice::<SerDeArgs>(args). The result is unwrapped with .expect(\"unable to decode args\") — a decode failure PANICS the module call rather than returning an error. Decode fails when the bytes do not match the reducer's expected argument tuple layout (wrong arity, types, or truncated/extra bytes).","triggerScenarios":"A client invoking the reducer with BSATN args encoded against a different module schema than the one deployed: argument count changed, a field type changed (u32 -> u64, string -> bytes), an enum/table row type renamed, or the payload is truncated / not BSATN at all. Deploying a new module version while old clients keep sending the old encoding.","commonSituations":"Client SDK generated from an older module; module updated (even a benign-looking type change) without regenerating bindings; hand-rolled encoders using JSON or a different serialization instead of BSATN; partial network writes corrupting the arg buffer; version skew between spacetimedb server, module, and SDK.","solutions":["Regenerate the client bindings/SDK from the currently deployed module version and retry the call","Redeploy so module and clients are built from the same schema; coordinate a version bump so old clients fail fast with a clear schema-mismatch instead","Verify the bytes being sent are BSATN of the exact argument tuple — encode with the same generated Args types on the client","Inspect server logs for the panic backtrace to identify which reducer/arg failed, then diff that schema between client and module"],"exampleFix":"// before (client, stale SDK: callReducer('add_user', bsatnEncode({ name: 'x' })) against module\n// whose reducer is fn add_user(ctx, name: String, age: u32) -> panics: unable to decode args\n\n// after: regenerate SDK from the deployed module, then\nconst res = await db.reducers.addUser('x', 30); // arity & types now match the module schema","handlingStrategy":"validation","validationCode":"// Client-side: encode args with the SAME generated types that will decode them.\n// If you must pre-validate raw bytes, decode them against the expected schema first:\nlet SerDeArgs(_) = spacetimedb::bsatn::from_slice(args)\n    .map_err(|e| anyhow::anyhow!(\"client args would fail module decode: {e}\"))?;","typeGuard":null,"tryCatchPattern":"// This is .expect() -> a panic, not a Result. There is no in-module catch:\n// guard at the boundary. Hosts should catch_unwind / trap the wasm call:\nlet res = std::panic::catch_unwind(AssertUnwindSafe(|| invoke_reducer(...)));\nif res.is_err() { /* report schema mismatch for this reducer to the client */ }","preventionTips":["Regenerate client SDKs whenever the module is redeployed and bump versions together","Add a CI check comparing client-generated schema hash to deployed module schema hash","Always encode reducer args with generated Args types, never by hand","For no-arg reducers send the canonical empty-tuple BSATN encoding"],"tags":["bsatn","serialization","schema-mismatch","rust","panic","spacetimedb","wasm"],"backgroundTag":"argument-decode-failed","analyzedSha":"eddf9f5014579a50d4b67630e28b6e15cad9c4af","analyzedAt":"2026-08-20T06:08:37.179Z","contentChangedAt":"2026-08-20T06:08:37.179Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}