CMK LOG CONCAT
Stitch 32 log blocks back into one readable report
- LOG
- log_block
- log_block_2
- log_block_3
- log_block_4
- log_block_5
- log_block_6
- log_block_7
- log_block_8
- log_block_9
- log_block_10
- log_block_11
- log_block_12
- log_block_13
- log_block_14
- log_block_15
- log_block_16
- log_block_17
- log_block_18
- log_block_19
- log_block_20
- log_block_21
- log_block_22
- log_block_23
- log_block_24
- log_block_25
- log_block_26
- log_block_27
- log_block_28
- log_block_29
- log_block_30
- log_block_31
- log_block_32
- LOG
CMK's modules each append a block to a log pipe: what they detected, what resolutions they used, whether they bypassed. That's great when everything flows in one line, and annoying the moment you run parallel branches - three detailer branches, three logs, and no way to see them together. CMK LOG CONCAT is the merge point. One LOG in, up to 32 CMK_LOG_BLOCK inputs, one LOG out.
The awkward part of the wiring
The inputs are called log_block, log_block_2, log_block_3 … log_block_32. The first one has no number, which is the kind of naming that looks like a typo and isn't. LOG is required; every block input is optional, and you use as many as you need.
A CMK_LOG_BLOCK isn't a data structure on the wire in the usual sense - it's a serialized string, prefixed CMK_LOG_BLOCK:: and containing JSON with the block's title, order, lines and enabled flag. That's why the nodes that produce them (like Prepare) emit a STRING-typed socket. The concat node parses them back with the same helper the rest of the pack uses, tolerates junk (anything that doesn't parse into a valid block is silently skipped), and drops disabled blocks along with any block whose lines are all blank.
What happens to order
Every block carries an order integer - the pack's modules ship with numbers like 44 for prepare, 45 for the detailer, 46 for pasteback. It's tempting to assume CMK LOG CONCAT sorts by it. It doesn't.
The pack's renderer is explicit in its comments: a CMK log pipe is an append-only representation of the actual modular data flow, so blocks render in the order they were appended, and the legacy order field is kept only for format compatibility. Concretely, that means the order you wire the block inputs determines the order they appear in the report. Which is honestly the more useful behaviour when you're debugging - you get your graph's reading order, not a numbering scheme from someone else's reference workflow.
What you get out
The LOG output is the same pipe shape the modules thread around: a dict with a blocks list. Feed it to any node that accepts a log pipe, or render the whole thing as text with CMK Log Export Text.
If you'd rather write your own notes into the report, CMK Log Set Block appends a titled block of free text to a pipe - same idea, no parsing required.
Install
cd ComfyUI
git clone https://github.com/CMKFlow/cmk_nodes.git custom_nodes/cmk_nodes
python3 custom_nodes/cmk_nodes/scripts/install_cmk_requirements.py
Or ComfyUI Manager → CMK Flow (registry cmk-flow) → install → restart. This node itself is pure Python with no model dependencies, but it ships inside a pack whose requirements are not light - insightface, onnxruntime, ultralytics, segment-anything, nudenet==3.4.2, opencv-python, imageio-ffmpeg - and the installer script is what puts them in the right Python environment, which is genuinely a problem on ComfyUI Desktop where system Python, standalone-env and .venv all sit side by side.
When it's actually useful
Two cases. First, comparing parallel branches: run two detailer settings on one image and merge both logs so you can see the exact parameters that produced each variant, in one panel, instead of scrolling console output. Second, module boundaries: the pack's architecture docs note that at a module boundary the LOG CONCAT node is what materializes the complete log for that module, which is how a boundary cache can verify it's serving a result that matches the current branch state.
It's a plumbing node. You'll forget it exists until a CMK graph has four branches, and then it's the difference between a report and a wall of noise.
Inputs (33)
| Name | Type | Default | Description |
|---|---|---|---|
| LOG | CMK_LOG_PIPE | — | |
| log_blockopt | CMK_LOG_BLOCK | — | |
| log_block_2opt | CMK_LOG_BLOCK | — | |
| log_block_3opt | CMK_LOG_BLOCK | — | |
| log_block_4opt | CMK_LOG_BLOCK | — | |
| log_block_5opt | CMK_LOG_BLOCK | — | |
| log_block_6opt | CMK_LOG_BLOCK | — | |
| log_block_7opt | CMK_LOG_BLOCK | — | |
| log_block_8opt | CMK_LOG_BLOCK | — | |
| log_block_9opt | CMK_LOG_BLOCK | — | |
| log_block_10opt | CMK_LOG_BLOCK | — | |
| log_block_11opt | CMK_LOG_BLOCK | — | |
| log_block_12opt | CMK_LOG_BLOCK | — | |
| log_block_13opt | CMK_LOG_BLOCK | — | |
| log_block_14opt | CMK_LOG_BLOCK | — | |
| log_block_15opt | CMK_LOG_BLOCK | — | |
| log_block_16opt | CMK_LOG_BLOCK | — | |
| log_block_17opt | CMK_LOG_BLOCK | — | |
| log_block_18opt | CMK_LOG_BLOCK | — | |
| log_block_19opt | CMK_LOG_BLOCK | — | |
| log_block_20opt | CMK_LOG_BLOCK | — | |
| log_block_21opt | CMK_LOG_BLOCK | — | |
| log_block_22opt | CMK_LOG_BLOCK | — | |
| log_block_23opt | CMK_LOG_BLOCK | — | |
| log_block_24opt | CMK_LOG_BLOCK | — | |
| log_block_25opt | CMK_LOG_BLOCK | — | |
| log_block_26opt | CMK_LOG_BLOCK | — | |
| log_block_27opt | CMK_LOG_BLOCK | — | |
| log_block_28opt | CMK_LOG_BLOCK | — | |
| log_block_29opt | CMK_LOG_BLOCK | — | |
| log_block_30opt | CMK_LOG_BLOCK | — | |
| log_block_31opt | CMK_LOG_BLOCK | — | |
| log_block_32opt | CMK_LOG_BLOCK | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| LOG | CMK_LOG_PIPE | — |