Garbled response from Symbolics.jl variables #6

Closed
opened 2026-03-06 17:48:30 -08:00 by jeffwack · 7 comments
jeffwack commented 2026-03-06 17:48:30 -08:00 (Migrated from github.com)

Human here: I encountered this issue when using Claude Code and so asked Claude to figure out what was going wrong. The rest of this issue is LLM generated text, I hope its analysis is of some use.

Bug: Symbolics.jl expressions produce garbled binary output

Symptom

Evaluating any Symbolics.jl expression via the ex MCP tool returns garbled binary data instead of text. Example:

@variables x y

Returns:

7JL   4ExceptionStackN�DF�4
NamedTupleN�D         exception       backtraceSN�D   MethodErrorN�DPN�D   TN�D   NothingN�DInterpreterIPN�D�4, 4#sizeN�D5SmallVec{AgF��֐=B��0X�SymbolicUtilsD        UnitRangeN�D    PN�D   , �5Backing{AgF��֐=B��0X�, D   , �4, `6�<
      ��      , , 1+   4,         4,         4,         4,         4,         4,         4, ...

Analysis

The output begins with \x07JL — Julia's native serialization magic bytes — and contains readable fragments of SymbolicUtils internals (ExceptionStack, MethodError, SmallVec, Backing). This is a serialized Julia object
leaking through as raw bytes.

There appear to be two compounding issues:

1. gate.jl ~line 547 — REPL backend error pair not checked

When _eval_with_capture throws inside the REPL backend, repl_backend_loop (REPL.jl:447) catches it and puts Pair{ExceptionStack, true} on the response channel — the true flag signals an error. gate_eval extracts
result.first without checking result.second:

result = REPL.call_on_backend(() -> _eval_with_capture(expr), backendref)
val = if result isa Pair
    result.first  # silently an ExceptionStack when _eval_with_capture threw

The ExceptionStack — whose backtrace contains SymbolicUtils types (SmallVec{...}, Backing{...}) — is then passed to _serialize_result as if it were a normal result NamedTuple.

2. gate_client.jl ~line 597 — cross-process deserialization fails, leaks binary as output

The MCP/TUI server process does not have Symbolics loaded, so deserializing an ExceptionStack containing SymbolicUtils types fails. The catch fallback uses string(data) — the raw binary bytes — as value_repr:

result = try
    deserialize(IOBuffer(Vector{UInt8}(data)))
catch
    (stdout="", stderr="", value_repr=string(data), ...)  # raw binary leaks here
end

This is the binary blob that appears as tool output.

The specific exception visible in the binary is a MethodError for #size on SmallVec{...} from SymbolicUtils, occurring inside _eval_with_capture when called from the REPL backend. Notably, calling _eval_with_capture
directly with a Symbolics expression works correctly — the failure is specific to the REPL backend execution context.

> Human here: I encountered this issue when using Claude Code and so asked Claude to figure out what was going wrong. The rest of this issue is LLM generated text, I hope its analysis is of some use. ## Bug: Symbolics.jl expressions produce garbled binary output ### Symptom Evaluating any Symbolics.jl expression via the `ex` MCP tool returns garbled binary data instead of text. Example: ```julia @variables x y ``` Returns: ``` 7JL 4ExceptionStackN�DF�4 NamedTupleN�D exception backtraceSN�D MethodErrorN�DPN�D TN�D NothingN�DInterpreterIPN�D�4, 4#sizeN�D5SmallVec{AgF��֐=B��0X�SymbolicUtilsD UnitRangeN�D PN�D , �5Backing{AgF��֐=B��0X�, D , �4, `6�< �� , , 1+ 4, 4, 4, 4, 4, 4, 4, ... ``` ### Analysis The output begins with `\x07JL` — Julia's native serialization magic bytes — and contains readable fragments of SymbolicUtils internals (`ExceptionStack`, `MethodError`, `SmallVec`, `Backing`). This is a serialized Julia object leaking through as raw bytes. There appear to be two compounding issues: **1. `gate.jl` ~line 547 — REPL backend error pair not checked** When `_eval_with_capture` throws inside the REPL backend, `repl_backend_loop` (REPL.jl:447) catches it and puts `Pair{ExceptionStack, true}` on the response channel — the `true` flag signals an error. `gate_eval` extracts `result.first` without checking `result.second`: ```julia result = REPL.call_on_backend(() -> _eval_with_capture(expr), backendref) val = if result isa Pair result.first # silently an ExceptionStack when _eval_with_capture threw ``` The `ExceptionStack` — whose backtrace contains SymbolicUtils types (`SmallVec{...}`, `Backing{...}`) — is then passed to `_serialize_result` as if it were a normal result NamedTuple. **2. `gate_client.jl` ~line 597 — cross-process deserialization fails, leaks binary as output** The MCP/TUI server process does not have Symbolics loaded, so deserializing an `ExceptionStack` containing SymbolicUtils types fails. The catch fallback uses `string(data)` — the raw binary bytes — as `value_repr`: ```julia result = try deserialize(IOBuffer(Vector{UInt8}(data))) catch (stdout="", stderr="", value_repr=string(data), ...) # raw binary leaks here end ``` This is the binary blob that appears as tool output. The specific exception visible in the binary is a `MethodError` for `#size` on `SmallVec{...}` from SymbolicUtils, occurring inside `_eval_with_capture` when called from the REPL backend. Notably, calling `_eval_with_capture` directly with a Symbolics expression works correctly — the failure is specific to the REPL backend execution context.
kahliburke commented 2026-03-06 21:44:01 -08:00 (Migrated from github.com)

