DEV Community

Cover image for The human clicked Approve. The agent applied it to a different question.
Dhrubojyoti Gangopadhyay
Dhrubojyoti Gangopadhyay

Posted on

The human clicked Approve. The agent applied it to a different question.

LangGraph just gave every interrupt its own id and added a mode that refuses stale answers. Here is the bug that closes.

The human clicked Approve. The agent applied it to a different question.

On 5 November 2026, langgraph 1.2.20 shipped two release-note lines that read like plumbing:

give each interrupt() in a task its own id

add a strict resume mode that refuses stale interrupt answers

They are not plumbing. They are a correctness fix for one of the nastiest failure shapes in human-in-the-loop systems.

The shape of the bug

An agent pauses to ask a human something. In LangGraph that pause is an interrupt(). The run suspends, the question goes to a person, and the answer comes back later — four minutes later, forty minutes later, tomorrow morning.

"Later" is where the trouble lives. Between the question and the answer, the run can be retried, re-planned, or restarted. Interrupts get raised again on the new path.

Now: how does the resumed run know which question the answer belongs to?

If interrupts have no stable identity, the only thing available is position. First interrupt gets the first answer. And position is not identity.

A concrete sequence:

  1. The agent reaches its first pause and asks: approve payment to vendor A, ₹4,00,000.
  2. A human looks at that, and approves it.
  3. Meanwhile the run is retried — a transient tool error, a restart, a re-plan. The graph rebuilds. This time the first pause is a different question: approve payment to vendor B.
  4. The resume applies the stored "yes" to position one.
  5. Vendor B is paid. Nothing throws. The run reports success.

A human authorised a payment they never saw. There is no exception, no alert, and the audit trail says "approved by " — which is true, and misleading.

Why strict resume matters more than it sounds

The fix has two halves and you need both.

Identity: every interrupt carries an id that survives a retry. The question has a name, not just a slot.

Refusal: the resume path verifies that id and refuses a mismatch rather than falling back to position. That is the "strict" in strict resume mode.

Refusing is the important half. A system that detects the mismatch and then does something reasonable-looking — re-asking silently, picking the closest match, defaulting to deny — has just invented a new way to be wrong. Stop, and make a human look again.

The approval record this implies

If you take this seriously, an approval stops being a boolean and becomes a record that names its own question:

approval_id        appr_01J...
interrupt_id       intr_7f3a...      <- the question's identity
run_id             run_55c1...
asked              {"tool": "pay_vendor",
                    "vendor_id": "V-2291",
                    "invoice": "INV-88412",
                    "amount_paise": 40000000}
asked_sha256       9f2c...e4a1
decision           approved
decided_by         <user id>
decided_at         2026-11-10T11:49:08+05:30
Enter fullscreen mode Exit fullscreen mode

On resume you check interrupt_id matches, re-hash asked, and compare. Mismatch means stop, not improvise.

Note what this record stores: what was asked, not only what was decided. A decision without its question cannot be audited six months later, when someone asks what exactly was approved.

A pause is not a failure

From the same release window, langgraph 1.2.19 (30 October 2026) fixed:

don't report a paused tool as failed on the tools stream

A tool waiting on a human was being reported as failed. That is the same family of bug seen from another side: a system that cannot distinguish "waiting for a person" from "errored" will retry, escalate, or roll back things a human is still thinking about.

Three states, not two. Running, failed, and waiting on a human — which is neither.

Two hard rules

An approval without an identity is a coin flip you cannot audit afterwards. It may well land on the right question most of the time. You will not be able to prove which times.

If a stale answer can be accepted, it eventually will be — and it will happen on the day the plan changed, which is exactly the day it matters most.

Honest limitation

I am reading release notes and reasoning from them. I have not reproduced either failure, I am not reporting the maintainers' stated motivation, and the approval-record shape above is a design I use rather than anything the library prescribes. The ₹ figures are illustrative.

Checkable references

  • langgraph 1.2.20 (5 November 2026) — "give each interrupt() in a task its own id"; "add a strict resume mode that refuses stale interrupt answers"
  • langgraph 1.2.19 (30 October 2026) — "don't report a paused tool as failed on the tools stream"

One open question

When your agent asks a human to approve something, does the answer know which question it belongs to — and what happens if it does not?

Top comments (0)