Skip to content

bub-tapestore-otel exports 0 spans: ValidationError "Input should be ... an instance of TapeEntry" against current bub git HEAD #48

Description

@CorrectRoadH

bub-tapestore-otel exports 0 spans: ValidationError: ... Input should be a dictionary or an instance of TapeEntry when used against current bub git HEAD

Summary

bub-tapestore-otel silently exports zero spans when installed into an
environment that resolves bub to git HEAD (e.g. uv tool install bub --with bub-tapestore-otel, or any fresh resolution that isn't pinned to the workspace
lock). Every tape append throws a pydantic ValidationError, which the
exporter swallows via its try/except … warning("tapestore.otel.export_failed"),
so the only visible symptom is export_failed log lines and no telemetry.

Symptom (exact error)

pydantic_core._pydantic_core.ValidationError: 10 validation errors for StepTrace
entries.0
  Input should be a dictionary or an instance of TapeEntry
  [type=dataclass_type, input_value=TapeEntry(id=1, kind='anc...'), input_type=TapeEntry]
    For further information visit https://errors.pydantic.dev/2.14/v/dataclass_type
entries.1
  Input should be a dictionary or an instance of TapeEntry
  [type=dataclass_type, input_value=TapeEntry(id=2, kind='eve...'), input_type=TapeEntry]
...

Note input_type=TapeEntry — the value is a TapeEntry, yet pydantic rejects
it. Raised from OTelTapeExporter._appendbuild_tape_traceStepTrace(**…),
caught in OTelTapeStore.append and logged as tapestore.otel.export_failed.

Root cause (bisected)

It is a bub version drift, not pydantic and not republic. Controlled A/B,
identical everything except the pinned variable:

bub republic pydantic result
0.3.9.dev6+g10fa0150a (the commit uv.lock pins) 0.5.8 2.13.4 ✅ projects + exports
0.3.9.dev6+g10fa0150a 0.5.8 2.14.0a1 ✅ projects + exports
0.3.10.dev9+ga2b638865 (git HEAD) 0.5.8 2.13.4 export_failed, 10 validation errors
0.3.10.dev9+ga2b638865 (git HEAD) 0.5.8 2.14.0a1 export_failed, 10 validation errors
  • The plugin's own unit tests pass under both pydantic 2.13.4 and 2.14.0a1
    (they build TapeEntry via TapeEntry.system()/.message()/.event()).
  • republic is 0.5.8 in every case, and republic.TapeEntry is republic.tape.entries.TapeEntry → a single class object, not a duplicate-import
    problem.
  • Therefore the trigger is the runtime TapeEntry instances produced by bub
    git HEAD
    (note the new kind='anchor' entries). They fail an isinstance
    check against the republic.TapeEntry captured in the plugin's pydantic schema,
    even though their repr/input_type say TapeEntry.

Why fresh installs hit HEAD

bub-tapestore-otel's pyproject.toml declares dependencies = ["bub", …]
(unpinned) and is consumed from the bub-contrib uv workspace, which sources
bub from git. A consumer doing uv tool install bub>=0.3.0a1 --with git+…/bub-tapestore-otel resolves bub to git HEAD, not the workspace's
locked commit 10fa0150a. (--prerelease allow, needed for bub's 0.3.0a1
spec, additionally pulls pydantic 2.14.0a1, but that is harmless per the table
above.)

Minimal repro

# FAILS (bub HEAD)
uv run --no-project --prerelease allow \
  --with "bub>=0.3.0a1" \
  --with "git+https://github.com/bubbuild/bub-contrib.git#subdirectory=packages/bub-tapestore-otel" \
  bub --workspace /tmp/ws run "read data.txt" --session-id x
# → tapestore.otel.export_failed ... ValidationError: ... instance of TapeEntry

# WORKS (pin bub to the tested commit)
uv run --no-project --prerelease allow \
  --with "bub @ git+https://github.com/bubbuild/bub.git@10fa0150a1168abfe67449380f714ac9456b3b7b" \
  --with "git+https://github.com/bubbuild/bub-contrib.git#subdirectory=packages/bub-tapestore-otel" \
  bub --workspace /tmp/ws run "read data.txt" --session-id x
# → projects + attempts OTLP export, no export_failed

Suggested fixes (upstream)

  1. Make the projection robust to TapeEntry identity/shapeentries (and
    the TapeEntry-typed fields) are internal-only; they don't need pydantic
    validation. Type them as SkipValidation[list[TapeEntry]] (pydantic) or
    list[Any], so the plugin survives any bub/republic change to how
    TapeEntry is constructed. This is the real durability fix.
  2. Constrain bub in pyproject.toml to the tested range, and/or publish
    bub-tapestore-otel to PyPI so consumers don't resolve bub to git HEAD.
  3. If bub HEAD intentionally changed TapeEntry construction/identity, treat
    that as a breaking change for plugins that receive TapeEntry instances.

Related

Same failure mode (a contrib plugin breaking against current bub main) as
#43 "bub-tapestore-sqlite breaks against latest bub main after _build_llm
removal"
— different plugin/symptom, same root pattern of unpinned bub drift.

Consumer workaround (until fixed)

Pin bub to the plugin's tested commit when installing:
bub @ git+https://github.com/bubbuild/bub.git@10fa0150a1168abfe67449380f714ac9456b3b7b

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions