close

DEV Community

Cover image for At Last, I clasp: Escaping the G's Apps Script Copy-Paste Gauntlet
Athreya aka Maneshwar
Athreya aka Maneshwar

Posted on

At Last, I clasp: Escaping the G's Apps Script Copy-Paste Gauntlet

Permission fixes and secure script workflows

Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. Star git-lrc to help devs discover the project. Do give it a try and share your feedback.


Hey. Come sit down. I need to tell you about the dumbest few hours of debugging I've had this year, and how it ended with me weirdly fond of a command line tool called clasp.

The setup: why does a LinkedIn extension need a backend

Here's the use case. I built myself a Chrome extension called ProfileKit.

It sits on LinkedIn profile pages, scrapes the useful bits (name, headline, current company). Nice, simple, personal tooling.

Then I wanted one more feature: a "Save Profile" button.

Click it on a profile, and that person gets attached to their company's row in a big Google Sheet I use to track.

Multiple profiles per company, no duplicates, all synced automatically.

Cool idea. Except now my little scraper needs a backend.

I don't want to stand up a server for a side project that runs maybe twenty times a day.

Shrijith gave me the idea of turning a Google Apps Script Web App into a free API, with a Google Sheet playing the role of the database

The wiring looks like this from the extension side:

// background.js
fetch(SHEET_SYNC_URL, {
  method: "POST",
  body: JSON.stringify(message.payload),
  redirect: "follow",
})
  .then((res) => res.text())
  .then((text) => sendResponse({ ok: true, data: JSON.parse(text) }))
  .catch((err) => sendResponse({ ok: false, error: err.message }));
Enter fullscreen mode Exit fullscreen mode

And on the other end, an Apps Script doPost matches the lead to a company row and appends it.

Free hosting, free auth (in theory), free database.

What could go wrong.

Here's roughly what that whole handshake looks like when it's working, redirect and all:

That redirect-to-an-echo-URL dance is a real thing Apps Script does for every response.

First time I saw it in the network tab I assumed something was broken.

It was not. It was just Google being Google.

The bug that made me question reality

Small bug first, for context: my original "Save Lead" logic matched leads to companies by name.

Text like "EmpInfo" versus "EmpInfo, Inc." versus whatever a headline happened to say.

Ambiguous, fragile, occasionally hilarious.

I fixed that by capturing the company's actual LinkedIn URL the moment you click "Search Relevant people" on a company page, and using that URL as the real key instead of vibes-based string matching. Good, solid, boring fix.

Then I redeployed the updated Apps Script code and everything broke.

Every single sync attempt failed with a 401.

Not "your code has a bug."

Not "column not found."

A flat, personality-free 401, straight from Google's infrastructure, before my code even ran.

I checked the deployment settings about four times. Right there, big as day: Who has access: Anyone.

Anyone. It said Anyone. I stared at that word like it owed me money.

Turns out they are not the same picture.

Under the hood, in the actual manifest that controls the deployment, the setting was:

"webapp": {
  "executeAs": "USER_DEPLOYING",
  "access": "ANYONE"
}
Enter fullscreen mode Exit fullscreen mode

And in Google's API, ANYONE quietly means "anyone who is logged into a Google account." Not anonymous. Not a curl request. Not my extension's fetch() call, which carries zero Google session cookies because why would it.

The setting that actually means "the public, no login, total strangers, machines, whatever" is a completely different value: ANYONE_ANONYMOUS.

So the UI says "Anyone," the API has two different flavors of anyone, and only one of them is actually anyone.

It's the access-control equivalent of a landlord's listing saying "pet friendly" and then you find out that means one goldfish, supervised.

Fixed it, and confirmed with a plain request:

curl -s "$DEPLOY_URL" -w "\n%{http_code}\n"
# {"ok":false,"error":"Use POST"}
# 200
Enter fullscreen mode Exit fullscreen mode
  1. A real response. From a stranger. Anonymously. As advertised, eventually.

Enter clasp, stage left

Here's the part that actually made this worth writing about.

Every time I fixed something in the Apps Script file, the workflow to ship it was: open the web editor, paste the code in by hand, click Deploy, click Manage Deployments, cut a new version, copy the new /exec URL, go paste that URL into my extension's background.js, bump the manifest version, reload the extension, refresh every open LinkedIn tab.

For one changed line of code.

I did this enough times that I finally asked out loud: isn't there a package for this.

Turns out yes.

It's called clasp, Google's own CLI for Apps Script, and it lets you push code, cut versions, and manage deployments straight from your terminal.

npm install -g @google/clasp
clasp login          # one-time OAuth in your browser
clasp clone <scriptId>
Enter fullscreen mode Exit fullscreen mode

The part that actually solved my URL-churn problem is this flag:

clasp deploy -i <existingDeploymentId> -V <newVersionNumber>
Enter fullscreen mode Exit fullscreen mode

That updates an existing deployment to point at a new version, in place.

Same deployment ID, same /exec URL, forever.

No more copy-pasting a new URL into the extension every single time I fix a typo.

Automating the whole dance

So I wrapped push, version, and redeploy into one script and one make target:

make apps-script-deploy MSG="fix lead matching"
Enter fullscreen mode Exit fullscreen mode

That's it. That's the whole release process now.

Here's the before and after, roughly:

Why this pattern is underrated

Chrome extension talking to a Google Sheet through an Apps Script Web App is not a serious architecture.

