# Hunting down bugs in a trenchcoat: why some records stayed locked

Posted on [date: 2026-10-15T10:26:13.348+02:00] by Ronak Ganatra , Silvano Stralla and Roger Tuan

Turns out it's reallllly fun going down 🐰🕳️🕳️🕳️ for us lately, and thanks to y'all, it's helping us surface (and learn from) some pretty damn interesting stuff.

The last rabbit hole was on how a customer's [S'gaw Karen content was showing weird dotted circles in Firefox, and how we went down said hole to eventually fixed it for 43 scripts rather than just one](https://www.datocms.com/blog/handling-less-common-scripts.md). Fun read.

But. On to this one.

### TLDR

-   A customer told us their record stayed locked after hitting Save. Wut?
-   Easy fix? Not really. Turns out it was 4 separate bugs working in tandem, and one of them had been chilling in our code for 4 years.
    
-   All 4 are fixed now (plus a 5th we found while fixing the others 🙃), so locks clear when they should.
-   Oh. And if you run scripts or agents that write to records while your team is editing, this one's for you too.
    

## Wait, what's a record lock again?

We [shipped this back in 2019](https://www.datocms.com/blog/new-collaboration-tools.md) 🦠😷🫗

When you open a record in DatoCMS, everyone else looking at it sees your avatar at the top. That's the presence indicator.

The moment you start editing, the record gets locked. Your colleagues can still see the record page, but they can't edit it until you save or close it.

The whole spiel was that multiple people couldn't overwrite each other's work into some weird soup. Simply lovely. Much ✨collaboration✨. [The docs explain it with considerably less saas here](https://www.datocms.com/docs/general-concepts/collaboration-features.md).

Behind the scenes, your browser also pings us every 30 seconds or so to say "eyo I'm still here, still editing". That's the keep-alive we'll dive into more later.

And if something writes to a record through the API while someone is editing the record with the lock, it gets a `422 ITEM_LOCKED` back. Which is exactly what's supposed to happen BTW.

Usually.

## "My lock sticks after Save"

That was pretty much the report we got earlier this month.

A customer would save a record inside a modal when editing a linked record, but after they closed the modal, their avatar just... stayed there. The record looked locked waaay longer than it should have, and their scripts kept bumping into `ITEM_LOCKED`.

Sounds like a UI glitch. One bug, one fix, ciao grazie.

Except NOBODY could *reliably* reproduce it. Not anyone from their team. Not even us. Sometimes it happened. Sometimes the lock "cleared by itself". And sometimes it *LOOKED* cleared while writes were still failing. 🫠

So we kept diving in to figure this out. We found one of the bugs by hand. Then another showed up. Then another. Then another. Those other three took a LOT more digging.

## Meet the 🐞🐛🪲🪳

*(4 bugs in a trenchcoat, if you will - very 90's cartoon of kids trying to look like an adult getting a ticket to the cinema - easier to visualize.)*

1.  **The stuck lock**: Saving a record that creates a new block (adding a block, or adding a locale and copying content over) never unlocked it. For one render, the form got a stale list of blocks, apparently panicked, and showed a spinner and remounted fresh. The fresh form had no idea it had just been saved, so it never sent the unlock, and the keep-alive happily kept the lock alive until you closed the record. This is the one that had been hiding for 4 years. Thanks, React.
    
2.  **The stale "unlocked" message**: When you save, the "this record is free now" update goes out to everyone else with a 3 second delay. If you started editing again within those 3 seconds, the newer "locked" update arrived first, and then the old "unlocked" one landed on top of it. So your colleagues saw the record as free game while it was still very much still locked.
    
3.  **The orphaned modal lock**: Modals get their own editing session. If you reloaded or closed the tab while a modal held a lock, we cleaned up the page's session, but not the modal's. So you'd reopen the record and get "Record editing is disabled!"... with YOUR name on it. Locked out by yourself for 2 to 3 minutes until our cleanup job swept it away.
    
4.  **The silent failed lock**: Thanks to bug 2, a colleague would see the record as free and start typing. Their CMS tried to take the lock, the server said no, and the UI ignored that and told them "You are editing this record" anyways. Then Save failed. And sometimes their edits got wiped too. Not great.
    

**But how did they team up?** That was the fun (🤨) part.

Bug 1 makes the lock stick.

Bug 2 hides it.

The record *LOOKS* free, but writes still fail.

Your colleague trusts the indicator and starts editing. Oh hey look, it's bug 4.

Close the tab instead of the modal, and bug 3 joins the party 🥳

(Image content)

Each one on its own looks like a tiny glitch. Together they make both the lock AND the indicator unreliable, but only with a very particular combination of the right timing, the right number of tabs, and the right person clicking the right thing within 3 seconds. Or rather, a very particular combination of the *wrong* timing, the *wrong* number of tabs, and the *wrong* person clicking the *wrong* thing within 3 seconds 😫

Which is why it was such a chooooore trying to reproduce them.

## How we actually found them

Our support team found the first one the old fashioned way. Clickety clickety.

For the other three, we pointed Claudia at the customer video, said "here's the code", and asked it to go clue hunting. He/she/it developed some initial hunches, but needed a better way to verify them.

Headless CMS, meet headless browser: [Playwright](https://playwright.dev/) is a Microsoft tool for controlling headless browsers and driving them for frontend testing, similar to Puppeteer or Selenium. Its a little like the [Claude in Chrome MCP](https://code.claude.com/docs/en/chrome), but faster, and way more powerful, AND able to run in the background without stealing your focus or your cursor. So we taught Claude Code [how to use Playwright](https://playwright.dev/agent-cli/skills) for this instance in a very labor-intensive way (by throwing a .md file at it).

Using Playwright, Claude spun up a few dozen headless browser instances in Blink, Webkit, and Gecko and had them all poke at the CMS UI over and over under different permutations of relatively unlikely user interactions, all in the background. One of these tests eventually isolated the first timing bug, and from there, further UI "fuzzing" discovered the rest.

(Image content)

These were not scenarios that a human tester would have *reasonably* found on their own, no matter how hard they tried (what do you mean, you didn't test the fourth field in the modal of the third link in the seventh block in the third browser tab exactly 57 milliseconds after the second one?! Amateur.). They were unlikely race conditions that required a specific combination of async operations across 2+ browser tabs of the same session at the same time, executed in a specific order within a 100ths-of-milliseconds window. Our customer discovered them only by chance (*somebody* was bound to, eventually, at our scale), but even to them it just looked like general unreliability. It took AI testing & fuzzing to really isolate the specific bugs and the particular conditions that triggered them.

From there on our devs fixed all 4, plus two smaller issues that popped up, plus a shiny brand new bug they found along the way (the CMS was deleting a modal's session the moment the modal opened, IF YOU MUST KNOW 🙃).

## What's different now

-   Saving a record with new blocks unlocks it properly
-   Old "unlocked" updates get ignored if there's a newer lock, so the indicator shows what the server actually thinks
    
-   Reloading or closing a tab cleans up every session it opened, modals included. No more locking yourself out
-   If the CMS can't get the lock, it surfaces that information to the user, instead of letting you type into a record you won't be able to save
    

If you're running scripts, integrations, or agents that write through the API while your team edits: `ITEM_LOCKED` should now only show up when someone is *actually* editing.