Hi @jeffwack, thanks for the bug report. This definitely seems strange and I'll take a look at how things are looking when using Symbolics.jl.

Hi @jeffwack, thanks for the bug report. This definitely seems strange and I'll take a look at how things are looking when using Symbolics.jl.
kahliburke commented 2026-03-07 10:25:38 -08:00 (Migrated from github.com)

Root cause identified: this is a world age problem in _eval_with_capture.

When Core.eval(Main, expr) executes something like using Symbolics, new methods (e.g. size(::SymbolicUtils.SmallVec)) are defined in a newer world age than the REPL backend thread's execution context. The subsequent show(io, MIME("text/plain"), value) call runs in the older world age and fails with:

size(::SymbolicUtils.SmallVec) (method too new to be called from this world context.)

This causes show to fail, then the repr fallback also fails for the same reason, and the exception escapes _eval_with_capture entirely. The exception object contains references to Symbolics types, so when it gets serialized over ZMQ and the MCP server process (which doesn't have Symbolics loaded) tries to deserialize it, deserialization fails and the raw binary bytes leak into the output.

Fix: wrap show, repr, and showerror in Base.invokelatest so they always resolve methods in the latest world age. PR #8.

Root cause identified: this is a **world age problem** in `_eval_with_capture`. When `Core.eval(Main, expr)` executes something like `using Symbolics`, new methods (e.g. `size(::SymbolicUtils.SmallVec)`) are defined in a newer world age than the REPL backend thread's execution context. The subsequent `show(io, MIME("text/plain"), value)` call runs in the older world age and fails with: ``` size(::SymbolicUtils.SmallVec) (method too new to be called from this world context.) ``` This causes `show` to fail, then the `repr` fallback also fails for the same reason, and the exception escapes `_eval_with_capture` entirely. The exception object contains references to Symbolics types, so when it gets serialized over ZMQ and the MCP server process (which doesn't have Symbolics loaded) tries to deserialize it, deserialization fails and the raw binary bytes leak into the output. Fix: wrap `show`, `repr`, and `showerror` in `Base.invokelatest` so they always resolve methods in the latest world age. PR #8.
kahliburke commented 2026-03-07 10:35:06 -08:00 (Migrated from github.com)

@jeffwack I believe I have fixed this issue, would you care to give it a try?

@jeffwack I believe I have fixed this issue, would you care to give it a try?
jeffwack commented 2026-03-07 12:41:56 -08:00 (Migrated from github.com)

There seems to be a dependency issue now. I get this error when running ./bin/kaimon. I tried first to update via git pull but also tried a fresh install and get the same error in both cases.

ERROR: LoadError: MethodError: no method matching Tachikoma.Block(; title::String, border_style::Tachikoma.Style, title_style::Tachikoma.Style, title_right::String, title_right_style::Tachikoma.Style, title_padding::Int64)
This method does not support all of the given keyword arguments (and may not support any).