I want to be clear about that.

It is also completely free, needs no server, no database, no auth system, and took me an evening to wire up.

For a personal tool that a handful of people (one person, me) use a few dozen times a day, that trade is fantastic.

Just know the one gotcha buried in the manifest, and maybe keep clasp installed before you need it instead of after three hours of 401s.

A quick shoutout

clasp was built by Grant Timmerman, who spent his time at Google Workspace basically inventing the developer story for Apps Script from scratch, this CLI included.

It still quietly handles a huge amount of traffic for people building Sheets and Docs add-ons, years later.

If you ever wire an extension or a script up to Google Workspace, you've probably benefited from his work without knowing it.

Tagging him properly when this goes live, he deserves the credit :)

Further reading:

Disclaimer: This article was written by me; AI was used to fix grammar and improve readability.


AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.

git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.

Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.

⭐ Star it on GitHub:

GitHub logo HexmosTech / git-lrc

Free, Micro AI Code Reviews That Run on Git Commit




GenAI today is a race car without brakes. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents silently break things: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.

git-lrc is your braking system. It hooks into git commit and runs an AI review on every diff before it lands. 60-second setup. Completely free.

In short, git-lrc helps Prevent Outages, Breaches, and Technical Debt Before They Happen

At a glance: 10 risk categories · 100+ failure patterns tracked · every commit…

Top comments (14)

Collapse
 
hemapriya_kanagala profile image
Hemapriya Kanagala

Really enjoyed this 😄
I had no idea ANYONE and ANYONE_ANONYMOUS behaved differently in Apps Script. That kind of tiny detail can easily cost hours of debugging. Also, clasp looks like a huge quality of life improvement. Thanks for sharing this. I'll definitely keep it in mind if I end up building something similar.

Collapse
 
lovestaco profile image
Athreya aka Maneshwar

Haha thanks Hema, I'm happy you liked it :)

Collapse
 
itskondrat profile image
Mykola Kondratiuk

yeah, the Apps Script copy-paste loop silently steals afternoons. clasp + a tsconfig sorted this for me too. curious what you do for deployment - push straight to prod or stage it first?

Collapse
 
lovestaco profile image
Athreya aka Maneshwar

Internal tool go directly to prod xD

Collapse
 
itskondrat profile image
Mykola Kondratiuk

haha fair, direct to prod is its own kind of discipline. what's the worst incident so far

Collapse
 
nazar-boyko profile image
Nazar Boyko

Heads up on the flip side of that fix. Once the deployment is ANYONE_ANONYMOUS, anyone who finds the URL can POST junk straight into your sheet, and some bored bot eventually will. A shared secret in the payload that doPost checks before writing costs a couple of lines and closes most of that hole. The clasp deploy -i trick was the part I didn't know, keeping one stable /exec URL kills the whole ritual of pasting a new URL into the extension. And the UI saying "Anyone" while the manifest means "anyone logged into Google" probably explains a lot of mystery 401 threads out there.

Collapse
 
lovestaco profile image
Athreya aka Maneshwar

Yo

Collapse
 
willy_james_45b9223ca9d6b profile image
Willy James

HIRE A BEST CRYPTO HACKER RECOVERY CRYPTOCURRENCY / BANK RECOVERY EXPERT MARK WIZARD HACKING RECOVERY
My name is Willy and i am here to share my experience about recovery expert who help me in getting back my funds without any issues.Back in 2025, I got scammed out of $840,000 by a fella on Instagram, I still have our entire conversation, and even took screenshots of the websites he gave me to send my Crypto to, and Coinbase still has the records of the transactions where I sent it over.
When I saw someone on Instagram, a few days ago, bring up that he'd been scammed out of over $8840,000K USD in Crypto, I brought up that I had too, and I saw people responding to my post, recommending I talk to certain people about recovery. If you recently lost a portion of your savings or retirement nest egg to a cryptocurrency, forex, or binary options scam, Bank account, Instagram, X twitter, Tiktok, FB and other platforms, Wizard Mark Hackers Recovery retrieve my funds worth $840,000 BTC from fake Binance. I am so glad right now. all thanks to Wizard Mark Hackers Recovery and the great help that was render to me. i am also reaching out to your who needs help. contact through Wizard Mark Hackers Recovery with their AI Expertise.
Email: :Markwizard23 @ gmail.com
WHATSAPP +4915218465599
TELEGRAM : @ wizardhackersrecovery
WEBSITE: https:// wizardmarkhackersrecovery.mystrikingly. com

Collapse
 
alexshev profile image
Alex Shev

Clasp is a good example of tooling turning a fragile ritual into a workflow. The win is not only fewer copy-paste mistakes, but having version history and review around scripts that affect real users.

Collapse
 
lovestaco profile image
Athreya aka Maneshwar

Yes sir

Collapse
 
vic_xie_9bed0062d5fd73d12 profile image
vic xie

TextStow could be useful for this workflow — clipboard history + reusable favorites + prompt templates + cleanup for JSON/PDF/URLs. Local-first, free: textstow.com

Collapse
 
lovestaco profile image
Athreya aka Maneshwar

I see

Collapse
 
grant profile image
Grant Timmerman

Nice use case for clasp! Glad those -i -V flags came clutch here.

Collapse
 
lovestaco profile image
Athreya aka Maneshwar

Yess