Skip to content

PatchWork Import Bugs

The PatchWork web UI throws a generic r.trim is not a function toast for most schema-mismatch failures, with no useful stack trace. This page is the catalog of known causes so you can identify which one you're hitting.

Every .nbflow we produce should be patched against this list before handing back to the user. The pre-generation sanity check automates most of these checks.

Bug 1: Dynamic dynamicRows must be flat strings, not objects

Symptom
Toast: r.trim is not a function on import.
Cause
The Dynamic Prompt's dynamicRows is [{label: "Scene 01", text: "<prompt>"}, ...] — array of objects. PatchWork's Prompt node calls r.trim() on each row directly and crashes when r is an object.
Fix
Flatten every dynamic row from {label, text} to just text. The label info is redundant (the node title carries the human-readable name).
for n in nodes.values():
    if n.get('type') == 'nanobanana/Prompt' and n.get('properties', {}).get('dynamicMode'):
        rows = n['properties'].get('dynamicRows', [])
        n['properties']['dynamicRows'] = [
            r['text'] if isinstance(r, dict) else r
            for r in rows
        ]

Bug 2: Template input slot name must equal the placeholder token

Symptom
Connection between Dynamic Prompt and Template Prompt appears in the file data but the UI shows them as disconnected. Generation receives the literal {placeholder} string.
Cause

The Importer creates template Prompts with inputs: [{name: "input", ...}] (generic name). PatchWork runs _syncTemplateInputs() on every import:

  1. Parses {placeholder} from the template text
  2. Removes any input slot whose name doesn't match a placeholder — destroying the connected link in the process
  3. Adds a fresh empty slot named after the placeholder

So name: "input" + template {scene} → input gets removed, link is destroyed, an empty scene slot is added → dynamic-to-template connection lost.

Fix

Rename input slots on every template Prompt to match the placeholder token in the template text. Both name and label should equal the placeholder.

Template type Input slot name
Image scene template scene
Veo speaking template dialogue (or whichever placeholder is used)
Veo speaking with pose dialogue + pose
B-roll image template broll
B-roll Veo template motion

Bug 3: Veo3 needs 3 input slots with spaces, not 2 with underscores

Symptom
Toast: r.trim is not a function on import. Or third slot silently missing in the UI.
Cause

The Importer emits 2 inputs with underscored names (start_frame). Canonical Veo3 inputs are:

"inputs": [
    {"name": "prompt", "type": "string", "link": null},
    {"name": "start frame", "type": "*", "link": null},
    {"name": "end frame", "type": "*", "link": null}
]

All three slots must exist with spaces in the names, not underscores. The end frame slot has link: null when unwired — but the slot itself must exist.

Fix
Restore the canonical 3-input shape on every Veo3 node. Use a known-good sibling .nbflow as a template if needed.

Bug 4: Veo3 needs model and negativePrompt properties

Symptom
Toast: r.trim is not a function.
Cause
The Importer leaves negativePrompt undefined. PatchWork's Veo3 code calls .trim() on it during import → crash.
Fix
Set negativePrompt: "" (empty string is fine — must be a string, not undefined). Also fill model: "veo-3.1-fast-generate-preview" (or the current Veo identifier), inputVariable, resolution, seed, imageCount, sampleCount, repeatCount, timeout, and the boolean UI flags (_advancedOpen, _disabled, _pinned, _savedGalleryIndex).

Bug 5: Template Prompt nodes need variableName and dynamicRows

