← Blog

Process automation

n8n war story: a Google Drive scope downgrade hid 287 PDFs

A Haarlem media agency ran n8n every Sunday to drop client PDFs into shared Drive folders. The dashboard stayed green while 287 reports landed in the wrong place.

Jacob Molkenboer· Founder · A Brand New Company· 12 Jun 2026· 6 min
Cream envelope with chartreuse wax seal on ivory blotter, brass tag on twine, dispatch slips, side window light.

On a Monday at 09:11, the founder of a 21-person media agency on the Spaarne walked into her office and found three of her account managers staring at the same screen. Their best retainer client had emailed: "Why are last week's reports in someone else's Drive folder? And why is there a German pet food brand's media plan in ours?"

Her n8n dashboard was green. It had been green for thirteen days. In that window, the agency's weekly client-reporting workflow had moved 287 PDFs into the wrong shared-drive folders, mostly into the service account's own My Drive, where nobody had thought to look. The Google Drive API never returned a 4xx. n8n never threw an error. The workflow ran 41 times, all marked success.

We came in on day fourteen. Here is what we found.

How the workflow was wired

The agency had built a tidy weekly automation in n8n, self-hosted on a Hetzner box. Every Sunday at 22:00, a Cron node kicked off a fan-out that did the same six things per client:

  1. Pull last week's metrics from Meta, Google Ads, and GA4.
  2. Render a PDF using a Carbone template.
  3. Look up the client's Drive folder by name in their Shared Drive.
  4. Upload the PDF using the Google Drive node, with that folder as the parent.
  5. Post a Slack message in #client-reports with a permalink.
  6. Append a row to a tracking sheet.

The auth was a service account with domain-wide delegation, set up two years earlier by a freelancer who no longer answered emails. It worked perfectly. Until it didn't.

The scope that changed under their feet

Thirteen days before the inbox lit up, the agency's IT lead had done what every Workspace admin is told to do. He reviewed third-party OAuth apps in the Admin Console and tightened scopes on anything that looked permissive. The n8n service account's client ID was using https://www.googleapis.com/auth/drive, the full-fat scope. He changed it to https://www.googleapis.com/auth/drive.file, which Google explicitly recommends as the least-privilege option.

The drive.file scope only allows the app to see and modify files that it created, or files that have been explicitly shared with it through a Google Picker. Everything else, including the client-named folders in the agency's Shared Drive, becomes invisible to the service account. Not forbidden. Invisible.

The IT lead saved his change. Nothing alerted. n8n's Drive credential kept working because the OAuth refresh still succeeded. The token simply came back with a narrower scope claim. No 4xx, because nothing had been rejected. The rules of the game had changed and the game kept playing.

Why the API never said no

This is the part that catches operators off guard, so it is worth saying plainly. Reducing an OAuth scope does not retroactively break tokens. It changes what new tokens are allowed to do. With drive.file, the service account could still:

  • Authenticate.
  • Create new files, which become "files created by the app".
  • Read and modify those files later.
  • Resolve 'root' as a parent, meaning its own My Drive root.

What it could no longer do was see folders it had not created. So the "search for folder by name" step at position 3 returned an empty result. No error. Just an empty list.

Here is where the workflow shot itself in the foot. The folder lookup fed a Set node that resolved the parent ID, with a defensive fallback:

// In the Set node, "Folder ID" field
{{ $json["files"][0]["id"] || "root" }}

That fallback had been written months earlier to handle "what if a brand new client doesn't have a folder yet, let it land in root and we will sort it manually". It was meant to fire never. Once the scope changed, it fired for every client, every week.

The Drive upload node then ran with parents: ["root"] and supportsAllDrives: true. The API happily wrote the file. To the service account's own My Drive, which no human had ever logged into. 200 OK. Slack got a permalink that, if you clicked it, returned a 404 because no humans had access to that file. But nobody clicked the permalinks. The workflow had been green for two years and the team had stopped looking.

Warning

