Skip to content
Expedify
9 min

Read the log, find the one that broke

One real execution log, one broken reference, and the debugging order that finds it in four steps.

Everything you have built so far works. This lesson is about the day it does not — and specifically about the failure that is worst to debug, which is not the one that crashes loudly. It is the one where a node reports success and the row it should have written is not there.

You are going to read one real execution log, find one broken reference, and fix it. The log is genuine, from this platform, and the bug is one that survived weeks of testing.

The symptom

A workflow ran. Seven nodes green. The eighth — the one that writes an audit note to the customer's record — is red:

CRM API Error (500): {'error': 'validation_error', 'field': 'system', 'message': 'An unexpected error occurred while creating the note', 'code': 'INTERNAL_ERROR'}

Which tells you almost nothing. field: system and INTERNAL_ERROR are the API saying "something you sent was unusable and I could not say which part". The temptation here is to go and read the note-creation code. Do not. Read what the node was actually handed.

Where the answer is

Open the failing node in the execution log and look at its resolved config — what the templates became, not what you typed:

{"content": "RESOLUTION EXECUTED. Reviewer score 5/5. Human decision: Approve.", "entity_type": "deal", "entity_id": ""}

There it is. entity_id: "". The content resolved perfectly — the score, the human's decision, all of it. One field came out empty, and the API's unhelpful 500 is what an empty foreign key looks like from the other side.

The node was configured with this:

{{account_lookup.results[0].id}}

Which looks entirely reasonable. There is a node called account_lookup. It does return results. And the log carries one more line that explains everything:

template_variables_detected: []

Zero. The reference was not resolved and found empty — it was never recognised as a reference at all. That distinction is the entire diagnosis.

Why it could never have worked

Compare the node list the execution actually produced against the canvas you drew:

Two nodes on the canvas produced no top-level result at all.

trigger, attempts, proposer, reviewer, verdict, gate, approval, audit

In the execution log
all present ✓

account_lookup

In the execution log
absent

policy_lookup

In the execution log
absent

Both missing nodes are tools — attached to an agent's tool handle rather than wired into the flow. A tool runs inside the agent that owns it, and its output lands in that agent's tool-call record. It is never a top-level step, so {{account_lookup.…}} has nothing to point at. It resolves to nothing, quietly, and the workflow carries on.

The rule to carry away. You can only reference a node that appears in the execution log as its own step. If it is a tool, its results belong to the agent, not to the workflow.

The fix, and the fix that made it worse

The obvious repair is to have something in the flow carry the id forward. That was tried, and it is worth knowing how it went, because it is the more instructive half:

The middle row passes every format check a reviewer could apply.

Ask the agent to state the record id

Result
It stated the account's NAME
Verdict
Wrong, but obviously wrong

Tell it the id is a UUID, not the name

Result
It produced a perfectly-formed UUID matching no record in the database
Verdict
Wrong, and invisible

Look the record up in a real step, by name

Result
The database returned the actual id
Verdict
Correct

Tightening the instruction did not make the model more accurate. It made the error better camouflaged. The working fix does not ask the model for the identifier at all: the model supplies a search term, and a query step supplies the id. A wrong search term returns zero rows and fails loudly. A wrong UUID fails silently forever.

Why nobody caught it for weeks

The broken node sat behind a human-approval gate. Every automated test stopped at the gate, because a machine cannot click Approve. Every node before it was verified dozens of times. The one node nobody could reach was the one that did not work.

You have not tested the path you cannot reach. Whatever sits after your approval step, your error branch, or your rare condition is the least-tested code you own — and it is usually the code that writes things down. Reach it deliberately, by hand, at least once.

The debugging order that works

  1. Read the failing node's resolved config — what the templates became, not what you wrote.
  2. Find the empty field. An unhelpful 500 is usually a blank where an id should be.
  3. Check the referenced node is in the log at all. If it is missing, it is a tool, and no wording of the reference will fix it.
  4. Only then read the node's code. You will almost never get this far.

Next: the capstone — the whole desk, built by you, with every one of these habits in it.

Related lessons