fix: resolve $0.00 session costs in tapes deck#133
Conversation
Response nodes since ~Feb 19 have empty model fields, causing cost calculation to skip them. This adds a last-seen model fallback in sessionFromNodes, adds claude-haiku-4.6 to the pricing table, and falls back to the request model in streaming responses.
oppegard
left a comment
There was a problem hiding this comment.
I'm not really familiar with much of the codebase yet, so let me know if this is a non-issue.
Codex found this issue:
Apply empty-model fallback in detail costing
buildSessionSummaryFromNodes now reuses the previous model when n.Model is empty, but buildSessionMessages still calls costForNode, which returns zero cost for empty-model nodes. In sessions with streamed responses missing Model, the summary and overview totals will now include those costs while per-message and grouped-message totals remain at zero, producing inconsistent cost reporting for the same session (summary vs detail view).
I had it write a simple failing test to show the inconsistency. I ran code coverage on the deck package as well, and it appears we don't have any tests exercising buildSessionMessages() in query.go.
It("keeps summary and message totals consistent", func() {
pricing := DefaultPricing()
q := &Query{pricing: pricing}
nodes := []*ent.Node{
{ID: "node-1", Role: "user", Model: "claude-opus-4-6-20260219"},
{
ID: "node-2",
Role: "assistant",
Model: "",
CompletionTokens: intPtr(500000),
},
}
summary, _, _, err := q.buildSessionSummaryFromNodes(nodes)
Expect(err).NotTo(HaveOccurred())
messages, _ := q.buildSessionMessages(nodes)
messageTotal := 0.0
for _, msg := range messages {
messageTotal += msg.TotalCost
}
Expect(summary.TotalCost).To(BeNumerically("~", messageTotal, 1e-12))
})
bdougie
left a comment
There was a problem hiding this comment.
Good catch — this is a real inconsistency. buildSessionMessages was calling costForNode which bails on empty models, while buildSessionSummaryFromNodes had the last-seen model fallback. Summary says "$X" but per-message detail sums to "$0" for those nodes.
Fixed in the latest push:
- Renamed
costForNode→costForModel(accepts a model string directly) - Added the same
lastModeltracking tobuildSessionMessages - Resolved model is now set on
SessionMessage.Modeltoo, so the detail view shows the correct model - Added your consistency test (
summary.TotalCost ≈ Σ message.TotalCost) — 126 deck tests and 55 proxy tests pass
|
No docs update needed. This PR fixes an internal bug in session cost calculation where response nodes with empty model fields were being skipped. The changes are:
These are all internal implementation fixes that don't change any user-facing configuration, commands, or documented behavior. The cost display in PR #133 was merged: fix: resolve $0.00 session costs in tapes deck |
Summary
sessionFromNodesloop and use it when response nodes have empty model fields. This fixes the root cause : 2042 of 2048 of my response nodes since Feb 19 had empty models, causing cost calculation to skip them entirely.claude-haiku-4.6toDefaultPricing()so sessions using it get costed correctly.enqueueStreamedResponse, fall back to the request model when the reconstructed response has an empty model, preventing future empty-model nodes.Test plan
claude-haiku-4.6resolution (exact and date-suffixed)go build ./...succeedsContinue Tasks: ✅ 1 no changes — View all