Skip to content

UndefinedFunctionError: ExternalBlobReference.decode/1 during app-state sync — possible module resolution ordering issue #10

@sh41

Description

@sh41

Summary

During the :regular collection app-state resync, the Coordinator GenServer crashed with UndefinedFunctionError because ExternalBlobReference.decode/1 was called as a bare module name, but could not be resolved at runtime.
Version: 0.1.0-alpha.7
Elixir: 1.19.5-otp-28
Erlang: 28.1
Rust: 1.94.1

Error

** (UndefinedFunctionError) function ExternalBlobReference.decode/1 is undefined
   (module ExternalBlobReference is not available)
    ExternalBlobReference.decode(<<10, 32, 119, ...>>)
    (baileys_ex) lib/baileys_ex/protocol/proto/noise_messages.ex:143:
      BaileysEx.Protocol.Proto.Wire.continue_nested_bytes/5
    (baileys_ex) lib/baileys_ex/protocol/proto/message_messages.ex:17:
      BaileysEx.Protocol.Proto.MessageSupport.decode_fields/3
    (baileys_ex) lib/baileys_ex/syncd/codec.ex:1007:
      BaileysEx.Syncd.Codec.decode_collection_patch/2

Root Cause Analysis

Correction: the original issue incorrectly stated the module does not exist. It does exist. Apologies for the initial inaccuracy.
The module is defined at BaileysEx.Protocol.Proto.Syncd.ExternalBlobReference in lib/baileys_ex/protocol/proto/syncd_messages.ex (line 233). The SyncdPatch module (same file, line 155) references it in its @decode_specs module attribute (line 191) as bare ExternalBlobReference:

# syncd_messages.ex — inside defmodule BaileysEx.Protocol.Proto.Syncd do
  defmodule SyncdPatch do          # line 155
    @decode_specs %{
      # ...
      3 => {:message, 3, ExternalBlobReference},   # line 191 — bare module ref
      # ...
    }
  end
  defmodule ExternalBlobReference do  # line 233 — defined AFTER SyncdPatch
    # ...
  end

Since both modules are nested inside BaileysEx.Protocol.Proto.Syncd, the bare ExternalBlobReference reference in @decode_specs should resolve at compile time to the fully-qualified BaileysEx.Protocol.Proto.Syncd.ExternalBlobReference. However, ExternalBlobReference is defined after SyncdPatch in the same file (line 233 vs line 155). This may cause a compile-time ordering issue where the module attribute is evaluated before the sibling module is compiled, resulting in the bare atom ExternalBlobReference being stored unresolved.
At runtime, Wire.continue_nested_bytes/5 calls the module from the decoded spec, and the bare ExternalBlobReference (without namespace) is not a loaded module — hence the UndefinedFunctionError.
Note: codec.ex line 874 references the same module but uses the fully-qualified path Syncd.ExternalBlobReference.decode/1, which works correctly.

Suggested Fix

Either:

  1. Use the fully-qualified module name in the decode spec (safest):
    3 => {:message, 3, BaileysEx.Protocol.Proto.Syncd.ExternalBlobReference}
  2. Move the ExternalBlobReference module definition above SyncdPatch in syncd_messages.ex, so it is compiled first and the bare reference resolves correctly.
  3. Use __MODULE__-relative aliasing in the decode spec:
    alias BaileysEx.Protocol.Proto.Syncd.ExternalBlobReference
    # then the bare reference in @decode_specs resolves correctly

Reproduction

  1. Pair a device and establish a connection with default config (sync_full_history: true)
  2. Wait for initial sync — the :regular collection app-state resync triggers
  3. Coordinator crashes with the error above
    Note: It may depend on the specific app-state patch data returned by the server at sync time — if the patch contains an ExternalBlobReference-typed field, the decode path is triggered.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions