Summary
traceroot traces get never sends the fields query parameter, so the server's default skeleton projection applies and every span comes back with input: null, output: null, metadata: null — the exact payload someone debugging a trace needs. Observed on a real trace: traces get <id> --json returned 0/11 spans with I/O, while the same endpoint called directly with ?fields=full returned 11/11 inputs and 8/11 outputs (including full LLM prompt/completion payloads).
Meanwhile traces export bundles do contain the full payloads in spans.json (the export endpoint defaults to full server-side, per #14). So get and export silently disagree about the same trace, and nothing in help or README says get is the lightweight view. The fields parameter (core/usage/io/metadata, aliases skeleton/full) is already documented in the CLI's vendored openapi.json — the capability exists end to end; the client just never uses it.
How to replicate
- Pick a trace with LLM traffic from
traceroot traces list.
traceroot traces get <trace_id> --json | jq '[.spans[] | {name, input, output}]' — every span's input/output is null.
curl -H "Authorization: Bearer tr-..." "<host>/api/v1/public/traces/<trace_id>?fields=full" | jq '[.spans[] | {name, input, output}]' — payloads are populated.
traceroot traces export <trace_id> --output ./bundle — ./bundle/spans.json has the full payloads. Same CLI, same trace, two contradictory answers about whether I/O was captured.
Root cause (confirmed)
getTrace (src/api/client.ts:153-155) builds the URL with no query string, and neither traces get nor traces export exposes a --fields flag (src/commands/traces/get.ts:92-102 registers only <traceId>).
- The vendored
openapi.json documents the parameter and its default: skeleton unless fields=full (or fields=io,metadata) is passed; the server validates unknown groups with a 400.
- Nothing documents the get/export asymmetry, so a consumer scripting against
traces get --json concludes the payloads were never captured and stops investigating.
Impact
- The core debugging question — what did the model actually receive and return? — cannot be answered by the primary read command.
- Automated consumers see uniform
null I/O with no error and report "inputs/outputs were not captured", which is wrong.
- The only workaround is a disk round-trip (
traces export → read spans.json → clean up).
Acceptance criteria
traceroot traces get <id> --fields <groups> passes the value through to the server verbatim (a 400 for unknown groups surfaces as a normal CLI error).
traceroot traces export accepts the same flag (default remains full).
- The default projection for
traces get is documented in --help and the README, including how to get span I/O.
- Stretch:
traceroot traces get <id> --span <span_id> fetches a single span's untruncated input/output, so consumers can inspect one suspicious span without a full fetch.
- A test pins that the requested
fields value appears in the outgoing request URL.
Summary
traceroot traces getnever sends thefieldsquery parameter, so the server's defaultskeletonprojection applies and every span comes back withinput: null,output: null,metadata: null— the exact payload someone debugging a trace needs. Observed on a real trace:traces get <id> --jsonreturned 0/11 spans with I/O, while the same endpoint called directly with?fields=fullreturned 11/11 inputs and 8/11 outputs (including full LLM prompt/completion payloads).Meanwhile
traces exportbundles do contain the full payloads inspans.json(the export endpoint defaults tofullserver-side, per #14). Sogetandexportsilently disagree about the same trace, and nothing in help or README saysgetis the lightweight view. Thefieldsparameter (core/usage/io/metadata, aliasesskeleton/full) is already documented in the CLI's vendoredopenapi.json— the capability exists end to end; the client just never uses it.How to replicate
traceroot traces list.traceroot traces get <trace_id> --json | jq '[.spans[] | {name, input, output}]'— every span'sinput/outputisnull.curl -H "Authorization: Bearer tr-..." "<host>/api/v1/public/traces/<trace_id>?fields=full" | jq '[.spans[] | {name, input, output}]'— payloads are populated.traceroot traces export <trace_id> --output ./bundle—./bundle/spans.jsonhas the full payloads. Same CLI, same trace, two contradictory answers about whether I/O was captured.Root cause (confirmed)
getTrace(src/api/client.ts:153-155) builds the URL with no query string, and neithertraces getnortraces exportexposes a--fieldsflag (src/commands/traces/get.ts:92-102registers only<traceId>).openapi.jsondocuments the parameter and its default: skeleton unlessfields=full(orfields=io,metadata) is passed; the server validates unknown groups with a 400.traces get --jsonconcludes the payloads were never captured and stops investigating.Impact
nullI/O with no error and report "inputs/outputs were not captured", which is wrong.traces export→ readspans.json→ clean up).Acceptance criteria
traceroot traces get <id> --fields <groups>passes the value through to the server verbatim (a 400 for unknown groups surfaces as a normal CLI error).traceroot traces exportaccepts the same flag (default remainsfull).traces getis documented in--helpand the README, including how to get span I/O.traceroot traces get <id> --span <span_id>fetches a single span's untruncated input/output, so consumers can inspect one suspicious span without a full fetch.fieldsvalue appears in the outgoing request URL.