Symptom
.nbflow imports but template Prompt node behaves oddly — onConfigure may try to read missing properties.
Cause
Templates carry variableName (string) and dynamicRows: [""] even though they're not in dynamic mode. The Importer often omits both.
Fix
Fill defaults on every template Prompt.
Symptom
Stale output.links arrays reference link IDs that don't originate from that output.
Cause
After a structural edit (fan-out renumber, variant patch, manual edit), output.links arrays don't get resynced from the graphData.links table.
Fix
After any structural edit, rebuild every output.links from the central links by scanning for (src_node_id, src_slot) matches. Use manager/scripts/_lib_link_refs.py (resync_link_refs(tab)).
Symptom
Avatar reference Media node imports but the UI shape is wrong.
Cause
Canonical Media node has inputs: [{name: "input", type: "*", link: null}]. The Importer sometimes emits empty inputs: [].
Fix
Add the empty input slot to match canonical shape — even though refs are data sources, the slot must exist for shape consistency.

Bug 8: NanobananaAPI input slot is image 1 (with space)

Symptom
Connection from a Media node to a NanobananaAPI's image input slot fails or maps to the wrong slot.
Cause
The Importer emits image_1 and image_2 (underscores). Canonical is image 1 and image 2 (spaces).
Fix
Rename input slots to use spaces. Same fix pattern as Veo3's start frame slot.
Symptom
In PatchWork UI, edges appear orphaned. When you run the workflow through the UI, reference images are NOT sent to G-Labs — generations come out looking like generic stock content, not matching the avatar's face or wardrobe.
Cause

PatchWork stores edges in two places:

  1. tab.graphData.links — the canonical central array. The headless workflow-runner.js reads this.
  2. node.outputs[slot].links + node.inputs[slot].link — per-node refs. PatchWork's UI renderer reads these.

Any mutation script (renumbering, fan-out, variant patcher) that updates the central array but doesn't resync the per-node refs leaves the file in an inconsistent state:

  • The connection exists in the data
  • PatchWork's canvas renders the edge as orphan
  • UI-driven generation walks the broken edge map and silently drops the reference
Fix

Mandatory rule for any script that mutates tab.graphData:

from _lib_link_refs import resync_link_refs, assert_clean

# ... do mutation work ...

for tab in workflow['tabs']:
    resync_link_refs(tab)
assert_clean(workflow)

json.dump(workflow, f, indent=2)

Helper lives at manager/scripts/_lib_link_refs.py. The PatchWork Importer and every fan-out / variant / merge script must call this.

Cached Media swap bug

Symptom
After a successful Generation Runner pass, the visual workflow graph in PatchWork shows "missing connections" — the avatar reference Media node and any setting / establishing Approve gates appear to have no outgoing edges, even though the link entries still exist in the data.
Cause

After a successful image generation, the runner replaces each nanobanana/NanobananaAPI node with a nanobanana/Media node holding the generated URL in properties.imageData.

  • Original API node had 3 input slots (prompt, image 1, image 2)
  • Media node has 0 input slots

The runner preserves outgoing edges but does not prune the incoming edges from avatar reference / setting reference image inputs. Those links still target slot 1 and slot 2 of the swapped node — slots that no longer exist on the Media node.

Fix when manually rewiring a runner output
  1. Use the pre-runner backup (in backups/{workflow}-{version}.nbflow.bak-*-pre-runner) to recover the original NanobananaAPI node templates.
  2. Replace each Cached Media node with the original NanobananaAPI structure (preserving the 3 input slots).
  3. Bake the cached image URL into properties._savedImages = [url] so the cached output is preserved.
  4. Set properties.outputCount = 4 and properties.model = "nano_banana_2".
  5. Re-run the link validator to confirm zero broken links.
Long-term fix
In the runner itself: set _savedImages on the existing API node and skip the type swap entirely.

Recovery pattern

When an Importer-emitted file fails to import:

  1. Identify a known-good sibling .nbflow from the same folder (or an older approved version).
  2. Capture its per-node-type canonical inputs / outputs / properties shape.
  3. Merge those defaults into the broken file (preserving the actual link IDs and per-node values).
  4. Run the pre-generation sanity check to verify zero dangling links.
  5. Try the import again.

The PatchWork Importer agent itself is being patched to emit the canonical shape going forward. Until that work is complete, run the sanity check on every Importer-produced file before assuming it'll import cleanly.