nogamsung
Where does next start read next.config from? Three run-ins with a Docker runtime image

Where does next start read next.config from? Three run-ins with a Docker runtime image

A Next.js runtime image that copied only .next from the build stage tripped us up three times in two months: images.remotePatterns ignored so images returned 400, basePath gone so every route returned 404, and after switching to a non-root user, corepack hanging while trying to re-download pnpm and a logger's mkdir failing with EACCES so every BFF returned an HTML 500. I checked the Next source for which settings are baked into the build output and which ones next start reads at runtime, and wrote down a checklist for non-root containers.

AI in the product — why I pulled the chat assistant after 5 days, and brought it back 4 months later as a document judge

AI in the product — why I pulled the chat assistant after 5 days, and brought it back 4 months later as a document judge

In May I added a Claude chat widget to our ERP and ripped the whole thing out five days later. In October I brought the same Claude API back as a "document judge" inside a batch job. What changed wasn't the model but the problem definition, the unit of cost and the failure handling. Here's how, after measuring $0.23 per document and $37 a day, I put a judgment model 100x cheaper in front as a gate and redesigned for a $10/day target — with the numbers.

Moving six subdomains under one host's basePath: four zero-downtime stages, and where Next doesn't add the prefix

Moving six subdomains under one host's basePath: four zero-downtime stages, and where Next doesn't add the prefix

We moved six Next.js apps, each on its own subdomain, under path prefixes on the portal host (example.com/svc). The four-stage order that meant app deploys and proxy changes never had to land at the same moment, a global patch for the places Next doesn't add basePath (fetch, string src, window.location), a "404 outside the prefix" proxy to find misses, why cookies became host-only, and four service workers deleting each other's FCM tokens.

A LiveKit call-ending race: when a REST leave and a webhook change the same state

A LiveKit call-ending race: when a REST leave and a webhook change the same state

Three problems from the first three days after adding LiveKit SFU voice calls to our internal messenger: an auth filter that checked LiveKit-signed JWTs as user JWTs and returned 401 on every webhook, a race between a synchronous REST leave and an asynchronous webhook updating the same call state (fixed with idempotent settlement), and per-user STOMP topics that couldn't stop the ringing on the responder's other devices.

Why the unread badge was wrong three times: from a client-side counter to a server count

Why the unread badge was wrong three times: from a client-side counter to a server count

An unread badge built as "+1 per received frame" went wrong three times in six weeks: a design limit that reset it to 0 on refresh, DMs counted twice because the server fanned them out to two topics, and one email showing up as a badge of 4. I suspected StrictMode, cleared it by reading the source, found zombie sockets, and ended up with "the server counts, events only invalidate".

Why WebSockets quietly die in background tabs: four causes behind the same banner

Why WebSockets quietly die in background tabs: four causes behind the same banner

Our web messenger's "Disconnected. Reconnecting..." banner showed up for four different reasons over six weeks: deactivate() re-triggering the reconnect, a Spring SimpleBroker that negotiated heartbeats down to 0,0, timer throttling in hidden tabs, and a first-entry race where the direct WebSocket beat the BFF-routed REST call and its state was lost. A walk through the stompjs and Spring source.

A subagent merged a PR under my name — permissions you shouldn't give an agent

A subagent merged a PR under my name — permissions you shouldn't give an agent

I handed a subagent a mechanical edit across dozens of files, and it went on to push, open a PR and merge it into a shared branch seven seconds later — under my account. Twelve tests were broken and dev was red for 50 minutes. I walk back through why that was possible given the harness at the time (inherited tools, an exit 1 hook, an unprotected branch), the rule I set that day, and the fact that the rule wasn't enforced.

Teaching the repo to agents — CLAUDE.md, memory/, hooks and skills

Teaching the repo to agents — CLAUDE.md, memory/, hooks and skills

The day I merged our backend and frontend repositories, the two harnesses they each carried collided. The .claude/ and .github/ folders in the subdirectories were both dead config, and the types on each side had already drifted apart. Instructions went into three layers of CLAUDE.md, configuration into a single stacks.json, enforcement into hooks and CI, memory into memory/, and the API contract into a single OpenAPI source. Here's how I split what the agent reads from what stops it.