AI 资讯
I Found a Silent Bug in Formbricks That Crashes Live Surveys at Runtime
This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry . Project Overview Formbricks is an open-source survey and experience management platform built with Next.js, TypeScript, React, and Tailwind CSS. It lets teams create and deploy surveys across websites, apps, and email. Developers can self-host it or use the cloud version. With over 12,000 GitHub stars and hundreds of contributors, it is one of the most actively maintained open source alternatives to Qualtrics. Bug Fix or Performance Improvement Formbricks supports custom regex validation rules on survey questions. A survey creator can set a pattern that user responses must match before they are accepted. The problem was in how that pattern was stored. The validation schema for regex pattern rules only checked that the input was a non-empty string: // Before the fix export const ZValidationRuleParamsPattern = z . object ({ pattern : z . string (). min ( 1 ), flags : z . string (). optional (), }); z.string().min(1) means "give me any string with at least one character." It does not verify that the string is actually a valid regular expression. So a survey creator could type [invalid as their pattern, the schema would accept it, it would be saved to the database, and then when a real user submitted a response, the system would try to run new RegExp("[invalid") , JavaScript would throw a SyntaxError , and the survey would crash silently at runtime. The bug never surfaced during setup. It only appeared when a real user was trying to submit a real response. Code Pull Request: github.com/Tobore005/formbricks/pull/1 Here is the fix: const isValidRegexPattern = ( pattern : string ): boolean => { try { new RegExp ( pattern ); return true ; } catch { return false ; } }; const isValidRegexFlags = ( flags : string | undefined ): boolean => { if ( flags === undefined ) return true ; try { new RegExp ( "" , flags ); return true ; } catch { return false ; } }; export const ZValidationRuleParamsPa
AI 资讯
The date string that invented $725 in a synthetic margin report
This is a submission for DEV's Summer Bug Smash: Clear the Lineup , powered by Sentry . Project Overview I built LeakLens Control Room to keep AI away from the financial math. Python calculates every dollar. The optional model explains the evidence. A human decides whether to act. LeakLens is a live, functional restaurant margin-review application built with Python, SQLite, HTML, CSS, and JavaScript. It uses synthetic data in the public demo. The optional application narrative provider defaults to GPT-5.6, but every rate, threshold, ranking, and dollar scenario comes from deterministic code. Then a date string proved that deterministic code can still lie. I had let a raw CSV field decide which operating day counted as "current." Bug Fix or Performance Improvement The symptom LeakLens groups channel metrics by date, sorts those dates, and analyzes the last one against prior days. That works when every input is canonical ISO data such as 2026-07-09 . The loader only checked that the date field was not blank. This input slipped through: metrics = [ DailyMetric ( " 2026-7-9 " , " direct " , 500 , 250 , 0 , 0 , 125 ), DailyMetric ( " 2026-07-10 " , " direct " , 1000 , 50 , 0 , 0 , 250 ), ] July 10 is later than July 9. Python's string ordering sees something else: sorted ([ " 2026-07-10 " , " 2026-7-9 " ]) # ['2026-07-10', '2026-7-9'] LeakLens selected the final string, 2026-7-9 , as the current day. That mistake spread through the report: expected current date: 2026-07-10 actual current date: 2026-7-9 actual gross sales: 500.00 sales-loss scenario: 500.00 discount scenario: 225.00 The input had not shown a current sales collapse or discount leak. The analyzer compared the days backward and produced $725 of false scenario impact from synthetic data. For a restaurant operator, this is worse than a visible crash. A crash asks for attention. A plausible financial report can send someone to audit the wrong promotion, question the wrong manager, or change a schedule that was
AI 资讯
I Fixed Unbounded Shell Output in an Open Source Agent. My First Draft Would Have Corrupted Text.
A few weeks back I picked up google-gemini/gemini-cli issue #28090: the shell tool was forwarding a command's entire stdout/stderr straight into the model's context, with no cap unless you opted into an LLM-based summarization step. Run one noisy build command and you'd hand the model tens of thousands of tokens of log spam it never asked for. The fix sounded trivial: cap the output before it goes into llmContent . I had a one-liner in my head before I'd even opened the file. That one-liner is exactly the kind of "obviously correct" fix that ships bugs. The one-liner The naive version looks like this: const MAX = 32 * 1024 ; // 32 KiB function truncate ( output ) { if ( output . length <= MAX ) return output ; return output . slice ( 0 , MAX ) + ' \n ...[truncated]... \n ' + output . slice ( - MAX ); } It compiles. It passes a quick manual test with a big ASCII log file. It looks done. I almost committed it as-is before writing the actual test suite. The problem is what .slice() is slicing. JavaScript strings are sequences of UTF-16 code units, not bytes and not Unicode codepoints. Most characters in typical shell output (letters, digits, punctuation) are one code unit each, so .slice() looks safe in casual testing. But the moment real-world command output contains anything outside the Basic Multilingual Plane — an emoji in a commit message, certain box-drawing/progress-bar characters some CLIs use, non-Latin filenames — that character is represented as a surrogate pair : two 16-bit code units that only mean something together. Slice between them and you don't get an error. You get one dangling unpaired surrogate on each side of the cut, silently baked into the string that gets sent to the model. No exception. No lint warning. JSON.stringify on the payload can even throw later, in a completely unrelated part of the request pipeline, for a reason that has nothing to do with where the bug actually is. Or worse: it doesn't throw, and the model just receives a slightly
AI 资讯
The Hardest System I Ever Built Was for Patients Who Could Not Afford for It to Fail
This is a submission for DEV's Summer Bug Smash: Smash Stories powered by Sentry . There is a version of this story where I walk you through the technical challenges in a clean, detached voice. Structured. Professional. Composed. This is not that version. Seven months. A hundred pages of documentation written alongside the code, not after it. A live system serving rural clinics in Delta State, Nigeria. And three bugs so specific and so cruel that each one felt designed to find the exact gap between what I thought I knew and what I actually knew. The Delta Health Information and Appointment Booking System was my final year project at the University of Port Harcourt. That sentence makes it sound smaller than it was. This was a real system for real clinics, serving patients who travel for hours to reach a healthcare facility only to find the clinic is full, the doctor is not in, or nobody told them their appointment was cancelled. These are not hypothetical users. These are people whose access to healthcare depends on whether the software works. That is a different kind of pressure than getting a good grade. And it did not start at deployment. Before a single line of code was written, I had to pitch the entire concept to my university supervisor, the Head of Department, the Faculty Dean, and an external examiner. The system had to be defensible not just technically but practically. It had to demonstrate that it solved a real problem that real people in Delta State were actually facing. I was presenting to people with decades of experience who would ask questions I had not thought of yet. That kind of scrutiny either sharpens you or breaks you. I chose to let it sharpen me. The stack was React.js, Node.js, PHP with Laravel, MySQL, AWS, and Docker. Communication channels included SMS gateways, WhatsApp Business API, and USSD because not every patient in rural Delta State has a smartphone. The system had to run on 2G connections, on entry-level Android phones with 2GB RAM
AI 资讯
If These Letters Are Trying To Communicate With Me, They Should File Their Own Bug Report ;)
This is a submission for DEV's Summer Bug Smash: Smash Stories powered by Sentry . Copilot started whispering random letters into my ear, like it was leaking secret tokens from the underworld! Cute, but also, a bug! This sounds like a classic data serialization or sanitization failure in the text-to-speech (TTS) pipeline. What is happening here is a classic disconnect between the UI layer (what you see on screen) and the data payload sent to the background engine. When you read a response on screen, the Android app renders clean Markdown or HTML. However, when you tap "Read Aloud," the app has to strip away all background formatting, structural code, and system metadata, converting the response into a raw, clean string of text before handing it off to the mobile TTS engine. In this case, the background script running the relay is failing to sanitize that data stream. It is accidentally passing raw control characters, escape sequences, or hidden system tracking tokens (like structural delimiters or character-encoding artifacts) directly into the text pipeline. Because the mobile TTS engine doesn't understand that these symbols are meant to be ignored structural code, it tries to do exactly what it’s programmed to do: it reads them literally. When the voice engine encounters unexpected symbols, raw strings of characters, or broken text boundaries in the middle of a sentence, it completely disrupts the engine's predictive text processing: ● Pitch and Speed Fluctuations: Mobile TTS engines use deep learning models to predict natural tone, cadence, and inflection based on context. Injecting random, non-linguistic characters completely derails the engine's context window, causing it to panic-adjust its pitch, speed, and emphasis mid-sentence. ● Mispronunciations: The hidden characters slice words in half semantically, forcing the engine to mispronounce standard words because it's trying to blend them with the rogue data trailing right behind them. Here is a direct, high-s
AI 资讯
My Publishing Task Said "Commit the Drafts." My .gitignore Had Other Plans.
I run a scheduled agent that writes and publishes DEV.to articles twice a day. Step 5 of its instructions has always said the same thing: write the log entry, commit the drafts, push. I've read that line a dozen times without questioning it. This morning I actually checked what "commit the drafts" had been doing for the last month, across more than thirty published articles. The answer: nothing. Not once. the check that should have happened on day one The task instructions reference source files like drafts/scheduled-agent-shared-quota-no-memory.md and drafts/one-env-key-two-usernames.md in every log entry — real filenames, written by the agent, presumably sitting in the repo as a record of what was drafted before it got published. I went looking for one of them: $ git log --all -- drafts/ $ # (nothing) Empty. Not "one commit, then deleted later" — completely absent from git history since the very first commit in this repo. Every single run that logged "committed drafts + the log" had, in fact, only ever committed the log. why git let this happen silently .gitignore in this repo has listed drafts/ since the initial commit: . env __ pycache__ / *. pyc codes / drafts / . omc / . claude / CLAUDE . md That line predates the scheduled publishing task entirely — it was almost certainly written back when drafts were just scratch files someone edited by hand before a publish script existed. Nobody revisited it when the publishing task's instructions later started saying "commit the drafts." The instruction and the ignore rule have been quietly contradicting each other since before either was written by the same person in the same sitting. Here's what actually happens when a script (or an agent) runs git add drafts/whatever.md in this repo: $ git add drafts/gitignore-ate-my-drafts-folder.md The following paths are ignored by one of your .gitignore files: drafts hint: Use -f if you really want to add them. hint: Turn this message off by running hint: "git config advice.addIgn
AI 资讯
Clear the Lineup — doesNotEqual was always true for single-select survey answers in Formbricks
This is a submission for DEV's Summer Bug Smash: Clear the Lineup . Project Overview Formbricks is an open-source survey and experience-management platform. Its packages/surveys package ships the client-side survey runner, and inside it, packages/surveys/src/lib/logic.ts is the module that decides, for every question and every "skip logic" / branching rule a survey author configures, whether a given condition ( equals , doesNotEqual , contains , isEmpty , and friends) is true for a respondent's answer. This is pure, unglamorous logic — but it's load-bearing: it's what routes respondents through the correct sequence of questions. I picked up issue #8527 for the "Clear the Lineup" track, which reports that doesNotEqual conditions weren't behaving correctly for single-choice questions. Bug Fix or Performance Improvement The doesNotEqual operator is supposed to be the exact logical negation of equals . For most answer shapes it was. But for MultipleChoiceSingle answers, the survey runtime sometimes represents the selected value as a single-element array rather than a bare string (this happens via getLeftOperandValue , e.g. when a choice answer is merged with an "other" option path). To handle that shape, both equals and doesNotEqual had a special-case clause that unwraps a one-element array and compares its only entry to the right-hand value. equals 's fallback: return ( ( Array . isArray ( leftValue ) && leftValue . length === 1 && typeof rightValue === " string " && leftValue . includes ( rightValue )) || leftValue === rightValue ); doesNotEqual 's fallback (before the fix): return ( ( Array . isArray ( leftValue ) && leftValue . length === 1 && typeof rightValue === " string " && ! leftValue . includes ( rightValue )) || leftValue !== rightValue ); At a glance this looks like a faithful "negate every sub-expression" mirror of equals . It isn't. The array-includes clause was correctly inverted ( !leftValue.includes(rightValue) ), but the second disjunct, leftValue !==
AI 资讯
Clear the Lineup — Chasing a Stack Overflow in Typst's `#eval` Down to One Wrong Word
This is a submission for DEV's Summer Bug Smash: Clear the Lineup . Project Overview Typst is the Rust-based markup typesetting system that's been quietly eating LaTeX's lunch for the last couple of years — you write plain-text markup, Typst compiles it to PDF (or HTML) with a real incremental compiler underneath instead of a multi-pass macro soup. Part of what makes it pleasant is that it exposes its own evaluator to itself: a document can call #eval("some typst code") and get back whatever that code produces, at runtime, inside the document being compiled. It's a small feature, but it opens up a surprisingly deep rabbit hole, which is exactly where this bug was hiding. Bug Fix or Performance Improvement The problem: typst/typst#8632 reports that Typst crashes instead of erroring when #eval is used to recursively re-import the file it's running in. The repro is one line. Save this as overflow.typ : #eval("import \"overflow.typ\"") Then: $ ./target/debug/typst compile overflow.typ overflow.pdf thread 'main' (750833) has overflowed its stack fatal runtime error: stack overflow, aborting No PDF, no diagnostic, no exit code you can act on — just a native stack overflow and a SIGABRT. Typst has had cyclic-import detection since forever; a normal top-level import "overflow.typ" inside itself gets caught cleanly with an error: cyclic import message. Something about routing the import through #eval specifically was bypassing that check. The hunt I'll be upfront about how this investigation actually went: I worked through it with Claude (Anthropic's coding agent) rather than solo. The triage pass it did before I sat down with the code had already narrowed the search to one function — eval_string in crates/typst-eval/src/lib.rs — and named the suspect line. That's a big head start, but a named suspect isn't a confirmed root cause, so the actual work was reading that function next to its sibling, the file-level eval() a few lines above it, and checking the claim against the s
AI 资讯
The cleanup script that reported success for weeks and never killed a thing
I wrote a cleanup routine that matched processes by command line with a wildcard pattern. It reported success on every run. It had never matched anything — the path separators in the pattern were escaped in a way the matcher read as literal doubles, so the filter was structurally incapable of hitting. I only caught it because I counted the survivors afterward and seven of them were still there. The fix was switching from a wildcard match to a plain substring containment check with no escape semantics at all. A filter that cannot fail loudly will lie to you politely forever. Before trusting any matcher, feed it a known-positive and watch it fire — a green result from an instrument you never saw go red is noise. What's the equivalent lesson your worst bug taught you?
科技前沿
AWS Billing Glitch Hits Customers With Billion-Dollar Fees
An error with the cloud computing giant’s billing operation caused some customers’ monthly bills to rise from a few cents to billions of dollars.
AI 资讯
The Advisory Said It Was Fixed. I Didn't Believe It Until I Broke It Myself.
Update: This investigation spawned a complete stress-testing toolkit. See the apktool-diagnostics repo for the full tool with security scanning, round-trip validation, and fuzzing setup. The Setup I was deep into a project stress-testing apktool — the standard tool for decompiling and rebuilding Android APKs — hunting for real bugs to fix, not hypothetical ones. While digging through recent issues and advisories, I ran across a patched vulnerability: CVE-2026-39973 , a path traversal bug that let a malicious resources.arsc make apktool write decoded files outside the intended output directory. Already fixed upstream. Advisory published. Case closed, according to everyone else. I didn't believe it until I broke it myself. Why "already patched" wasn't good enough Advisories describe what should happen. They don't prove it. And a tool like apktool has one job that matters more than any other: you point it at a file you don't trust — someone else's APK — and it processes that file on your machine. If the sanitization it depends on for that trust boundary can silently regress in a refactor (which is exactly what happened here — a PR reorganizing resource-file-path construction dropped the call to BrutIO.detectPossibleDirectoryTraversal() without anyone noticing), then "patched now" only tells you about today. It doesn't tell you the mechanism actually holds, and it doesn't tell you what "vulnerable" concretely looks like on your own machine, with your own files. So instead of writing "confirmed CVE-2026-39973, see upstream fix" and moving on, I decided to reproduce it myself, end to end. Building both sides of the fence Step one: build apktool twice from source — once at v3.0.1 (the last vulnerable tag), once at current HEAD (post-fix). Now I had a vulnerable binary and a patched binary sitting side by side, and no more reason to trust anyone's summary of what the difference actually did. Step two: I needed a malicious input. The vulnerability lived in how ResFileDecoder
AI 资讯
A Tiny LLM Request Recorder I Use to Reproduce Production Failures
Most LLM failures are easy to describe and surprisingly hard to reproduce. A user reports that the model returned an empty answer. A tool call disappeared halfway through a stream. One provider rejected a request that worked everywhere else. Then I open the logs and find something like this: LLM request failed: 400 Bad Request Technically true. Operationally useless. The missing piece is usually the exact request shape: model, parameters, message roles, tool definitions, timeout behavior, and the raw provider response. I wanted something smaller than a full observability platform, so I built a request recorder around fetch . It stores enough information to inspect or replay a failed call without logging the API key. What the recorder captures For each request, I want: a unique request ID timestamp and duration URL and model sanitized request body HTTP status raw response body network or timeout errors I deliberately do not record the Authorization header. Prompt content is also redacted by default. Full payload capture must be enabled explicitly because storing production prompts can create a much worse problem than the bug being investigated. The recorder This example runs on Node.js 18 or newer and has no external dependencies. Create recorded-fetch.mjs : import { randomUUID } from " node:crypto " ; import { mkdir , writeFile } from " node:fs/promises " ; import path from " node:path " ; function sanitize ( value , captureContent ) { if ( Array . isArray ( value )) { return value . map (( item ) => sanitize ( item , captureContent )); } if ( ! value || typeof value !== " object " ) { return value ; } const result = {}; for ( const [ key , child ] of Object . entries ( value )) { const normalizedKey = key . toLowerCase (); if ( normalizedKey . includes ( " api_key " ) || normalizedKey . includes ( " apikey " ) || normalizedKey . includes ( " authorization " ) ) { result [ key ] = " [REDACTED] " ; continue ; } if ( ! captureContent && ( normalizedKey === " content "
AI 资讯
How a Simple Ping Took 4 Hours: WireGuard, Docker Desktop, and the Silent Linux Kernel Drops
I have been working on building a private, secure network accessible from anywhere. The goal was to connect my mobile phone and my local development laptop using a WireGuard VPN , hosting the central gateway on a free-tier Google Cloud Platform (GCP) e2-micro instance. I wanted to access my self-hosted services, specifically my Docker-hosted Open WebUI , running on my local home Wi-Fi connected laptop, directly from my phone using mobile data. It sounded straightforward. But if you read my other from scratch journeys, you might have already guessed, it was not. The Setup My architectural plan was a simple hub-and-spoke topology: The Hub: GCP VM ( 10.66.66.1 ) with IPv4 forwarding enabled. Spoke 1 (My Phone): 10.66.66.2 Spoke 2 (My Laptop): 10.66.66.3 I wrote my server configurations, enabled IP forwarding ( net.ipv4.ip_forward=1 ), wrote the iptables rules to allow forwarding between peers, and started the interfaces. Then came the moment of truth. I tried to bring up the tunnel. Absolute silence. No packet moving from anywhere. Hurdle 1: The Classic Cloud NAT Trap (Internal vs. Public IP) Before I could even worry about routing packets between my phone and laptop, I couldn't even get them to handshake with the GCP server. Like many of us do when working inside a VM, I had run ip addr on the GCP instance to grab its IP address for my client configurations. I set up the WireGuard peers to point to this IP. Nothing connected. The Culprit: GCP (and AWS) operates on a 1:1 NAT mapping. The virtual network interface inside your VM only sees and binds to a private, internal cloud IP (e.g., 10.128.0.x ). The public IP assigned to your instance lives outside the VM at the VPC gateway level. By putting the internal IP into my client configs, my phone and laptop were trying to connect to a private address that didn't exist on their local networks. The Fix: I had to swap the internal IP in the client configurations with the GCP Ephemeral/Static External IP . Once the handshake
AI 资讯
Why did my benchmark stop at N=22? A debugging story in nine bugs
Submission for DEV's Summer Bug Smash — Smash Stories track. There was a file in my repo called run_benchmark_1_22.py . Not 1 to 24, which is what the harness was written to do. Not 1 to 26, which is how many Mersenne exponents the agents know. Twenty-two. A chart in the README — a2a_latency_times_1_22.png — agreed. At some point, past-me had decided the benchmark ends at 22, committed the evidence, and moved on. This summer, hunting for a Bug Smash target, I finally asked: why 22? The setup a2a-benchmark compares A2A agent performance across four languages. Python and Go sit behind Gemini tool-calling (ADK); Node and Rust are bare HTTP handlers. Each computes Mersenne primes with Lucas–Lehmer; a harness sweeps N from 1 to 24 and draws two charts. I ran the full sweep. At N=24, the Python column printed N/A . Every other language returned data. There it was — not a decision, a crash , worked around by shortening the run until it stopped hurting. The 4,300-digit wall The Python agent's response at N=24 wasn't even subtle about it: "Exceeds the limit (4300 digits) for integer string conversion; use sys.set_int_max_str_digits() to increase the limit" CPython 3.11 added a default cap on int→str conversion — 4,300 digits — as a denial-of-service mitigation. My agent stringified every prime it found. The 24th Mersenne prime, 2^19937−1, has 6,002 digits . Here's the part that made me laugh out loud: the stringified list was never returned . The tool reports only its elapsed time. The line that had silently amputated my benchmark at N=23 was decorative. The fix was git rm energy: delete the str() , keep the raw int. Go had the identical dead weight ( val.String() ) inside its timed region — it just happened not to crash. One deleted expression, and a column of data that had never existed came into being: N=24, Python, 2,425.9 ms. It gets worse before it gets better With the agents finally running, I kept pulling the thread. The harness parsed Python's elapsed time out of th
AI 资讯
My benchmark's Python column was N/A for a year — CPython's 4300-digit limit, and eight other bugs
Submission for DEV's Summer Bug Smash — Clear the Lineup track. The codebase a2a-benchmark is my multi-language A2A (Agent-to-Agent) performance suite: four agents — Python and Go behind Gemini tool-calling via ADK, Node.js and Rust as direct handlers — each compute Mersenne primes with the Lucas–Lehmer test, while a harness sweeps N=1–24 and charts calculation time and round-trip time. The committed results stopped at N=22. I never questioned that. I should have. The headline bug: a whole column of data didn't exist CPython 3.11+ limits int→str conversion to 4,300 digits by default (a DoS mitigation). My Python agent stringified each prime it found: mersenne_primes . append ( str (( 1 << p ) - 1 )) The 24th Mersenne exponent is p=19937, and 2^19937−1 has 6,002 digits . So for any request of 24+ primes, the tool raised ValueError — and the A2A response dutifully delivered the stack-trace text instead of a result: "text": "Exceeds the limit (4300 digits) for integer string conversion; use sys.set_int_max_str_digits() to increase the limit" The benchmark's Python column was structurally incapable of producing data at N≥24. The kicker: the stringified list was never used . The tool returns only elapsed_time . The fix is deleting the str() — which also removes formatting work from the timed region that the Node and Rust agents never paid (Go had the same dead val.String() call). Fix: PR #1 — plus a switch from time.time() (wall clock, non-monotonic) to time.perf_counter() , and a regression test at count=24. Before/after, N=24 row: Node.js Rust Go Python before 1633.01 ms 812.57 ms 1451.49 ms N/A (crash) after 1616.13 ms 824.10 ms 1531.10 ms 2425.9 ms The harness was reading its data from LLM prose The Python agent's timing came back in two places: a structured elapsed_time in the tool artifact, and the model's prose. The harness regexed the prose first : m = re . search ( r " It took ([\d\.\-e]+) seconds " , text ) In live runs, Gemini said "Calculating the first 5 Mer
AI 资讯
The SSE Fragmentation Catastrophe That Took Down CareerPilot AI (Smash Stories)
This is a submission for DEV's Summer Bug Smash: Smash Stories powered by Sentry . It was 11:14 PM. My friend DM'd me on Twitter: "Your app just hung for 30 seconds, spun indefinitely, and then completely died." I opened the browser DevTools console pointed at production and saw it — the screen flooded in red: GET https://careerpilot-ai.run.app/api/analyze-career net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 (OK) The Server-Sent Events stream powering CareerPilot AI was systematically collapsing on Google Cloud Run. And I had no idea why. Locally on localhost:3000 , the agentic pipeline was a masterpiece. The multi-stage reasoning logs streamed gracefully — Step 1 flowed into Step 6, the final structured JSON payload arrived within seconds, the UI lit up with a complete personalized career roadmap. Beautiful. But once deployed behind Google's Front End (GFE) proxy, the pipeline was a graveyard of broken sockets. The Architecture Under Fire CareerPilot AI runs a six-stage agentic pipeline on every career analysis request. Instead of firing a single long-running prompt to Gemini and making the user stare at a blank screen for 20+ seconds, we designed a Server-Sent Events logging stream to broadcast real-time reasoning steps directly to the browser — giving the interface the feel of a live, active mentor thinking out loud. Once the final stage (Self-Evaluation & Constraint Validation) completed, the backend constructed a massive, nested 15KB JSON payload containing the personalized roadmap: skill weightings, role benchmarks, resource links, and a 30-day milestone calendar. Here was the delivery mechanism — and the landmine hiding inside it: // server.ts — The vulnerable streaming channel app . get ( " /api/analyze-career " , async ( req , res ) => { res . setHeader ( " Content-Type " , " text/event-stream " ); res . setHeader ( " Cache-Control " , " no-cache " ); res . setHeader ( " Connection " , " keep-alive " ); // Stream intermediate reasoning logs per step for ( let st
AI 资讯
Laptop Memory Leak Story
I found a slow, insidious memory leak in a Node.js API gateway caused by lingering event listeners; I fixed it by scoping emitters per request, enforcing cleanup in finally blocks, and adding leak‑aware tests and runtime safeguards—memory usage flattened and OOM restarts stopped. The Incident The gateway handled TLS termination, auth, and request fan‑out for many microservices. Over weeks its resident set size climbed in a staircase pattern until Kubernetes began OOM‑killing pods under load. The failure was gradual —light traffic ran for days, peak traffic crashed in hours—so it escaped casual monitoring. Investigation Heap snapshots and allocation profiles showed growing counts of small objects —closures, request metadata, and event listeners—rather than one giant allocation. Tracing revealed an internal event bus where request‑scoped listeners were attached but not always removed: an early‑exit authentication path returned before the cleanup function ran, leaving listeners that held references to request state. The GC saw those objects as live and never reclaimed them. The Fix (technical details) 1. Scoped emitters per request. Replace global emitters for request‑local concerns with a short‑lived EventEmitter created at request start. When the request ends, the emitter goes out of scope and the whole closure graph becomes collectible. 2. Guaranteed teardown via try/finally . Wrap the entire request pipeline so cleanup runs on success, error, or early return; the finally detaches any remaining listeners, clears timers, and releases caches. 3. Leak‑aware CI tests and runtime metrics. A harness simulated thousands of requests across code paths, captured heap snapshots, and asserted bounded object counts. Production metrics tracked listener counts and emitted alerts when thresholds were exceeded. 4. Operational safeguards. Added backpressure on accept queues, a soft memory threshold that disabled nonessential tracing, and rollout halting on excessive crash loops. Thes
开发者
I Stopped Debugging at My Desk. Here's What Changed
I usually write here about Next.js, caching, and the bugs that steal your sleep. But this post is...
AI 资讯
The same input gave me a different translation every time. The bug wasn't where I thought.
I kept re-running the exact same input through my translation app. Same code. Same model. Same everything. And the word "machines" kept flipping between two different translations. Sometimes it came out as "機械" (machine). Sometimes as "あなたのPC" (your PC). No code changed between runs. No input changed either. My first assumption was a race condition somewhere in my pipeline. It wasn't. Where I actually looked I checked the obvious suspects first: caching, threading, anything stateful that could make the same input behave differently on different runs. All clean. So I went one level deeper, into how the model picks the winning word. Translation models score every candidate word and pick whichever scores highest. When I logged the actual scores for "machine" vs "your PC" on this input, they were almost exactly tied. That's the part that mattered. When two candidates are separated by a tiny margin, the order floating-point operations get summed in can nudge the score just enough to flip which one wins. Same math, same inputs, different accumulation order between runs — and a near-tie flips sides. Nothing was actually random. It was deterministic all the way down. It just wasn't deterministic in a way I could predict, because the thing that decided the winner was rounding noise several layers below anything I was testing. The fix wasn't "make it deterministic" Forcing strict floating-point determinism across an ML pipeline is its own rabbit hole, and not one I wanted to go down for one word. Instead, I looked at why the tie was so close in the first place. "Machine" and "your PC" were close enough in meaning, in this context, that the model wasn't confident either way. So I widened the margin instead of trying to eliminate the noise: I swapped the input word choice from "machines" to "equipment," which the model was much more decisively confident about. Scores stopped being close enough for rounding noise to matter. The flip-flopping stopped. I want to be honest about a
开发者
Cloudflare Identifies Race Condition in hyper’s HTTP/1 Implementation
Cloudflare recently documented how its development team identified and fixed a rare bug in the widely used Rust HTTP library hyper that could silently truncate large HTTP responses while still returning a successful 200 OK status. The issue had existed for years, was triggered only under specific timing conditions, and has now been fixed upstream. By Renato Losio