fix: pass mode='json' to model_dump() in PyHookRegistry emit path - #77
Merged
Brian Krabach (bkrabach) merged 1 commit intoMay 18, 2026
Merged
Conversation
The Rust hook registry calls try_model_dump() on every hook payload then passes the result directly to json.dumps() with no custom encoder. Bare model_dump() returns Python-native types — including Decimal for fields such as Usage.cost_usd — which json.dumps() cannot handle, producing: TypeError: Object of type Decimal is not JSON serializable Root cause for this crash is the hook emitter itself: it is the caller of json.dumps(), so it is the right place to request JSON-safe output. Passing mode="json" tells Pydantic to emit only JSON-native Python types (str, int, float, list, dict, bool, None) regardless of the field's Python type or whether a @field_serializer is present on the model. try_model_dump() is a single shared helper that serves both emit() and emit_and_collect() in hooks.rs, so this one-function change covers the entire emit path. Note: amplifier-core 1.5.1 added a @field_serializer on Usage.cost_usd which also fixes the immediate symptom, but the Docker worker image is built from 1.5.0. This fix is architecturally correct regardless of the model version — the hook system should not rely on individual field serializers to be JSON-safe. Rebuilding amplifier-cache:python after this merges will resolve the crash for all consumers without any per-consumer workarounds. Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Diego Colombo (colombod)
force-pushed
the
fix/hook-emit-json-safe-model-dump
branch
from
May 11, 2026 16:56
0eb30cd to
a91b883
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
TypeError: Object of type Decimal is not JSON serializableon the first LLM response, inPyHookRegistry::emit(). The crash fires before any Python hook handler gets control, making it impossible to intercept from the consumer side.try_model_dump()inhelpers.rscalls baremodel_dump()", which returns Python-native types includingDecimal(used byUsage.cost_usd). The result is passed directly tojson.dumps()with no custom encoder — which cannot handleDecimal`.json.dumps(). It is the correct place to request JSON-safe output. Puttingmode="json"here means no consumer or model field ever needs to compensate for the hook system's serialization contract.What changed
One function,
try_model_dump()inbindings/python/src/helpers.rs:mode="json"tells Pydantic to emit only JSON-native Python types (str,int,float,list,dict,bool,None) regardless of the field's underlying Python type or whether a@field_serializeris present on the model.try_model_dump()is a shared helper called by bothemit()andemit_and_collect()inhooks.rs— this one-function change covers the entire emit path.Why this is architecturally correct (not a workaround)
The immediate trigger is
amplifier-core 1.5.0in the Docker worker image (amplifier-cache:python) predating the@field_serializeronUsage.cost_usdadded in 1.5.1. But this fix is correct regardless of model version:UUID,datetime, custom type) in any hook payload would cause the same crash without this fix.Test plan
amplifier-cache:pythonimage after this merges and bump/releaseamplifier-coreTypeError: Object of type Decimal is not JSON serializableon LLM responseemit()andemit_and_collect()both work correctly (both flow throughtry_model_dump())Rebuilding
amplifier-cache:pythonafter this merges will fully resolve the crash for all consumers without any per-consumer workarounds.Generated with Amplifier