9 Ways Your AI Agent Silently Fails (and How to Catch Each)
Your agent passed its tests. It ran clean in the demo. You shipped it. Two days later it's...
找到 2656 篇相关文章
Your agent passed its tests. It ran clean in the demo. You shipped it. Two days later it's...
Hello, I'm Shrijith Venkatramana, and I'm building LiveReview — a blast-radius aware AI code review built for your business-critical systems. Star us to help devs discover the project, give it a try, and share your feedback to help improve the product. Most people learn neural networks by staring at the model. Weights. Attention. MLPs. LayerNorm. Tokenizers. Context windows. But when you actually train an LLM, there is another piece of machinery making billions of decisions every second: the optimizer. A 70-billion-parameter model does not "learn" because gradient descent tells it which direction is better. It learns because an optimizer turns an enormous, noisy stream of gradients into parameter updates that are small enough not to explode, large enough to make progress, and adaptive enough that different parameters can move at radically different effective rates. For the last decade, the dominant answer has largely been some form of Adam , and increasingly AdamW . The interesting part is that Adam is not some mysterious LLM-specific invention. The original Adam paper was submitted in December 2014 by Diederik Kingma and Jimmy Ba, before the Transformer, before GPT, and before the modern LLM era. Kingma was working on scalable machine learning and generative models; Ba was then a PhD student working with Geoffrey Hinton at Toronto. Three years later, the Transformer paper used Adam directly in its training recipe. Then came AdamW, which fixed a subtle but important problem in how regularization interacted with adaptive optimization. By 2025, Adam was sufficiently influential to receive an ICLR Test of Time award. So what exactly is Adam doing? And why is AdamW usually what you actually want when training a Transformer? 1. First, forget Adam: what problem is the optimizer solving? Suppose your neural network has parameters theta = [theta_1, theta_2, ..., theta_N] and your training batch produces a loss L . Backpropagation gives you g = dL/dtheta The simplest possibl
1. Problem Statement I wanted to contribute to an open source project called WikiEduDashboard , a web application built by Wiki Education. It helps instructors and program leaders run Wikipedia-editing classes and campaigns: students join a course, make edits to Wikipedia, and the dashboard tracks their work. To contribute code to this project, I first need a working copy of it running on my own PC. This is called a "local development environment." Without it, I can't test any changes I make before sending them back to the project. The problem: this project was built with tools that work best on Mac or Linux, not on plain Windows. So the first challenge wasn't even the project itself, it was figuring out how to run a Linux-friendly project on a Windows PC. 2. The Solution (High Level) Instead of fighting Windows directly, we used a feature built into Windows called WSL (Windows Subsystem for Linux) . WSL lets a real Linux system (Ubuntu, in our case) run inside Windows, side by side with your normal Windows apps. It's not a separate computer or a virtual machine you have to babysit, it just works like an extra terminal environment on the same PC. Once inside Ubuntu, we could follow the project's official setup instructions exactly as written, since those instructions assume a Mac or Linux machine. The overall plan looked like this: Get a Linux environment running on Windows (WSL + Ubuntu) Get a personal copy of the project's code (fork it on GitHub, then clone it) Install the programming language the project is built with (Ruby) Run the project's automated setup script, which installs the rest of the required tools (database, background job system, etc.) Start the actual application and view it in a browser Build the frontend (the visual, interactive part of the site) Set up an editor (VS Code) that can actually see and edit the code living inside Ubuntu 3. Step by Step: What We Did and Why Step 1: Install WSL and Ubuntu What: WSL is a Windows feature that runs a re
Live API specs for coding agents An agent writing frontend code has to know the backend's API. It has three options. It can read the backend source and work out from scratch what the service already publishes. It can ask you, which promotes you to API documentation. Or it can swallow the entire OpenAPI document in order to use one route out of it. Then it does the same thing again tomorrow, against a stale swagger.json you exported last week. docs-mcpserver takes the spec straight from the running service, caches it, and serves it one operation at a time. The config { "cacheDir" : "./cache" , "libraries" : [ { "name" : "orders-api" , "description" : "Order handling service" , "sources" : [ { "type" : "url" , "origin" : "https://localhost:5001/openapi/v1.json" , "kind" : "schema" , "name" : "orders" } ] } ] } npm install -g docs-mcpserver claude mcp add docs -- docs-mcpserver --config /path/to/dev-docs.json That is the whole setup. One operation, not the whole spec The agent lists the definitions in orders , picks the one it needs, and fetches that. For an OpenAPI document the path operations are exposed as definitions named GET /orders/{id} , so it can also search by keyword. A few hundred tokens for the operation it is writing against, instead of the entire document. That keeps working as the service grows, which a pasted spec does not. The backend does not have to be running Every call is answered from the cached spec, never from the network. The fetch happens on startup and then in the background while you work, so an endpoint you added 20 seconds ago is already visible. Start the backend once, shut it down, and keep building the frontend. The agent still has real routes and real payload shapes. If the service is down, or answers with something that is not a spec, the last known-good copy keeps being served. Code and issues: github.com/jgauffin/dev-docs-mcp . On npm as docs-mcpserver .
Old Light is a browser strategy game where a tab can stay open for days. The client holds a full copy of the galaxy state it is allowed to see, and the server keeps that copy honest by sending patches: every change arrives as a world.delta message the client merges into what it already has. Sending changes instead of resending state is textbook delta encoding. What that leaves open is what a game state patch actually holds, and why the patch a rival receives is not the one you receive. I covered how the stream starts (one snapshot on connect, then deltas) and the time-math traps inside it in the networking post . This post is about the delta itself. What goes in a game state patch When people say delta encoding they usually mean byte diffs: compare two versions of a blob, ship the difference. That requires the sender to know which version the receiver holds. A game server broadcasting to thousands of sockets can't afford that; tracking a per-client "last known state" and diffing against it on every change would be more expensive than the update. So an Old Light delta states facts about players and sectors instead: interface WorldDelta { added ?: { players ?: Player [] }; removed ?: { playerIds ?: string [] }; updated ?: { players ?: Player []; sectors ?: Sector []; dirtySectors ?: SectorCoord []; // map data here went stale, refetch it tradeBoard ?: TradeBoardDelta ; // the market board moved deals ?: DealsDelta ; // a negotiation moved; only its two parties get this }; serverNow : number ; } A delta says a player joined, an id is gone, a player's row changed, or a sector's public map data went stale. The last two fields carry no payload. They say a surface moved, a client with that surface open goes and reads it, which keeps a busy marketplace off every socket that isn't looking at one. The server can emit the identical message to every socket without knowing what any of them currently holds, and the client can apply it to whatever it has. It also tells the rendere
Been looking for a simple, offline ready web application to save things I want to read after Pocket shut down. Couldnt find anything that I liked so created one - hopefully others might like. monkeydust / rightread Read-later: capture links from anywhere, read them clean and offline rightread Capture links from anywhere. Read them clean, later, offline. Paste a link. It gets extracted and it's ready to read, clean and offline. Save a link from your phone's share sheet or your browser toolbar. rightread strips the page down to the article, with no ads, no cookie banners and no newsletter popups, and keeps it readable offline in typography built for long reading. Why this exists On 22 May 2025, Mozilla announced it was winding Pocket down . I'd used it for years for one thing: saving something on my phone and reading it properly later, usually when I was on the tube. The alternatives were mostly 'meh' so I built the small thing I missed. One queue, clean text, works on a plane, running on a server I control with the whole library in a single SQLite file I can copy. The reading list lives on your… View on GitHub
I’m looking for a little community help validating a new SharePoint Framework sample: React Flex Forms . What does it do? The sample contains two SPFx web parts: Form Designer — creates and manages one-page form definitions using supported SharePoint field types. Form Renderer — loads a published form, validates responses, and saves submissions to a SharePoint list. The sample includes automated tests and the local lint, build, and packaging checks are passing. The remaining gap is real-tenant validation and the static screenshots required for the PnP sample README. Could you help? If you have access to a SharePoint Online tenant and a few minutes to spare, please try the sample and capture: The Form Designer working in the SharePoint-hosted workbench. The Form Renderer displaying and submitting a published form. Dummy data is completely fine. Please remove or blur tenant, site, list, user, and other sensitive details before sharing. Feedback about provisioning, permissions, validation, submission, keyboard use, responsive behavior, dark theme, or high-contrast mode would also be very valuable. You can attach the scrubbed screenshots and observations directly to PR #6473 . The setup instructions are included in the sample README. This is a small request, but it would significantly speed up the final validation and help move the contribution toward completion. Thank you to anyone who can lend a tenant or share feedback. sharepoint #spfx #opensource #webdev
Thin vs Thick Provisioning: Which One Is Actually Eating Your Datastore? You just got an alert: your datastore is at 92% capacity. But when you check the actual VMs, they're barely using half the storage you allocated to them. Welcome to the most common source of confusion in virtualization storage — the gap between allocated and used . This comes down to how you provisioned your virtual disks in the first place. Thin Provisioning: Pay As You Go With thin provisioning, a 100 GB virtual disk doesn't actually consume 100 GB on your datastore right away. It grows as data is written to it. Create ten VMs with 100 GB thin disks, and if they're only using 20 GB each, your datastore shows 200 GB used — not 1 TB. This is why thin provisioning is the default choice for most environments today. It lets you overcommit storage and squeeze more VMs onto the same physical hardware. The catch: you must monitor actual datastore consumption, not just allocated capacity. If every VM suddenly starts writing more data than expected, you can run out of physical space even though your dashboards showed "plenty of room" based on allocated sizes. Thick Provisioning: Reserve It All Up Front Thick provisioning reserves the full disk size the moment you create it. There are two flavors: Lazy-zeroed : space is reserved, but blocks are only zeroed out the first time the VM writes to them. Faster to create, slightly slower on first write. Eager-zeroed : every block is zeroed at creation time. Slower to provision (a 500 GB disk can take a while), but delivers the most predictable, consistent I/O performance from the very first write. Which One Should You Actually Use? A simple rule of thumb: default to thin provisioning for general-purpose VMs — web servers, file servers, domain controllers, dev/test environments. Switch to eager-zeroed thick provisioning specifically for workloads where I/O consistency matters more than storage efficiency — databases, latency-sensitive applications, anything whe
You type "Hi" and the computer stores 01001000 01101001 . Text is just numbers wearing a costume. Here is exactly how a string turns into binary, why UTF-8 matters, and how to do the conversion both ways in a few lines of JavaScript. What "binary" actually means here Computers do not store letters. They store numbers, and every number is a run of ones and zeros. Each character maps to a code point, that number becomes a byte, and each byte is written as eight bits . The letter A has the ASCII code 65. In binary that is: 65 = 01000001 Lowercase a is 97, which is 01100001 . So the whole word "Hi" ( H = 72, i = 105) becomes: 01001000 01101001 Group the bits into bytes of 8 and you can read any binary string back into text. Text to binary in JavaScript The reliable way is TextEncoder . It hands you the raw UTF-8 bytes, so you do not have to worry about character codes above 127. function textToBinary ( text ) { const bytes = new TextEncoder (). encode ( text ); return Array . from ( bytes ) . map ( b => b . toString ( 2 ). padStart ( 8 , " 0 " )) . join ( " " ); } textToBinary ( " Hi " ); // "01001000 01101001" toString(2) gives the binary digits, and padStart(8, "0") keeps every byte a full 8 bits. Without the pad, H would come out as 1001000 (7 bits) and the string would be impossible to split back cleanly. Binary back to text Reverse the process: strip spaces, cut the string into 8-bit chunks, parse each chunk as a base-2 number, then decode the bytes with TextDecoder . function binaryToText ( bin ) { const bits = bin . replace ( / \s +/g , "" ); const bytes = new Uint8Array ( bits . length / 8 ); for ( let i = 0 ; i < bytes . length ; i ++ ) { bytes [ i ] = parseInt ( bits . slice ( i * 8 , i * 8 + 8 ), 2 ); } return new TextDecoder ( " utf-8 " ). decode ( bytes ); } binaryToText ( " 01001000 01101001 " ); // "Hi" Two checks worth adding in real code: reject anything that is not 0 or 1 , and reject a bit count that is not a multiple of 8. Those two guards catch almo
An extension can work perfectly in development and still fail after packaging. The risky change is often not in the feature code itself. It can be a permission that moved, a host pattern that expanded, a content script that now runs somewhere new, or a browser surface that was never included in the release checklist. Here is the small preflight review I now use before testing an MV3 release. 1. Compare the packaged manifests Compare the last version you actually shipped with the new packaged version, not only the source manifest. Check separately: required permissions; optional permissions; required host access; optional host access. A permission moving from optional to required deserves attention even if the set of permission names looks familiar. 2. List every browser surface Turn the manifest into a list of things a person can interact with or that Chrome can start: action popup; options page; side panel; background service worker; content scripts; commands; externally connectable pages; declarative network rules; web-accessible resources. If a surface changed, add at least one release check for it. This sounds obvious, but it is easy to review the main popup while forgetting an options page or a host-specific content script. 3. Check where code can now run For every content script, compare: match patterns; excluded matches; frames; execution world; run timing. The JavaScript file can be unchanged while one of these settings changes the extension's behavior on real sites. 4. Test the packaged build Run the checklist against the same build directory that will be uploaded. A development build can hide packaging, path, minification, or generated-manifest differences. At minimum, reload the packaged extension and exercise one path through each changed surface. 5. Record why each check exists Instead of keeping a generic list such as “test the popup,†connect each check to a release change: host access expanded → test the new host and confirm the old hosts still
When an internet outage hits, users immediately turn to their phones to find out if it's just them or a widespread network issue. Because they are often relying on spotty cellular data, any tracking site needs to load instantly and deliver highly localized information. I recently launched CoxOutage.us to map and track Cox Communications disruptions. Here is a breakdown of the technical and SEO strategies I used to build it. Performance & Traffic Handling Outage trackers face a unique challenge: they get zero traffic when things are fine, and massive, sudden spikes the minute a service goes down. Aggressive Caching: I implemented LiteSpeed Cache combined with Memcached for object caching. This ensures that database queries are kept to an absolute minimum when a sudden wave of users hits the site. Edge Delivery: Everything sits behind Cloudflare for DNS management and edge-level caching, ensuring the server (hosted via InterServer) doesn't get overwhelmed during regional outages. Scalable SEO & Routing Architecture The biggest hurdle was capturing local search intent accurately. Hyper-Specific URL Slugs: Initially, you might think to use a simple routing structure like /los-angeles . However, I found that using full keyword slugs—such as /cox-outage-los-angeles —significantly boosted visibility and search performance. Automated Indexing & Schema: I utilized the Google Indexing API to push new city landing pages instantly. Paired with Rank Math, the site generates precise schema markup so search engines understand the real-time nature of the status updates. Looking Forward Right now, the focus is on scaling out the localized landing pages and refining the automated reporting pipeline. If you have experience building high-traffic, real-time alert systems or handling sudden traffic spikes, I’d love to hear your approach. Check out the live project here: CoxOutage.us Feedback and suggestions are always welcome!
When applying for engineering roles, automated applicant tracking systems (ATS) often silently reject candidates due to parsing blockers like multi-column layouts, missing quantitative metrics, or non-standard font embeddings. To fix this latency bottleneck, I built MyRizzume ( https://myrizzume.me ) — designed to parse and score resumes end-to-end in under 1,000ms. What it checks: Layout Integrity: Validates that column and table layouts won't merge or scramble text during ATS ingestion. Action Verb Strength: Highlights passive phrases and suggests active, quantifiable replacements. Keyword Density: Compares section headers and skill blocks against common parser taxonomies. Try it out live at https://myrizzume.me and let me know how it handles your layout!
An early version of Say It Ahead had a basic problem. A user could listen carefully, ask good questions, and offer a reasonable plan, but the AI character might still sound just as upset as it did at the start. That made the practice feel arbitrary. The user could not tell whether anything they said had changed the conversation. The character had a strong opening mood, but no clear reason to move away from it. The fix was not a list of magic calming phrases. It was a simple model of how a difficult conversation can move forward. This note explains that model, how the live progress display works, and where the system can still get it wrong. The first character knew how to be upset The first parent scenario was easy to start. The prompt described an angry parent, gave the parent a complaint, and told the voice to push back. The result sounded convincing for the first few turns. The problem appeared when the user handled the conversation well. The model had been told why the parent was upset, but not what would make the parent become more open. It often treated anger as the character's permanent personality. A good question might produce an answer, but the next reply could jump back to the original complaint as if no trust had been built. Adding more instructions such as 'calm down when appropriate' did not solve the problem. Appropriate is too vague. The model needed to know what evidence to watch for and how its behavior should change after seeing it. A useful character needs a reason to resist Each ready-made scenario now gives the character more than a mood. It describes what happened, what the character believes, what facts they know, why they do not trust an easy answer, and what a credible resolution would look like. For example, a parent may reject a general promise because two earlier meetings led nowhere. A manager may care less about one missed deadline than about whether the same communication problem will happen again. An interviewer may accept transferabl
KH4 Companion is a small extension I built: it counts down to Kingdom Hearts IV, puts the days remaining on the toolbar badge, pulls series news and trailers from public feeds, carries a lore compendium, and hides a three-lane rhythm minigame in the popup. It has been on the Chrome Web Store since 19 August. As of this week it is also on addons.mozilla.org , which makes it my first Mozilla listing. I had been putting the port off, because "port" sounds like work. It was not. Same build, same version number, same feature set — what changed was four keys in manifest.json . This is the writeup I wanted to find before I started. The thing nobody tells you first The blocker is not your code. It is that AMO rejects the package before it ever shows you a listing form. So the order of operations is: fix the manifest, get the linter to zero errors, then worry about icons and screenshots and copy. Assets built against a package that cannot upload are wasted. npx addons-linter@latest <extension-dir> is the gate. Run it before you touch anything else. 1. Firefox needs an explicit add-on ID Chrome derives an extension ID for you. Firefox does not — in MV3 you must state it: "browser_specific_settings" : { "gecko" : { "id" : "kh4-companion@dhseadev.online" } } The email-ish form or a {8-4-4-4-12} GUID both work. Pick carefully: this ID is your update identity forever. Changing it later means a new listing, not an update. 2. There are no extension service workers in Firefox This is the real difference, and it is smaller than it sounds. Firefox runs an event page where Chrome runs a service worker. background.service_worker is simply ignored, with a BACKGROUND_SERVICE_WORKER_IGNORED warning. The cross-browser answer is the dual key: "background" : { "scripts" : [ "core/lib.js" , "background.js" ], "service_worker" : "background.js" } Chrome reads service_worker . Firefox reads scripts . One file, both browsers. Two traps live in here, and both pass a manifest review and fail at run
The Silent Killer of Next.js Performance: Component Poisoning In the modern React ecosystem, specifically within Next.js and the new paradigms introduced in React 19, the distinction between Server Components and Client Components is the most critical architectural concept to master. Yet, it is also the most frequently misunderstood. If you have ever imported a React Server Component directly into a Client Component, you have inadvertently "poisoned" your application. This silent performance killer is rampant in production codebases, leading to bloated bundles, broken security, and a complete breakdown of the server-side benefits you migrated to React Server Components (RSC) to achieve in the first place. What is Component Poisoning? Component poisoning occurs when a developer treats file boundaries as mere organizational choices rather than strict execution boundaries. When you write import MyServerComponent from './MyServerComponent' inside a file marked with 'use client' , you are telling the bundler to include that component in the client-side JavaScript bundle. The moment that import statement is parsed, the Server Component is stripped of its server-only capabilities—like direct database access or environment variable usage—and compiled into a Client Component. The result? Bundle Bloat: Code that was meant to stay on the server is now shipped to the browser. Broken Logic: Any code relying on Node.js-specific APIs or secret keys will throw errors at runtime because it is now executing in the browser's environment. Performance Degradation: The primary benefit of RSC—reducing the amount of JavaScript sent to the client—is completely negated. The Mental Model: Respecting the Serialization Boundary To avoid poisoning, you must shift your mental model. Client Components cannot "own" Server Components. They cannot import them, nor can they directly control their execution lifecycle. Instead, think of the Serialization Boundary . React Server Components render on the
Most nginx misconfigurations announce themselves. You typo a directive, nginx -t fails, you fix it. That feedback loop is fast and it works. The dangerous ones are different. The config is valid. nginx -t passes. The server starts, serves traffic, logs nothing unusual. And the thing you configured is quietly not happening. I maintain gixy-ng , a static analyzer for nginx configs. A growing share of its checks exist for exactly this category, because it turns out static analysis is the only practical way to catch a failure that produces no signal at runtime. Here are four worth knowing about. 1. OCSP stapling that staples nothing server { listen 443 ssl ; server_name example.com ; ssl_certificate /etc/ssl/example.com.pem ; ssl_certificate_key /etc/ssl/example.com.key ; ssl_stapling on ; ssl_stapling_verify on ; } Looks right. It does nothing. OCSP stapling means nginx fetches the certificate's revocation status from the CA itself and attaches it to the handshake, so the client does not have to. To do that, nginx has to make an outbound request to a hostname. nginx does not use the system resolver for runtime lookups. It has its own, and it only exists if you configure it. No resolver in scope means the hostname never resolves, the fetch never happens, and stapling is silently skipped. Your config test passes. Your clients go do their own OCSP lookups, which is the exact thing you turned stapling on to avoid. resolver 127.0 .0.1 valid=300s ipv6=off ; resolver_timeout 5s ; Use a local resolver or your cloud provider's internal DNS. Pointing this at 8.8.8.8 sends every internal lookup off your network in cleartext, which is its own problem. Check it with: echo | openssl s_client -connect example.com:443 \ -servername example.com -status 2>/dev/null \ | grep -A 17 'OCSP response' Working stapling prints OCSP Response Status: successful . Broken stapling prints no response sent . Run it twice, since the first handshake after a reload usually goes out unstapled while the f
You know technical debt. Code that works today but accumulates hidden costs over time. Shortcuts that seem reasonable in the moment and compound into architectural problems that take months to untangle. The kind of debt that doesn't announce itself until the system starts failing in ways that are expensive and slow to fix. Chronic stress works the same way. Every sprint crunch, every production incident at 11PM, every sustained period of pressure without adequate recovery — these aren't just experiences you have and move past. They're transactions against a biological account. And like technical debt, the interest compounds quietly until the system starts failing. Here's what the debt actually is, how it accumulates, and — most importantly — how to stop it before the refactor becomes mandatory. The Debt Accumulation Model javascript class StressDebt { constructor() { this.magnesium = 100 // % of optimal this.vitaminD = 100 // % of optimal this.omega3Index = 8 // % target this.HPARegulation = 100 // % of optimal this.prefrontalIntegrity = 100 // % of optimal this.dopamineBaseline = 100 // % of optimal } // called every week of unaddressed chronic stress accrue(stressLevel, coffeePerDay, supplementation) { // magnesium depletion this.magnesium -= stressLevel * 0.3 // cortisol burns magnesium this.magnesium -= coffeePerDay * 0.15 // caffeine accelerates excretion if (!supplementation.magnesium) { this.magnesium -= 0.5 // diet doesn't replace it } // downstream effects of magnesium depletion this.HPARegulation = this.magnesium * 0.9 // HPA loses regulator — cortisol response amplifies // vitamin D depletion (passive — no sun exposure) if (!supplementation.vitaminD) { this.vitaminD -= 0.3 // indoor work, winter, no replacement } this.dopamineBaseline = this.vitaminD * 0.85 // tyrosine hydroxylase requires vitamin D // omega-3 insufficiency (dietary) if (!supplementation.omega3) { this.omega3Index = 3.5 // western diet default } // neuroinflammation runs elevated at <6% /
A ticket calendar looks like an easy automation target: request a page, search for a date, and send an email when it appears. That implementation works until the first queue, partial response, stale cache or provider outage. Then it can quietly turn "I do not know" into "sold out" — or generate a false alert. I learned this while building MachuPing , an independent monitor for official Machu Picchu ticket availability. I am the maker. It does not sell, hold, reserve or buy admission; the official booking platform remains the source of truth. The useful abstraction is a small state machine: UNKNOWN -> CONFIRMED_UNAVAILABLE -> RETURNED_AVAILABLE ^ | | | v v +------------- PROVIDER_ERROR ------ ALERTED The exact labels will vary, but three rules matter. 1. Unknown is not unavailable Queues, timeouts, malformed payloads and incomplete calendars are observations about the monitor, not evidence about inventory. Persist them separately. A provider error should never close a date or trigger a reassuring "still sold out" message. 2. Match the user's real constraint "Machu Picchu is available" is too broad to be useful. Inventory is split by route, date, entry time and capacity. A valid transition requires a match for the selected combination, including the requested party size. This also prevents a common analytics mistake: counting every polling response or every seat-like value as a unique ticket. A state change is a state change, not proof of inventory volume. 3. Alert on a confirmed transition, not a snapshot The valuable event is not simply available . It is a move from a previously confirmed unavailable state to confirmed available. Persist an idempotency key for that combination so retries do not create duplicate email. Before sending, revalidate the observation when the provider permits it. The alert should still state the limitation plainly: availability can disappear before the traveller reaches official checkout. A practical event record At a module boundary, I pr
If you have ever built a health check, you have probably written something close to this: const res = await fetch ( url , { method : ' GET ' , signal : AbortSignal . timeout ( 10000 ) }); const isUp = res . status === 200 ; I ran a version of that for a while. It is wrong in at least five ways, and every one of them bit me while building an outage tracker for Indian services. This is a write-up of what actually breaks, because most monitoring tutorials stop at the snippet above. 1. The server answers, the service is dead The single biggest gap. 200 OK tells you a server returned a response. It tells you nothing about whether the thing a user came to do still works. A bank homepage can render in 400ms while UPI payments from that same bank are failing at the switch. Different systems, different teams, different failure modes. Your check is green and the feature is on fire. You cannot fully solve this from outside. What you can do is stop treating a 200 as proof of health, and stop displaying it as one. 2. 403 is not down Plenty of sites block automated requests deliberately. Bot protection, WAF rules, rate limits, geo rules. In India this is common on high-value government and travel portals. IRCTC is the obvious example. A naive checker marks these down permanently. Users learn to ignore your tool inside a week. 403 means the server is alive and refusing your specific request. That is different information from 500 , and treating them the same throws away the distinction that matters most: Code Server state What it tells a user 200 Alive, responded Little. The feature may still be broken. 401 / 403 Alive, refusing this request Usually nothing about the outage. Often your check being blocked. 404 Alive The path is wrong, not the service 429 Alive, rate limiting you You are the problem, back off 500 / 502 / 503 Broken, overloaded, or in maintenance Genuine signal 504 Something upstream did not answer Genuine signal, usually a dependency Timeout / DNS failure Unknown A
I’ve been building a browser-first project called RelicBeam, and one feature I wanted was simple in theory: Open a folder on one device and temporarily browse it from another device without installing anything. That became Remote Files, part of RelicBeam’s Device Portal. The host selects a folder, another device joins with a QR/code, the host approves the connection, and the second device can browse, preview and download files. The folder itself is never uploaded to RelicBeam. File data travels over a WebRTC DataChannel. If a direct connection isn’t possible, my own TURN server relays the encrypted traffic. Device Portal traffic is end-to-end encrypted between the connected browsers. The interesting problems The file browser itself was actually the easy part. Android file pickers kept killing sessions When I added optional uploads, I noticed something odd during testing. The first upload worked, but after opening the Android file picker a few times, the Remote Files session could suddenly disconnect. It turned out Android can background or suspend the browser while the native file picker is open. That could temporarily drop the Socket.IO signaling connection, and my server was treating any disconnect as the viewer leaving permanently. The fix was a short reconnect grace period. Temporary disconnects now get time to recover, while explicit Leave and End session actions still terminate access immediately. Firefox and Safari can browse, but not host uploads Remote Files works read-only across browsers, but writable folder access is more limited. Chrome and Edge expose writable directory handles through the File System Access API, so a host can optionally allow remote uploads into the selected folder. Firefox and Safari don’t currently expose the same writable directory picker. So today: Chrome / Edge host Browse ✅ Preview ✅ Download ✅ Optional uploads ✅ Firefox / Safari host Browse ✅ Preview ✅ Download ✅ Host uploads ❌ Firefox and Safari can still be the remote device