Closest candidates are:
  Tachikoma.Block(::String, ::Tachikoma.Style, ::Tachikoma.Style, ::NamedTuple) got unsupported keyword arguments "title", "border_style", "title_style", "title_right", "title_right_style", "title_padding"
   @ Tachikoma ~/.julia/packages/Tachikoma/wxvSC/src/widgets/block.jl:6
  Tachikoma.Block(::Any, ::Any, ::Any, ::Any) got unsupported keyword arguments "title", "border_style", "title_style", "title_right", "title_right_style", "title_padding"
   @ Tachikoma ~/.julia/packages/Tachikoma/wxvSC/src/widgets/block.jl:6
  Tachikoma.Block(; title, title_style, border_style, box) got unsupported keyword arguments "title_right", "title_right_style", "title_padding"
   @ Tachikoma ~/.julia/packages/Tachikoma/wxvSC/src/widgets/block.jl:12

Stacktrace:
  [1] kwerr(kw::@NamedTuple{title::String, border_style::Tachikoma.Style, title_style::Tachikoma.Style, title_right::String, title_right_style::Tachikoma.Style, title_padding::Int64}, args::Type)
    @ Base ./error.jl:175
  [2] view(m::Kaimon.KaimonModel, f::Tachikoma.Frame)
    @ Kaimon ~/Kaimon.jl/src/tui/view.jl:218
  [3] (::Tachikoma.var"#173#174"{Tachikoma.AppOverlay, Bool, Kaimon.KaimonModel})(f::Tachikoma.Frame)
    @ Tachikoma ~/.julia/packages/Tachikoma/wxvSC/src/app.jl:991
  [4] draw!(func::Tachikoma.var"#173#174"{Tachikoma.AppOverlay, Bool, Kaimon.KaimonModel}, t::Tachikoma.Terminal)
    @ Tachikoma ~/.julia/packages/Tachikoma/wxvSC/src/terminal.jl:578
  [5] (::Tachikoma.var"#167#168"{Int64, Bool, Kaimon.KaimonModel})(t::Tachikoma.Terminal)
    @ Tachikoma ~/.julia/packages/Tachikoma/wxvSC/src/app.jl:983
  [6] with_terminal(f::Tachikoma.var"#167#168"{Int64, Bool, Kaimon.KaimonModel}; tty_out::Nothing, tty_size::Nothing, on_stdout::Nothing, on_stderr::Nothing)
    @ Tachikoma ~/.julia/packages/Tachikoma/wxvSC/src/terminal.jl:1049
  [7] with_terminal
    @ ~/.julia/packages/Tachikoma/wxvSC/src/terminal.jl:1027 [inlined]
  [8] #app#160
    @ ~/.julia/packages/Tachikoma/wxvSC/src/app.jl:891 [inlined]
  [9] invokelatest
    @ ./Base_compiler.jl:250 [inlined]
 [10] tui(; port::Int64, theme_name::Symbol)
    @ Kaimon ~/Kaimon.jl/src/tui/lifecycle.jl:328
 [11] top-level scope
    @ ~/Kaimon.jl/bin/kaimon:183
 [12] include(mod::Module, _path::String)
    @ Base ./Base.jl:306
 [13] exec_options(opts::Base.JLOptions)
    @ Base ./client.jl:317
 [14] _start()
    @ Base ./client.jl:550
