Sujal (@hecodesforme) header
Sujal (@hecodesforme) profile picture

just an AGI looking for work

Delhi, India
Joined September 2020
Photo by Sujal (@hecodesforme)Photo by Sujal (@hecodesforme)Photo by Sujal (@hecodesforme)Photo by Sujal (@hecodesforme)Photo by Sujal (@hecodesforme)Photo by Sujal (@hecodesforme)Photo by Sujal (@hecodesforme)Photo by Sujal (@hecodesforme)Photo by Sujal (@hecodesforme)Photo by Sujal (@hecodesforme)
Replying to @TTrimoreau
none of them honestly. i'd rather keep shipping for five clients than bet a year on one idea i didn't pick myself.
1
1
35
5,631
the Cancel button on a settings form was saving the form 17 support tickets in one week. "I hit cancel and my changes went through anyway" the button had an onClick that closed the modal. it had no type attribute a `<button>` inside a `<form>` is type="submit" by default. so every cancel closed the modal and submitted the form on the way out the fix was one attribute. type="button" HTML has a default for everything. it just doesn't tell you which ones are going to hurt
2
1
2
100
a headless Chrome on a $6 VPS was taking screenshots for a side project it got started with `--remote-debugging-address=0.0.0.0` so someone could poke at it from their laptop the DevTools protocol has no login. not a weak one, none at all. Chromium's own flag docs say so anyone who finds port 9222 can open a tab, read every cookie and run JS as that browser it sat open for 11 days bind it to 127.0.0.1 and reach it over an SSH tunnel. an open debug port is a logged-in browser with the front door taken off
3
1
84
a deploy failed at 11pm on a Friday and the diff was one line of CSS the build log said a dependency wouldn't compile. it compiled fine 3 days earlier the Dockerfile started with `FROM node:latest`. a new major Node version had come out that week, and latest moved to it without asking anyone same code, different runtime, 40 minutes of my Friday pinned the exact version and the build went green on the first try "latest" isn't a version. it's a surprise you scheduled for later
6
4
139
support ticket: "it logs me out every week, but only on my iPhone" Android was fine. desktop was fine. same app, same code the login token lived in localStorage. Safari deletes everything a script can write, localStorage and IndexedDB included, after 7 days of browsing without you touching that site so anyone who opened the app twice a month got a login screen each time moved the session into an httpOnly cookie set by the server, which that rule doesn't touch "works on my phone" depends on how often you open the app
2
1
3
117
changed an API URL in the hosting dashboard at 6pm and the frontend kept calling the old one restarted it twice. purged the CDN. still the old URL the variable started with `VITE_`. those aren't read when the app runs. the build pastes the value into the JS as a plain string, so the old URL was sitting inside a 300kb bundle the whole time the fix was a rebuild. 40 seconds same rule means anything you put behind that prefix ships to every visitor, API keys included if the browser can read it, it was decided at build time
32
a side project's Postgres started refusing connections at 9:15pm on a Tuesday "too many clients already" traffic was normal. maybe 40 requests a minute the app runs on serverless functions, and each instance opened its own pool of 10. 12 warm instances is 120 connections, and Postgres allows 100 by default nothing was leaking. it just scaled out the fix was PgBouncer in transaction mode in front, and a pool of 1 per function serverless doesn't remove the servers. it just stops telling you how many there are
2
5
80
a user deleted their account, then tried to sign up again with the same email 2 days later "email already taken" the account was soft deleted. `deleted_at` was set, the row was still there, and the unique index on email was still counting it the fix is a partial index: `CREATE UNIQUE INDEX ON users (email) WHERE deleted_at IS NULL` 4 minutes to write. 40 minutes to work out why 11 people had been stuck soft delete means your constraints need to know what "deleted" means too
1
3
65
a checkout page went down for 20 minutes and nothing in my code had changed a shipping-rate API it calls got slow. not down, just slow. 40 seconds a request Node's built-in fetch has no overall timeout. it'll wait up to 5 minutes for the headers before it gives up so every checkout sat there holding a connection open. 300 of them by 6:10pm, and the server stopped answering anyone the fix was one line: `signal: AbortSignal.timeout(5000)` slow is worse than down. down at least fails fast
1
3
125
`Cache-Control: no-cache` does not mean "don't cache" it means "store it, but check with the server before using it". the browser keeps the response and asks each time, and gets a 304 back if nothing changed the one that actually means don't store it is `no-store` found this out after 40 minutes of wondering why a "no-cache" API response was sitting in the disk cache for user data you probably want `no-store`. for your JS bundle you want the opposite: a hashed filename and `max-age=31536000, immutable` the names are just bad, that's the whole lesson
1
1
125
page 1 of the orders list loads in 20ms. page 5,000 takes 3 seconds same query, same index. the only difference is `OFFSET 100000` offset doesn't skip rows, it reads them and throws them away. to show you rows 100,001 to 100,020 the database walks through the first 100,000 first the fix is keyset pagination: remember the last id you showed and ask for `WHERE id > $last ORDER BY id LIMIT 20` page 5,000 goes back to 20ms you lose "jump to page 4,812", which almost no one was clicking anyway
2
1
1
119
about to move a side project's background jobs off a managed queue and onto a plain Postgres table $29 a month for the queue. the database is already there, mostly idle the plan: a `jobs` table `SKIP LOCKED` so two workers never grab the same row a lease column so a dead worker's job comes back after 5 minutes a nightly cron that deletes finished rows about 60 lines of code to save $348 a year the queue never once paged me, which is the part I'm choosing to ignore tonight
1
2
93
an admin page took 4 seconds to load and the database said it was barely working turned on query logging. 301 queries for one page 1 to fetch 300 orders, then 1 more per order for the customer name. the ORM made it look like a property, `order.customer.name`, so it never looked like a query in the template the fix was loading the customers up front in one go. 2 queries total, page went to 90ms the database was never slow. it was answering 300 tiny questions in a row, each with its own round trip ORMs don't hide the queries, they just make them look free
1
72
"store everything in UTC" holds up great until someone books something in the future a weekly 9:00 standup in New York, saved in July as 13:00 UTC on 1 November the clocks go back. 13:00 UTC is now 8:00 in New York, and the whole team shows up an hour early for the rest of winter for past events UTC is perfect, they already happened. for future ones, store the local time plus the zone name like `America/New_York`, and work out the UTC moment close to the day countries change their DST rules, sometimes with a few weeks' notice. the zone name survives that, a frozen UTC timestamp doesn't
3
2
51
"log out" with a JWT doesn't log anyone out the token is just signed data. the server checks the signature and the expiry, it doesn't look anything up. so when someone hits logout, the app deletes its own copy and that's it anyone holding another copy can keep using it until it expires. with a 30 day expiry, that's 30 days the usual fix is boring: access tokens that live 15 minutes, a refresh token stored in your database, and logout deletes the refresh token if you need "kill this session right now", you need state somewhere. the token can't do it for you
2
1
60
login was taking 1.4 seconds and the users table already had an index on email the query was `WHERE lower(email) = lower($1)` Postgres can't use a plain email index for that. the index stores email, the query asks for lower(email), and to the planner those are two different things. so it read all 800,000 rows, on each login the fix was one line: `CREATE INDEX ON users (lower(email))` login went to 3ms if your WHERE wraps a column in a function, the index has to be built on that same expression or it's just sitting there
1
2
192
never store money as a float 0.1 + 0.2 in JavaScript is 0.30000000000000004. spread that across 40,000 invoices and the monthly total stops matching the bank by a few cents store integer cents (or paise), do every calculation in integers, format it only when a human looks at it and keep the currency right next to the amount. 1000 is $10 in cents but ¥1000 in yen, because yen has no minor unit the accountant will find this before your tests do
1
3
131
"works in Postman, fails in the browser" is almost always CORS, and the server usually isn't broken CORS is enforced by the browser, not the server. on a simple GET the server answers like normal, then the browser checks for Access-Control-Allow-Origin and refuses to hand the response to your JavaScript if it's missing curl and Postman never check, so they always "work" it also means CORS doesn't protect your API from anyone with a terminal. it only decides what other websites can read took me 2 hours to learn that the first time
1
56
SELECT COUNT(*) on a big Postgres table is slow, and that's not a bug there's no row count stored anywhere. two transactions can see two different counts at the same moment, so Postgres walks the table (or an index) and checks which rows are visible to you on 40 million rows that's seconds, not milliseconds if the dashboard only needs "about 40M", read reltuples from pg_class. it's an estimate that VACUUM and ANALYZE keep fresh, and it comes back instantly exact counts are expensive. most screens never needed one
3
3
83
your authenticator app has no idea the website exists when you scanned that QR code, both sides saved the same secret key. that was the only conversation they ever had after that, each 30 seconds, your phone and the server run the same math on that secret plus the current time and keep 6 digits of the result same secret, same clock, same 6 digits. nothing travels over the network it's also why a phone with the wrong time keeps getting "invalid code". most servers only forgive about one 30 second step of drift
3
1
123
webhooks don't arrive in the order things happened, and you usually learn that from a support ticket a customer upgrades at 10:41 and cancels 7 seconds later your server gets "cancelled" first, then "upgraded" if the handler just applies each payload as it lands, that customer is now paying for a plan they cancelled sorting by the event timestamp doesn't save you either, two events can share the same second the boring fix: treat the webhook as a nudge, fetch the current state from the API, save that
1
58