Any expression in your automation that ends with || 'root', ?? defaultFolderId, or || process.env.FALLBACK_ID is a silent-failure waiting list. Defensive defaults turn missing data into wrong data.

What the run history actually showed

n8n's execution log is honest. It records what each node returned. The Drive search node returned []. The Set node returned { folderId: "root" }. The Drive upload node returned a valid file resource. Every step succeeded. The workflow status field said "success" because no node threw.

If you had been reading the logs, you would have seen it on day one. Nobody reads the logs of a workflow that has been green for two years. That is the actual lesson, and it has nothing to do with Google or n8n. The system was doing exactly what it was told. Nobody had thought to ask it what it was doing.

What we changed

Killed the silent default. The || "root" fallback became a hard error. If the folder lookup returns empty, the workflow now throws and the Slack alert goes to #ops, not #client-reports.

Added a contract check at the top. Before any client gets processed, the workflow lists the expected client folders by name and asserts the count matches what is in the tracking sheet. If a folder has gone invisible, the workflow stops on the first client and screams.

Pinned the scope explicitly. The n8n credential now requests drive, and we documented why in a comment on the Workspace Admin Console entry. Least-privilege is correct in principle, but it has to be paired with an actual access map. We added a quarterly review where IT and the automation owner sit down and reconcile.

Turned on a canary. Every run now writes one extra file, a one-line health PDF, into a known canary folder, and then reads it back. If the read returns 404, the workflow fails loudly. This catches drift in the exact place where drift happens.

Recovery of the 287 PDFs was the easy part. The service account owned them. We added a human user using domain-wide delegation impersonation, batch-moved the files to their correct destinations, and the agency sent a one-paragraph note to the affected clients explaining what had happened.

The shape of the bug, generalised

If you run any automation that touches a third-party API with scoped auth, the pattern to watch is this. A config change happens somewhere outside your code: admin console, OAuth consent screen, IAM role, billing tier. Your workflow keeps running because nothing it does gets rejected. The 4xx you were counting on never arrives, because the change moved you into a smaller permission set where the new, smaller operations are still valid.

You cannot prevent the config change. You can only make your workflow notice that the world it expects no longer exists. That is what contract checks and canary writes are for. An automation that has been green for two years is not a working automation. It is an automation that nobody has tested in two years.

What we built for them

When we rebuilt the reporting workflow for the Haarlem agency, the thing we ran into was that n8n's Drive node does not surface the actual OAuth scope it received on the token. We ended up adding a small pre-flight node that calls tokeninfo and asserts the scope string before any other step runs. That two-second check is now in every n8n process automation we ship with a Workspace dependency.

Here is the smallest thing you could do today. Open the run history of your most important automation. Pick a successful run from last week. Read every node's output. If you cannot tell, from the JSON alone, whether the workflow did the right thing, your green dashboard is decorative.

Key takeaway

An automation that has been green for two years is not a working automation. It is an automation that nobody has tested in two years.

FAQ

What is a Google Drive OAuth scope downgrade?

A change to the scopes an app or service account is allowed to request, which reduces the permissions of new tokens. Existing tokens keep working until they expire; new ones come back narrower.

Why didn't the Drive API return a 4xx error?

The service account still had a valid token and could still upload. It just could not see the destination folders. Upload to a fallback 'root' parent succeeded, so the API answered 200.

How do I detect this kind of silent failure in n8n?

Add a pre-flight node that calls the tokeninfo endpoint and asserts the OAuth scope string. Pair it with a canary write to a known folder, then read the file back. If the read 404s, fail loudly.

Is drive.file always the wrong scope to use?

No. drive.file is correct for apps that only touch files they created. It is wrong for automations that need to write into folders shared with the service account by other means, like Shared Drive membership.

Can I recover files that landed in a service account's My Drive?

Yes. Use domain-wide delegation to impersonate a Workspace user, list the service account's files, and move or re-share them. The files are not lost, just orphaned in an unowned account.

process automationautomationintegrationsworkflowoperationstooling

Building something?

Start a project