in expression starting at /home/jeff/Kaimon.jl/bin/kaimon:183
There seems to be a dependency issue now. I get this error when running ./bin/kaimon. I tried first to update via git pull but also tried a fresh install and get the same error in both cases. ``` ERROR: LoadError: MethodError: no method matching Tachikoma.Block(; title::String, border_style::Tachikoma.Style, title_style::Tachikoma.Style, title_right::String, title_right_style::Tachikoma.Style, title_padding::Int64) This method does not support all of the given keyword arguments (and may not support any). Closest candidates are: Tachikoma.Block(::String, ::Tachikoma.Style, ::Tachikoma.Style, ::NamedTuple) got unsupported keyword arguments "title", "border_style", "title_style", "title_right", "title_right_style", "title_padding" @ Tachikoma ~/.julia/packages/Tachikoma/wxvSC/src/widgets/block.jl:6 Tachikoma.Block(::Any, ::Any, ::Any, ::Any) got unsupported keyword arguments "title", "border_style", "title_style", "title_right", "title_right_style", "title_padding" @ Tachikoma ~/.julia/packages/Tachikoma/wxvSC/src/widgets/block.jl:6 Tachikoma.Block(; title, title_style, border_style, box) got unsupported keyword arguments "title_right", "title_right_style", "title_padding" @ Tachikoma ~/.julia/packages/Tachikoma/wxvSC/src/widgets/block.jl:12 Stacktrace: [1] kwerr(kw::@NamedTuple{title::String, border_style::Tachikoma.Style, title_style::Tachikoma.Style, title_right::String, title_right_style::Tachikoma.Style, title_padding::Int64}, args::Type) @ Base ./error.jl:175 [2] view(m::Kaimon.KaimonModel, f::Tachikoma.Frame) @ Kaimon ~/Kaimon.jl/src/tui/view.jl:218 [3] (::Tachikoma.var"#173#174"{Tachikoma.AppOverlay, Bool, Kaimon.KaimonModel})(f::Tachikoma.Frame) @ Tachikoma ~/.julia/packages/Tachikoma/wxvSC/src/app.jl:991 [4] draw!(func::Tachikoma.var"#173#174"{Tachikoma.AppOverlay, Bool, Kaimon.KaimonModel}, t::Tachikoma.Terminal) @ Tachikoma ~/.julia/packages/Tachikoma/wxvSC/src/terminal.jl:578 [5] (::Tachikoma.var"#167#168"{Int64, Bool, Kaimon.KaimonModel})(t::Tachikoma.Terminal) @ Tachikoma ~/.julia/packages/Tachikoma/wxvSC/src/app.jl:983 [6] with_terminal(f::Tachikoma.var"#167#168"{Int64, Bool, Kaimon.KaimonModel}; tty_out::Nothing, tty_size::Nothing, on_stdout::Nothing, on_stderr::Nothing) @ Tachikoma ~/.julia/packages/Tachikoma/wxvSC/src/terminal.jl:1049 [7] with_terminal @ ~/.julia/packages/Tachikoma/wxvSC/src/terminal.jl:1027 [inlined] [8] #app#160 @ ~/.julia/packages/Tachikoma/wxvSC/src/app.jl:891 [inlined] [9] invokelatest @ ./Base_compiler.jl:250 [inlined] [10] tui(; port::Int64, theme_name::Symbol) @ Kaimon ~/Kaimon.jl/src/tui/lifecycle.jl:328 [11] top-level scope @ ~/Kaimon.jl/bin/kaimon:183 [12] include(mod::Module, _path::String) @ Base ./Base.jl:306 [13] exec_options(opts::Base.JLOptions) @ Base ./client.jl:317 [14] _start() @ Base ./client.jl:550 in expression starting at /home/jeff/Kaimon.jl/bin/kaimon:183 ```
kahliburke commented 2026-03-07 13:12:55 -08:00 (Migrated from github.com)

Hi you're right it's a new thing in Tachikoma which hasn't yet been released. I thought I had avoided it but it slipped in. I'm building Tachikoma 1.0.4 which will be up shortly, and I'll update the dependency in Kaimon main.

You could in the meantime use `]add https://github.com/kahliburke/Tachikoma.jl" to get it pointed at the main unreleased branch which includes this.

Sorry for the hassle, let me know if it works.

Hi you're right it's a new thing in Tachikoma which hasn't yet been released. I thought I had avoided it but it slipped in. I'm building Tachikoma 1.0.4 which will be up shortly, and I'll update the dependency in Kaimon main. You could in the meantime use `]add https://github.com/kahliburke/Tachikoma.jl" to get it pointed at the main unreleased branch which includes this. Sorry for the hassle, let me know if it works.
kahliburke commented 2026-03-07 13:49:39 -08:00 (Migrated from github.com)

@jeffwack, the fix for that is in, I tested locally and did not get that dependency error. Hope it works for you too but let me know!

@jeffwack, the fix for that is in, I tested locally and did not get that dependency error. Hope it works for you too but let me know!
jeffwack commented 2026-03-07 14:40:48 -08:00 (Migrated from github.com)

It works in my environment too, and the original issue with Symbolics is resolved. Thanks for the quick fix.

It works in my environment too, and the original issue with Symbolics is resolved. Thanks for the quick fix.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kahliburke/Kaimon.jl#6
No description provided.