Casa · log run
One JSON line per run, so the cost of your pipeline isn't a guess
- line
"It cost about a tenth of a cent per image, I think" is not a number. This node is how the pack's author got to a real one: the whole project - every benchmark, A/B, grid and rejected render included - came to $0.145, and $0.021 per approved piece, because every stage wrote a line into a file and the table was generated from those lines instead of estimated after the fact.
CasaLogRun is that writer. It appends one JSON object to out/runs.jsonl and hands the same object back as a string.
What lands in the file
{"ts":"2026-09-25T14:22:07+00:00","stage":"graph_render","item":"street/restored",
"backend":"local_comfyui","ms":133000,"tokens_in":0,"tokens_out":0,
"cost_usd":0.0,"cache_hit":false,"source":"comfy_graph","notes":"PASS edge 0.882 ..."}
Not glamorous, but it's the right shape: a timestamp in UTC, the stage, the item, which backend did the work, wall time in milliseconds (you type seconds, it multiplies by 1000), token counts, dollars, and a free-text notes field.
The inputs
stage defaults to graph_render and is the row key in the cost table - render, qa_gate, copy, layout, whatever vocabulary you use. item is the thing being logged (street/restored in the master graph). backend is a three-way enum: local_comfyui, comfy_cloud, partner_api.
That enum isn't decoration. The three cost you different things, and conflating them is exactly how people end up with a useless spreadsheet. A local_comfyui row has no dollars attached - the cost is your electricity and your GPU seconds, which the pack's cost table prices at $0.10/kWh as a stand-in. A comfy_cloud row is rented GPU time (the table uses Comfy Cloud's RTX 6000 Pro rate of $3.49/hour). A partner_api row is metered credits against a pay-as-you-go balance that is separate from a Cloud subscription - so it's the row most likely to be the reason your run stopped. seconds is actually seconds as a float, tokens_in / tokens_out are ints for LLM stages, cost_usd takes four decimal places so you can log sub-cent amounts, and notes is multiline.
The output and why it's an output node
line is the JSON that was just written, so you can pipe it into a text display and watch runs accumulate in the console area. The node is also marked as an output node, which in ComfyUI means it always executes - you don't have to wire line to anything for the write to happen. That's the right call for a logger: wiring up a consumer just to get a side effect is a workflow smell.
Why you'd actually bother
Because it makes two cheap tricks available. First, wire CasaQAGate.report into notes - the pack's master graph does exactly this - and every run line carries its own gate verdict next to its cost. You stop keeping a separate QA spreadsheet. Second, run the pack's aggregator:
python pipeline/cost_table.py
That reads out/runs.jsonl, groups by stage and backend, counts cache hits and errors, totals energy and dollars, then writes docs/costs.md including the line that matters - cost per approved piece, computed from the approved list in config/approved.json. The published table also carries the gate's own scoreboard underneath, and it's the most useful number in the file: 6 of 18 verdicts passed across all attempts. Two thirds of the renders failed and got thrown away, and it still came to two cents a piece, because the expensive part was never the retry.
Install
It's part of the same pack: ComfyUI Manager → From 3D to Campaign, or git clone into ComfyUI/custom_nodes. No models, no extra packages - it's stdlib JSON and file appends.
Gotchas
It writes inside the pack, not inside ComfyUI/output. The path is resolved relative to the repo root, so with a Manager install your log lands in ComfyUI/custom_nodes/from-3d-to-campaign/out/runs.jsonl. Backing up or clearing that file is on you; nothing rotates it, and it grows one line per stage per run forever.
Don't call your gate stage qa_gate unless you mean it. The cost table deliberately skips rows with that stage name from its per-stage aggregation and prints the gate's pass/fail counts separately instead. Log the gate as qa_gate and you're opting out of the table - which is fine, as long as it's a choice.
cache_hit is always written as false. For graph runs that's honest; it's the headless exporter and the CLI stages that set it true. If you want a real cache figure from the graph side, you already have it - CasaRender3D's info string tells you from cache or the render milliseconds, and that's the string worth putting in notes.
The seconds field is easy to misuse. It's wall time, and for a sampler you'll be guessing unless you read it off the console. If precision matters to you more than convenience, log 0 and let the energy model in the cost table do the reasoning instead of pasting a made-up number into a field named seconds.
Inputs (8)
| Name | Type | Default | Description |
|---|---|---|---|
| stage | STRING | graph_render | — |
| item | STRING | — | |
| backend | COMBO | 3 options: local_comfyui, comfy_cloud, partner_api | |
| seconds | FLOAT | 0.0 | — |
| tokens_in | INT | 0 | — |
| tokens_out | INT | 0 | — |
| cost_usd | FLOAT | 0.0000 | — |
| notes | STRING | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| line | STRING | — |