{"articles":[{"feed_name":"The Verge","feed_url":"https://www.theverge.com/rss/index.xml","title":"Synth historian Oli Freke will spend big on a good bicycle","link":"https://www.theverge.com/entertainment/971013/oli-freke-bjooks-beat-gems-drum-machine-book-interview","description":"Oli Freke is a musician and journalist whose works have appeared in Sound on Sound, The Quietus, and Mixmag. This has included using math to explore the melodic potential of the Western 12-tone scale and deep dives on effects plug-ins. He's even written a book tracing the evolution of the synthesizer from 1963 through 1995, [&#8230;]","content":"Oli Freke is a musician and journalist whose works have appeared in Sound on Sound, The Quietus, and Mixmag. This has included using math to explore the melodic potential of the Western 12-tone scale and deep dives on effects plug-ins. He’s even written a book tracing the evolution of the synthesizer from 1963 through 1995, charting the change from analog to digital and back.\nSynth historian Oli Freke will spend big on a good bicycle\nHe’s hard at work on the upcoming Beat Gems, a gorgeous coffee-table book about the history of drum machines.\nSynth historian Oli Freke will spend big on a good bicycle\nHe’s hard at work on the upcoming Beat Gems, a gorgeous coffee-table book about the history of drum machines.\nHis new project is Beat Gems, a coffee-table visual history of drum machines from publisher Bjooks. Like other Bjooks titles, Beat Gems is loaded with gorgeous photos, fascinating historical tidbits, and practical advice for making the most of your gear, whether it’s a vintage Roland TR-808 or a modern oddity like Erica Synths’ Pērkons.\nWhat is the first app you install on a new phone or computer?\nIf I’m buying a new computer, it’ll almost certainly be my music computer, so I’ll be installing Ableton Live straight away. It’s a remarkable piece of music software, with a feature set that still feels staggering compared to when I started making electronic music with an Atari ST 520 that could just about manage MIDI, with no audio at all.\nWhich social media platform do you use the most (if any)?\nProbably YouTube, if we’re counting it as social media. It’s brilliant having experts in almost any subject area explain ideas, concepts, and methods. I watch a lot of science, maths, chess, and, inevitably, electronic music. I could do with fewer algorithmic clickbait temptations, which is one reason I’m not a heavy user of other social media platforms.\nWhich is the most disappointing gadget you’ve ever owned?\nVegetable choppers and cubers seem like a great idea, but they never work properly and never actually save any time or effort.\nWhat game do you have the fondest memories of?\nElite on the Atari ST was incredible. It’s world-building on a (memory) shoestring when severe technical constraints existed, and the results were truly creative.\nWhat is one thing you wish you had created?\nAs a musician, I wish I’d written a song that the whole world knew and loved, and that fans would demand I play at every opportunity — until I got so heartily sick of it that, in the end, I wished I’d never written it. Perhaps this has happened, and all these wishes have come true!\nWhat’s the best piece of advice you’ve ever received?\nFrom my father (I’m appropriately writing this on Father’s Day in the UK): “Say yes to any opportunity that comes your way — worry about whether you can do it or not later!”\nWhat is your current obsession?\nDrum machines! Researching for the Beat Gems book has reminded me of the primacy of rhythm to the human musical experience; how drum machines have tapped into this need, and in doing so have transformed musical culture over the last sixty years. It’s utterly fascinating.\nWhat do you do when you need to focus?\nI put on www.rinse.fm/kool, choose a set of banging drum’n’bass rollers, and then I’m very happy (and focused).\nWhat do you do when you’re feeling stuck?\nRemind myself of the infinite number of combinations of ideas. I once wrote an article that demonstrated there were 82 quintillion (82^18) 10-note melodies that can be written with the eight notes of the octave.\nWhat’s the last piece of physical media you bought?\nThe soundtrack on vinyl to a beautiful film called Resynator. It’s about an obscure synth from the early ’80s and a daughter’s quest to learn about her father, who invented it but sadly died while she was a baby. Can’t recommend it enough.\nWhat do you think is worth splurging on?\nA high-quality road bicycle. Compared with cars, bikes are cheap, and you can buy machines not far off the quality professionals use for relatively reasonable sums. The difference between a good bike and a run-of-the-mill one is vast. Cycling in the countryside on a good bike brings me a great deal of joy.\nWhat would the tagline for your biopic be?\nWatch a man type words into a computer. For ages. Then watch him make music on a computer. Also for ages. Then watch him do some normal things involving a house, family, and car. Occasionally he is turned away from boarding a plane to Berlin to go to the launch of his Kickstarter because they changed the rules without him realizing it! Disappointingly, this doesn’t transform the biopic into a Falling Down-type scenario.\nWhat’s the last GIF or meme you used?","published":"2026-07-25T11:15:00-04:00","author":"Terrence O’Brien","guid":"https://www.theverge.com/?p=971013","created_at":"2026-07-25T17:27:31.065226","last_synchronized":"2026-07-25T17:27:31.065226","sentiment":{"sentiment":"Neutral","score":0.0,"details":{"neg":0.0,"neu":1.0,"pos":0.0,"compound":0.0}}},{"feed_name":"The Verge","feed_url":"https://www.theverge.com/rss/index.xml","title":"Teenage Engineering’s unique music machines are 30 percent off","link":"https://www.theverge.com/gadgets/970685/teenage-engineering-30-percent-deal-sale","description":"With elements from synthesizers, loopers, and effects pedals, Teenage Engineering’s devices lie somewhere between musical instrument and modern art. These grooveboxes, as they’re often called, are capable of helping you produce songs in real time from a single unit, with an eye-catching design language. A wide swath of the brand’s offerings are 30 percent off [&#8230;]","content":"With elements from synthesizers, loopers, and effects pedals, Teenage Engineering’s devices lie somewhere between musical instrument and modern art. These grooveboxes, as they’re often called, are capable of helping you produce songs in real time from a single unit, with an eye-catching design language. A wide swath of the brand’s offerings are 30 percent off at Guitar Center when you use code TE30, at Amazon when you click the on-page coupon for whichever product you’re interested in, and B&H Photo (discount applied automatically at checkout). The terms for the Amazon coupon claim it will run through August 8th, 2026, while Guitar Center deal is listed as “limited time.”\nTeenage Engineering’s unique music machines are 30 percent off\nAll in-stock instruments are discounted, and we’ve highlighted a couple that stand out if you’re just getting started.\nAll in-stock instruments are discounted, and we’ve highlighted a couple that stand out if you’re just getting started.\nThe OP-XY is one of the flagship units, which my colleague Terrence O’Brien described as a “greatest hits tour for Teenage Engineering.” Normally $2,299, the discount code brings the price down to a more palatable $1,609.30, which is a lot cheaper, but still pretty pricey for an aspiring musician. That said, it’s loaded with features for both production and live performance, and has a premium, durable build quality that reflects its price point. It’s currently sold out at Amazon, but in stock and discounted at Guitar Center and B&H Photo. Read our Teenage Engineering OP-XY review.\nThe model I’m most tempted by is the EP-40 Riddim, a compact sampler with a library of over 400 built-in synth settings and effects particularly well-suited for producing dub and reggae music, plus 128MB of storage for your own loops and recorded sounds. It’s a bit less intimidating for a beginner than the OP-XY, both in terms of price point and features, and its tighter genre focus may help concentrate your efforts and onboarding. Normally $329, the EP-40 is $230.30 at Amazon, Guitar Center, and B&H Photo during the promotion.","published":"2026-07-25T11:00:00-04:00","author":"Brad Bourque","guid":"https://www.theverge.com/?p=970685","created_at":"2026-07-25T17:27:31.065226","last_synchronized":"2026-07-25T17:27:31.065226","sentiment":{"sentiment":"Positive","score":0.5859,"details":{"neg":0.0,"neu":0.918,"pos":0.082,"compound":0.5859}}},{"feed_name":"DEV Community","feed_url":"https://dev.to/feed","title":"12 things to check before you ship your vibe-coded app","link":"https://dev.to/decivo/12-things-to-check-before-you-ship-your-vibe-coded-app-n0p","description":"<p>Getting an app to <em>work</em> has stopped being the hard part. You describe what you want, Lovable or Bolt or v0 builds it, and forty minutes later there's something on a real URL that real people can click.</p>\n\n<p>The hard part moved. It's now everything between \"it works\" and \"it survives contact with the internet.\"</p>\n\n<p>That gap isn't a vibe. It's measurable. <a href=\"https://www.symbioticsec.ai/research\" rel=\"noopener noreferrer\">Symbiotic Security</a> crawled 65,643 URLs and fully scanned 1,072 Supabase-backed vibe-coded apps in June 2026: 98% had at least one security issue, 16% had something critical. A separate <a href=\"https://arxiv.org/abs/2606.23130\" rel=\"noopener noreferrer\">academic study by Deng et al.</a> found that vibe-coded apps show <em>recurring</em> vulnerability patterns that differ from the ones traditional codebases produce — meaning these aren't random mistakes, they're structural. And an Xint.io analysis reported by SecurityWeek turned up 434 exploitable flaws concentrated in secrets exposure, broken authorization and denial of service.</p>\n\n<p>Same handful of failure modes, over and over. Which is good news, because it means you can check for them in about fifteen minutes.</p>\n\n<p>Below is the list I actually walk through. Everything here you can run against your own domain with curl and browser devtools. No tooling required.</p>\n\n<h2>\n\n\n1. Is your <code>.env</code> reachable over HTTP?\n</h2>\n\n<p>The single most common catastrophic finding. It happens when the build output directory and the project root end up being the same thing.<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight shell\"><code>curl <span class=\"nt\">-sI</span> https://yourapp.com/.env | <span class=\"nb\">head</span> <span class=\"nt\">-1</span>\ncurl <span class=\"nt\">-sI</span> https://yourapp.com/.env.local | <span class=\"nb\">head</span> <span class=\"nt\">-1</span>\ncurl <span class=\"nt\">-sI</span> https://yourapp.com/.env.production | <span class=\"nb\">head</span> <span class=\"nt\">-1</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Anything other than <code>404</code> is an emergency. Rotate every key in that file before you do anything else — assume it's already been scraped, because bots hit these paths constantly.</p>\n\n<h2>\n\n\n2. Is your <code>.git</code> directory exposed?\n</h2>\n\n<p>Worse than <code>.env</code>, because it hands over your entire history including keys you <em>thought</em> you'd removed.<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight shell\"><code>curl <span class=\"nt\">-sI</span> https://yourapp.com/.git/HEAD | <span class=\"nb\">head</span> <span class=\"nt\">-1</span>\ncurl <span class=\"nt\">-s</span>https://yourapp.com/.git/config\n</code></pre>\n\n</div>\n\n\n\n<p>If <code>HEAD</code> returns 200, the whole repository is reconstructable by a stranger.</p>\n\n<h2>\n\n\n3. Which keys are sitting in your client bundle?\n</h2>\n\n<p>Open devtools, go to Sources, and search across all files. You're looking for <code>sk-</code>, <code>service_role</code>, <code>SECRET</code>, <code>PRIVATE_KEY</code>, and long strings starting with <code>eyJ</code> (those are JWTs).</p>\n\n<p>The nuance that trips people up: a Supabase <em>anon</em> key in the browser is fine by design. A <em>service_role</em> key is not — it bypasses row-level security entirely. AI assistants confuse the two constantly, because both are \"the Supabase key\" from the prompt's point of view.</p>\n\n<h2>\n\n\n4. Is row-level security actually on?\n</h2>\n\n<p>Having an anon key in the browser is only safe if RLS is enabled on every table. Default-off is the trap. Go through your tables one by one and confirm policies exist. \"I'll add policies later\" is how the 16% happens.</p>\n\n<h2>\n\n\n5. Security headers\n</h2>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight shell\"><code>curl <span class=\"nt\">-sI</span> https://yourapp.com | <span class=\"nb\">grep</span> <span class=\"nt\">-iE</span> <span class=\"s1\">'content-security-policy|strict-transport-security|x-frame-options|x-content-type-options|referrer-policy|permissions-policy'</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Most vibe-coded deploys return none of these. The two that matter most immediately are <code>Content-Security-Policy</code> (or at minimum <code>frame-ancestors</code>) to stop clickjacking, and <code>Strict-Transport-Security</code> so a downgrade attack can't strip your TLS.</p>\n\n<h2>\n\n\n6. Published source maps\n</h2>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight shell\"><code>curl <span class=\"nt\">-sI</span> https://yourapp.com/_next/static/chunks/main.js.map | <span class=\"nb\">head</span> <span class=\"nt\">-1</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Source maps in production hand attackers your original, readable source — comments, internal function names, dead code paths and all. Turn them off in your build config, or restrict them to authenticated access.</p>\n\n<h2>\n\n\n7. Debug leftovers and orphan routes\n</h2>\n\n<p>Grep your own bundle for <code>console.log</code> and check whether anything sensitive is being printed on page load. Then try the routes nobody meant to ship: <code>/api/debug</code>, <code>/api/test</code>, <code>/admin</code>, <code>/api/seed</code>. AI-generated scaffolding loves to leave these behind, unauthenticated.</p>\n\n<h2>\n\n\n8. Rate limiting on auth and API endpoints\n</h2>\n\n<p>Almost never present unless explicitly asked for. Without it, your login endpoint is a free credential-stuffing target and your LLM-backed API route is somebody else's free inference budget. Check whether your host gives you rate limiting at the edge — often it's a config flag you just haven't flipped.</p>\n\n<h2>\n\n\n9. Error messages that leak\n</h2>\n\n<p>Trigger a failure deliberately: malformed JSON to a POST endpoint, a bad ID in a path parameter. If you get back a stack trace, a file path, or an ORM error naming your tables and columns, that's free reconnaissance for anyone probing you.</p>\n\n<h2>\n\n\n10. Untouched boilerplate\n</h2>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight shell\"><code>curl <span class=\"nt\">-s</span> https://yourapp.com | <span class=\"nb\">grep</span> <span class=\"nt\">-iE</span> <span class=\"s1\">'&lt;title&gt;|og:image|og:description'</span>\n</code></pre>\n\n</div>\n\n\n\n<p>If it still says \"Create Next App\", or there's no Open Graph image, you're broadcasting that nobody reviewed this. It's not a vulnerability, but it changes how everything else about your product gets judged — including by the security researcher deciding whether you're worth poking at.</p>\n\n<h2>\n\n\n11. Trackers firing before consent\n</h2>\n\n<p>Open the Network tab, hard-reload, and watch what leaves the page before you've clicked anything. If Google Analytics, Meta Pixel or a session recorder fires on load, you have a consent problem in the EU — and a \"we didn't know it was there\" problem generally, because AI-generated templates ship with analytics snippets baked in.</p>\n\n<p>Related and easy to miss: Google Fonts loaded at runtime from Google's CDN transmits visitor IP addresses to a third country. A German court ruled on exactly this in 2022 and it kicked off a wave of warning letters. Self-host your fonts. It's faster anyway.</p>\n\n<h2>\n\n\n12. Imprint and privacy policy\n</h2>\n\n<p>If you have users in Germany or Austria, an imprint is a legal requirement, not a nice-to-have, and the privacy policy has to actually describe what you're collecting. This is the check that costs nothing and gets skipped the most, because it's boring and nobody's prompt asked for it.</p>\n\n\n\n\n<h2>\n\n\nThe fifteen-minute version\n</h2>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight shell\"><code><span class=\"nv\">DOMAIN</span><span class=\"o\">=</span><span class=\"s2\">\"https://yourapp.com\"</span>\n\n<span class=\"k\">for </span>path <span class=\"k\">in</span> /.env /.env.local /.env.production /.git/HEAD /.git/config <span class=\"se\">\\</span>\n/api/debug /api/test /admin<span class=\"p\">;</span> <span class=\"k\">do\n</span><span class=\"nv\">code</span><span class=\"o\">=</span><span class=\"si\">$(</span>curl <span class=\"nt\">-s</span> <span class=\"nt\">-o</span> /dev/null <span class=\"nt\">-w</span> <span class=\"s2\">\"%{http_code}\"</span> <span class=\"s2\">\"</span><span class=\"nv\">$DOMAIN$path</span><span class=\"s2\">\"</span><span class=\"si\">)</span>\n<span class=\"nb\">echo</span> <span class=\"s2\">\"</span><span class=\"nv\">$code</span><span class=\"s2\"></span><span class=\"nv\">$path</span><span class=\"s2\">\"</span>\n<span class=\"k\">done\n\n</span><span class=\"nb\">echo</span> <span class=\"s2\">\"--- headers ---\"</span>\ncurl <span class=\"nt\">-sI</span> <span class=\"s2\">\"</span><span class=\"nv\">$DOMAIN</span><span class=\"s2\">\"</span> | <span class=\"nb\">grep</span> <span class=\"nt\">-iE</span> <span class=\"s1\">'content-security-policy|strict-transport-security|x-frame-options|x-content-type-options|referrer-policy'</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Every <code>200</code> in that first block is a finding. Every missing header in the second block is a gap. Run it against your own domain only — this is a self-audit, not a scanner to point at other people's sites.</p>\n\n<h2>\n\n\nWhat to do with the results\n</h2>\n\n<p>Sort into three buckets and be honest about which one you're in.</p>\n\n<p>Exposed secrets, an open <code>.git</code>, or a service_role key in the bundle means <strong>stop</strong>. Rotate keys, fix, redeploy, and don't announce anything until it's clean.</p>\n\n<p>Missing headers, source maps, debug routes and boilerplate metadata mean <strong>iterate</strong> — real issues, fixable in an afternoon, not reasons to delay a soft launch to a small audience.</p>\n\n<p>Everything clean means <strong>go</strong>, with the caveat that this is a point-in-time snapshot. The next AI-generated feature can reintroduce any of it, so re-run before each meaningful deploy.</p>\n\n\n\n\n<p><em>Disclosure: I work at <a href=\"https://www.decivo.de\" rel=\"noopener noreferrer\">decivo</a>, where we do exactly this kind of review for teams shipping AI-built products. We wrapped the outside-in portion of this checklist into a free scan called <a href=\"https://www.decivo.de/de/rescue\" rel=\"noopener noreferrer\">Vibe Code Rescue</a> — you paste a URL, it runs the external checks and gives you a Go / Iterate / Stop verdict. No signup, no code access, nothing stored. The manual checklist above covers the same ground if you'd rather do it yourself, which is genuinely fine by me.</em></p>","content":"Getting an app to work has stopped being the hard part. You describe what you want, Lovable or Bolt or v0 builds it, and forty minutes later there's something on a real URL that real people can click.\nThe hard part moved. It's now everything between \"it works\" and \"it survives contact with the internet.\"\nThat gap isn't a vibe. It's measurable. Symbiotic Security crawled 65,643 URLs and fully scanned 1,072 Supabase-backed vibe-coded apps in June 2026: 98% had at least one security issue, 16% had something critical. A separate academic study by Deng et al. found that vibe-coded apps show recurring vulnerability patterns that differ from the ones traditional codebases produce — meaning these aren't random mistakes, they're structural. And an Xint.io analysis reported by SecurityWeek turned up 434 exploitable flaws concentrated in secrets exposure, broken authorization and denial of service.\nSame handful of failure modes, over and over. Which is good news, because it means you can check for them in about fifteen minutes.\nBelow is the list I actually walk through. Everything here you can run against your own domain with curl and browser devtools. No tooling required.\n1. Is your .env\nreachable over HTTP?\nThe single most common catastrophic finding. It happens when the build output directory and the project root end up being the same thing.\ncurl -sI https://yourapp.com/.env | head -1\ncurl -sI https://yourapp.com/.env.local | head -1\ncurl -sI https://yourapp.com/.env.production | head -1\nAnything other than 404\nis an emergency. Rotate every key in that file before you do anything else — assume it's already been scraped, because bots hit these paths constantly.\n2. Is your .git\ndirectory exposed?\nWorse than .env\n, because it hands over your entire history including keys you thought you'd removed.\ncurl -sI https://yourapp.com/.git/HEAD | head -1\ncurl -s https://yourapp.com/.git/config\nIf HEAD\nreturns 200, the whole repository is reconstructable by a stranger.\n3. Which keys are sitting in your client bundle?\nOpen devtools, go to Sources, and search across all files. You're looking for sk-\n, service_role\n, SECRET\n, PRIVATE_KEY\n, and long strings starting with eyJ\n(those are JWTs).\nThe nuance that trips people up: a Supabase anon key in the browser is fine by design. A service_role key is not — it bypasses row-level security entirely. AI assistants confuse the two constantly, because both are \"the Supabase key\" from the prompt's point of view.\n4. Is row-level security actually on?\nHaving an anon key in the browser is only safe if RLS is enabled on every table. Default-off is the trap. Go through your tables one by one and confirm policies exist. \"I'll add policies later\" is how the 16% happens.\n5. Security headers\ncurl -sI https://yourapp.com | grep -iE 'content-security-policy|strict-transport-security|x-frame-options|x-content-type-options|referrer-policy|permissions-policy'\nMost vibe-coded deploys return none of these. The two that matter most immediately are Content-Security-Policy\n(or at minimum frame-ancestors\n) to stop clickjacking, and Strict-Transport-Security\nso a downgrade attack can't strip your TLS.\n6. Published source maps\ncurl -sI https://yourapp.com/_next/static/chunks/main.js.map | head -1\nSource maps in production hand attackers your original, readable source — comments, internal function names, dead code paths and all. Turn them off in your build config, or restrict them to authenticated access.\n7. Debug leftovers and orphan routes\nGrep your own bundle for console.log\nand check whether anything sensitive is being printed on page load. Then try the routes nobody meant to ship: /api/debug\n, /api/test\n, /admin\n, /api/seed\n. AI-generated scaffolding loves to leave these behind, unauthenticated.\n8. Rate limiting on auth and API endpoints\nAlmost never present unless explicitly asked for. Without it, your login endpoint is a free credential-stuffing target and your LLM-backed API route is somebody else's free inference budget. Check whether your host gives you rate limiting at the edge — often it's a config flag you just haven't flipped.\n9. Error messages that leak\nTrigger a failure deliberately: malformed JSON to a POST endpoint, a bad ID in a path parameter. If you get back a stack trace, a file path, or an ORM error naming your tables and columns, that's free reconnaissance for anyone probing you.\n10. Untouched boilerplate\ncurl -s https://yourapp.com | grep -iE '<title>|og:image|og:description'\nIf it still says \"Create Next App\", or there's no Open Graph image, you're broadcasting that nobody reviewed this. It's not a vulnerability, but it changes how everything else about your product gets judged — including by the security researcher deciding whether you're worth poking at.\n11. Trackers firing before consent\nOpen the Network tab, hard-reload, and watch what leaves the page before you've clicked anything. If Google Analytics, Meta Pixel or a session recorder fires on load, you have a consent problem in the EU — and a \"we didn't know it was there\" problem generally, because AI-generated templates ship with analytics snippets baked in.\nRelated and easy to miss: Google Fonts loaded at runtime from Google's CDN transmits visitor IP addresses to a third country. A German court ruled on exactly this in 2022 and it kicked off a wave of warning letters. Self-host your fonts. It's faster anyway.\n12. Imprint and privacy policy\nIf you have users in Germany or Austria, an imprint is a legal requirement, not a nice-to-have, and the privacy policy has to actually describe what you're collecting. This is the check that costs nothing and gets skipped the most, because it's boring and nobody's prompt asked for it.\nThe fifteen-minute version\nDOMAIN=\"https://yourapp.com\"\nfor path in /.env /.env.local /.env.production /.git/HEAD /.git/config \\\n/api/debug /api/test /admin; do\ncode=$(curl -s -o /dev/null -w \"%{http_code}\" \"$DOMAIN$path\")\necho \"$code $path\"\ndone\necho \"--- headers ---\"\ncurl -sI \"$DOMAIN\" | grep -iE 'content-security-policy|strict-transport-security|x-frame-options|x-content-type-options|referrer-policy'\nEvery 200\nin that first block is a finding. Every missing header in the second block is a gap. Run it against your own domain only — this is a self-audit, not a scanner to point at other people's sites.\nWhat to do with the results\nSort into three buckets and be honest about which one you're in.\nExposed secrets, an open .git\n, or a service_role key in the bundle means stop. Rotate keys, fix, redeploy, and don't announce anything until it's clean.\nMissing headers, source maps, debug routes and boilerplate metadata mean iterate — real issues, fixable in an afternoon, not reasons to delay a soft launch to a small audience.\nEverything clean means go, with the caveat that this is a point-in-time snapshot. The next AI-generated feature can reintroduce any of it, so re-run before each meaningful deploy.\nDisclosure: I work at decivo, where we do exactly this kind of review for teams shipping AI-built products. We wrapped the outside-in portion of this checklist into a free scan called Vibe Code Rescue — you paste a URL, it runs the external checks and gives you a Go / Iterate / Stop verdict. No signup, no code access, nothing stored. The manual checklist above covers the same ground if you'd rather do it yourself, which is genuinely fine by me.\nTop comments (0)","published":"Sat, 25 Jul 2026 15:16:54 +0000","author":"Janni Hares","guid":"https://dev.to/decivo/12-things-to-check-before-you-ship-your-vibe-coded-app-n0p","created_at":"2026-07-25T17:27:41.996322","last_synchronized":"2026-07-25T17:27:41.996322","sentiment":{"sentiment":"Negative","score":-0.5339,"details":{"neg":0.058,"neu":0.889,"pos":0.054,"compound":-0.5339}}},{"feed_name":"DEV Community","feed_url":"https://dev.to/feed","title":"I Used the OpenAI SDK—and Claude Answered. Here’s Why.","link":"https://dev.to/aekanun/i-used-the-openai-sdk-and-claude-answered-heres-why-1l8o","description":"<p>You install the OpenAI Python package.</p>\n\n<p>You import <code>OpenAI</code>.</p>\n\n<p>You call <code>client.chat.completions.create()</code>.</p>\n\n<p>And <strong>Claude</strong> answers.<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight python\"><code><span class=\"kn\">import</span> <span class=\"n\">os</span>\n<span class=\"kn\">from</span> <span class=\"n\">openai</span> <span class=\"kn\">import</span> <span class=\"n\">OpenAI</span>\n\n<span class=\"n\">client</span> <span class=\"o\">=</span> <span class=\"nc\">OpenAI</span><span class=\"p\">(</span>\n<span class=\"n\">api_key</span><span class=\"o\">=</span><span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">[</span><span class=\"sh\">\"</span><span class=\"s\">ANTHROPIC_API_KEY</span><span class=\"sh\">\"</span><span class=\"p\">],</span>\n<span class=\"n\">base_url</span><span class=\"o\">=</span><span class=\"sh\">\"</span><span class=\"s\">https://api.anthropic.com/v1/</span><span class=\"sh\">\"</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">client</span><span class=\"p\">.</span><span class=\"n\">chat</span><span class=\"p\">.</span><span class=\"n\">completions</span><span class=\"p\">.</span><span class=\"nf\">create</span><span class=\"p\">(</span>\n<span class=\"n\">model</span><span class=\"o\">=</span><span class=\"sh\">\"</span><span class=\"s\">claude-sonnet-4-6</span><span class=\"sh\">\"</span><span class=\"p\">,</span>\n<span class=\"n\">messages</span><span class=\"o\">=</span><span class=\"p\">[</span>\n<span class=\"p\">{</span><span class=\"sh\">\"</span><span class=\"s\">role</span><span class=\"sh\">\"</span><span class=\"p\">:</span> <span class=\"sh\">\"</span><span class=\"s\">user</span><span class=\"sh\">\"</span><span class=\"p\">,</span> <span class=\"sh\">\"</span><span class=\"s\">content</span><span class=\"sh\">\"</span><span class=\"p\">:</span> <span class=\"sh\">\"</span><span class=\"s\">Explain this in one sentence.</span><span class=\"sh\">\"</span><span class=\"p\">}</span>\n<span class=\"p\">],</span>\n<span class=\"p\">)</span>\n\n<span class=\"nf\">print</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">choices</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">].</span><span class=\"n\">message</span><span class=\"p\">.</span><span class=\"n\">content</span><span class=\"p\">)</span>\n</code></pre>\n\n</div>\n\n\n\n<p>That looks wrong the first time you see it.</p>\n\n<p>If the SDK says <code>openai</code>, shouldn't an OpenAI model answer?</p>\n\n<p>No—and the reason matters far beyond this one code sample.</p>\n\n<h2>\n\n\nThe 10-second answer\n</h2>\n\n<p>Three independent choices are hiding in that snippet:</p>\n\n<ul>\n<li>\n<strong>The SDK</strong> decides how your application constructs requests and reads responses.</li>\n<li>\n<strong>The endpoint and API contract</strong> decide where the request goes and what shape crosses the network.</li>\n<li>\n<strong>The model identifier and routing rules</strong> decide what ultimately runs.</li>\n</ul>\n\n<p>The package name is not the model name.</p>\n\n<p>In the example above:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>OpenAI Python SDK\n↓ speaks OpenAI-style HTTP\nAnthropic compatibility endpoint\n↓ maps the request\nClaude Sonnet\n↓ result is mapped back\nOpenAI-shaped response\n↓ parsed by the SDK\nresponse.choices[0].message.content\n</code></pre>\n\n</div>\n\n\n\n<p>Anthropic officially provides this compatibility layer for testing and comparing Claude with existing OpenAI integrations. It is not the recommended production path for most Claude-first applications, for reasons we will get to shortly.</p>\n\n<p>But first, let us separate the layers developers accidentally compress into the word “AI.”</p>\n\n<h2>\n\n\nThe four layers hiding behind one API call\n</h2>\n\n<p>The useful mental model is not “SDK → model.” It is:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>┌───────────────────────────────────────────────┐\n│ 1. Application + SDK │\n│Builds a request and parses a response│\n├───────────────────────────────────────────────┤\n│ 2. API endpoint / gateway│\n│Auth, routing, policy, protocol mapping │\n├───────────────────────────────────────────────┤\n│ 3. Serving layer │\n│Scheduling, batching, streaming, metrics│\n├───────────────────────────────────────────────┤\n│ 4. Inference runtime + model│\n│Prompt → tokens → generation → tokens │\n└───────────────────────────────────────────────┘\n</code></pre>\n\n</div>\n\n\n\n<p>These layers may live in one program, several containers, or multiple companies' infrastructure. The boundaries are conceptual, but the responsibilities are different.</p>\n\n<h3>\n\n\n1. The SDK is a client, not a model\n</h3>\n\n<p>An SDK usually gives you:</p>\n\n<ul>\n<li>authentication and default headers;</li>\n<li>request and response types;</li>\n<li>serialization and validation;</li>\n<li>retries, timeouts, and error classes;</li>\n<li>streaming helpers;</li>\n<li>a nicer interface than raw HTTP.</li>\n</ul>\n\n<p>Without an SDK, the same job can be done with an HTTP client:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight python\"><code><span class=\"kn\">import</span> <span class=\"n\">os</span>\n<span class=\"kn\">import</span> <span class=\"n\">httpx</span>\n\n<span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">httpx</span><span class=\"p\">.</span><span class=\"nf\">post</span><span class=\"p\">(</span>\n<span class=\"sh\">\"</span><span class=\"s\">https://api.anthropic.com/v1/messages</span><span class=\"sh\">\"</span><span class=\"p\">,</span>\n<span class=\"n\">headers</span><span class=\"o\">=</span><span class=\"p\">{</span>\n<span class=\"sh\">\"</span><span class=\"s\">x-api-key</span><span class=\"sh\">\"</span><span class=\"p\">:</span> <span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">[</span><span class=\"sh\">\"</span><span class=\"s\">ANTHROPIC_API_KEY</span><span class=\"sh\">\"</span><span class=\"p\">],</span>\n<span class=\"sh\">\"</span><span class=\"s\">anthropic-version</span><span class=\"sh\">\"</span><span class=\"p\">:</span> <span class=\"sh\">\"</span><span class=\"s\">2023-06-01</span><span class=\"sh\">\"</span><span class=\"p\">,</span>\n<span class=\"sh\">\"</span><span class=\"s\">content-type</span><span class=\"sh\">\"</span><span class=\"p\">:</span> <span class=\"sh\">\"</span><span class=\"s\">application/json</span><span class=\"sh\">\"</span><span class=\"p\">,</span>\n<span class=\"p\">},</span>\n<span class=\"n\">json</span><span class=\"o\">=</span><span class=\"p\">{</span>\n<span class=\"sh\">\"</span><span class=\"s\">model</span><span class=\"sh\">\"</span><span class=\"p\">:</span> <span class=\"sh\">\"</span><span class=\"s\">claude-sonnet-4-6</span><span class=\"sh\">\"</span><span class=\"p\">,</span>\n<span class=\"sh\">\"</span><span class=\"s\">max_tokens</span><span class=\"sh\">\"</span><span class=\"p\">:</span> <span class=\"mi\">100</span><span class=\"p\">,</span>\n<span class=\"sh\">\"</span><span class=\"s\">messages</span><span class=\"sh\">\"</span><span class=\"p\">:</span> <span class=\"p\">[{</span><span class=\"sh\">\"</span><span class=\"s\">role</span><span class=\"sh\">\"</span><span class=\"p\">:</span> <span class=\"sh\">\"</span><span class=\"s\">user</span><span class=\"sh\">\"</span><span class=\"p\">,</span> <span class=\"sh\">\"</span><span class=\"s\">content</span><span class=\"sh\">\"</span><span class=\"p\">:</span> <span class=\"sh\">\"</span><span class=\"s\">Hello</span><span class=\"sh\">\"</span><span class=\"p\">}],</span>\n<span class=\"p\">},</span>\n<span class=\"p\">)</span>\n\n<span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">response</span><span class=\"p\">.</span><span class=\"nf\">json</span><span class=\"p\">()</span>\n<span class=\"nf\">print</span><span class=\"p\">(</span><span class=\"n\">data</span><span class=\"p\">[</span><span class=\"sh\">\"</span><span class=\"s\">content</span><span class=\"sh\">\"</span><span class=\"p\">][</span><span class=\"mi\">0</span><span class=\"p\">][</span><span class=\"sh\">\"</span><span class=\"s\">text</span><span class=\"sh\">\"</span><span class=\"p\">])</span>\n</code></pre>\n\n</div>\n\n\n\n<p>The native Anthropic response uses <code>content[]</code>. The OpenAI-style response uses <code>choices[]</code>.</p>\n\n<p>Those shapes are API contracts. Neither is the natural output format of a neural network.</p>\n\n<h3>\n\n\n2. The endpoint is more than a URL\n</h3>\n\n<p>Consider these clients:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight python\"><code><span class=\"c1\"># OpenAI\n</span><span class=\"nc\">OpenAI</span><span class=\"p\">(</span>\n<span class=\"n\">api_key</span><span class=\"o\">=</span><span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">[</span><span class=\"sh\">\"</span><span class=\"s\">OPENAI_API_KEY</span><span class=\"sh\">\"</span><span class=\"p\">],</span>\n<span class=\"n\">base_url</span><span class=\"o\">=</span><span class=\"sh\">\"</span><span class=\"s\">https://api.openai.com/v1</span><span class=\"sh\">\"</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"c1\"># Anthropic's OpenAI compatibility layer\n</span><span class=\"nc\">OpenAI</span><span class=\"p\">(</span>\n<span class=\"n\">api_key</span><span class=\"o\">=</span><span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">[</span><span class=\"sh\">\"</span><span class=\"s\">ANTHROPIC_API_KEY</span><span class=\"sh\">\"</span><span class=\"p\">],</span>\n<span class=\"n\">base_url</span><span class=\"o\">=</span><span class=\"sh\">\"</span><span class=\"s\">https://api.anthropic.com/v1/</span><span class=\"sh\">\"</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n<span class=\"c1\"># A local Ollama server\n</span><span class=\"nc\">OpenAI</span><span class=\"p\">(</span>\n<span class=\"n\">api_key</span><span class=\"o\">=</span><span class=\"sh\">\"</span><span class=\"s\">ollama</span><span class=\"sh\">\"</span><span class=\"p\">,</span><span class=\"c1\"># required by the client; ignored locally\n</span><span class=\"n\">base_url</span><span class=\"o\">=</span><span class=\"sh\">\"</span><span class=\"s\">http://localhost:11434/v1/</span><span class=\"sh\">\"</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n</code></pre>\n\n</div>\n\n\n\n<p>The calling style barely changes, but the request crosses three completely different trust, billing, latency, and data boundaries.</p>\n\n<p>The <code>base_url</code> can point to:</p>\n\n<ul>\n<li>the model provider itself;</li>\n<li>a multi-provider gateway such as OpenRouter or LiteLLM;</li>\n<li>a cloud proxy;</li>\n<li>an OpenAI-compatible server such as vLLM;</li>\n<li>a local runtime such as Ollama;</li>\n<li>your own internal policy and routing service.</li>\n</ul>\n\n<p>This is why “we use the OpenAI SDK” tells an architect almost nothing about where prompts go.</p>\n\n<p>The next questions should be:</p>\n\n<ol>\n<li>What is the <code>base_url</code>?</li>\n<li>Who controls that endpoint?</li>\n<li>Which model identifier is sent?</li>\n<li>Can the gateway rewrite or reroute it?</li>\n<li>Which protocol features survive the translation?</li>\n</ol>\n\n<h3>\n\n\n3. The serving layer creates the API-shaped response\n</h3>\n\n<p>At the lowest useful level, a language model works with numbers.</p>\n\n<p>The prompt is formatted and tokenized. The model produces logits. A generation strategy selects new token IDs. Those IDs are decoded into text.</p>\n\n<p>For example, Hugging Face Transformers documents that <code>generate()</code> returns token sequences—or a richer internal <code>ModelOutput</code> when requested:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight python\"><code><span class=\"n\">generated_ids</span> <span class=\"o\">=</span> <span class=\"n\">model</span><span class=\"p\">.</span><span class=\"nf\">generate</span><span class=\"p\">(</span><span class=\"o\">**</span><span class=\"n\">model_inputs</span><span class=\"p\">,</span> <span class=\"n\">max_new_tokens</span><span class=\"o\">=</span><span class=\"mi\">50</span><span class=\"p\">)</span>\n<span class=\"n\">text</span> <span class=\"o\">=</span> <span class=\"n\">tokenizer</span><span class=\"p\">.</span><span class=\"nf\">batch_decode</span><span class=\"p\">(</span>\n<span class=\"n\">generated_ids</span><span class=\"p\">,</span>\n<span class=\"n\">skip_special_tokens</span><span class=\"o\">=</span><span class=\"bp\">True</span><span class=\"p\">,</span>\n<span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n</code></pre>\n\n</div>\n\n\n\n<p>There is no universal neural-network law requiring the result to contain:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight json\"><code><span class=\"p\">{</span><span class=\"w\">\n</span><span class=\"nl\">\"choices\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p\">[],</span><span class=\"w\">\n</span><span class=\"nl\">\"finish_reason\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"stop\"</span><span class=\"p\">,</span><span class=\"w\">\n</span><span class=\"nl\">\"usage\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p\">{</span><span class=\"w\">\n</span><span class=\"nl\">\"prompt_tokens\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"mi\">10</span><span class=\"p\">,</span><span class=\"w\">\n</span><span class=\"nl\">\"completion_tokens\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"mi\">5</span><span class=\"w\">\n</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">}</span><span class=\"w\">\n</span></code></pre>\n\n</div>\n\n\n\n<p>That public JSON shape is assembled by software around the generation runtime.</p>\n\n<p>A serving system may also produce internal text, token IDs, finish metadata, timing information, cache statistics, and scheduler state. The important distinction is not “the engine only returns text.” The important distinction is:</p>\n\n<blockquote>\n<p><strong>Provider JSON is a network contract created by the serving/API layer—not an intrinsic property of the model weights.</strong></p>\n</blockquote>\n\n<p>Projects such as vLLM make this visible: the inference machinery returns internal request outputs, while the OpenAI-compatible server exposes endpoints with OpenAI-style schemas.</p>\n\n<h3>\n\n\n4. The model is selected downstream\n</h3>\n\n<p>The <code>model</code> field is a request to the service, not a Python import.</p>\n\n<p>The endpoint decides how to interpret it.<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight python\"><code><span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">client</span><span class=\"p\">.</span><span class=\"n\">chat</span><span class=\"p\">.</span><span class=\"n\">completions</span><span class=\"p\">.</span><span class=\"nf\">create</span><span class=\"p\">(</span>\n<span class=\"n\">model</span><span class=\"o\">=</span><span class=\"sh\">\"</span><span class=\"s\">anthropic/claude-sonnet-4.6</span><span class=\"sh\">\"</span><span class=\"p\">,</span>\n<span class=\"n\">messages</span><span class=\"o\">=</span><span class=\"p\">[{</span><span class=\"sh\">\"</span><span class=\"s\">role</span><span class=\"sh\">\"</span><span class=\"p\">:</span> <span class=\"sh\">\"</span><span class=\"s\">user</span><span class=\"sh\">\"</span><span class=\"p\">,</span> <span class=\"sh\">\"</span><span class=\"s\">content</span><span class=\"sh\">\"</span><span class=\"p\">:</span> <span class=\"sh\">\"</span><span class=\"s\">Hello</span><span class=\"sh\">\"</span><span class=\"p\">}],</span>\n<span class=\"p\">)</span>\n</code></pre>\n\n</div>\n\n\n\n<p>A gateway might:</p>\n\n<ul>\n<li>map that identifier to Anthropic;</li>\n<li>choose one of several upstream regions;</li>\n<li>fail over to another provider hosting the same open model;</li>\n<li>reject the identifier;</li>\n<li>apply an alias configured by your organization.</li>\n</ul>\n\n<p>So even the model string is not always the complete deployment identity. In production, log the resolved provider, model version, request ID, and routing decision whenever the platform exposes them.</p>\n\n<h2>\n\n\n“OpenAI-compatible” does not mean “identical”\n</h2>\n\n<p>This is where a convenient prototype becomes a quiet production bug.</p>\n\n<p>Two services can support <code>/v1/chat/completions</code> and still disagree on:</p>\n\n<ul>\n<li>accepted parameters;</li>\n<li>tool-call guarantees;</li>\n<li>multimodal content;</li>\n<li>structured output enforcement;</li>\n<li>streaming event details;</li>\n<li>token accounting;</li>\n<li>error payloads;</li>\n<li>provider-specific capabilities.</li>\n</ul>\n\n<p>Anthropic documents several concrete limitations in its OpenAI compatibility layer:</p>\n\n<ul>\n<li>\n<code>strict</code> for function calling is ignored;</li>\n<li>\n<code>response_format</code>, <code>logprobs</code>, and several other fields are ignored;</li>\n<li>prompt caching is not supported through this surface;</li>\n<li>system and developer messages are hoisted and combined;</li>\n<li>the full Claude feature set requires the native Claude API.</li>\n</ul>\n\n<p>Some unsupported fields are silently ignored.</p>\n\n<p>That last behavior is more dangerous than a clean error. Your code can compile, your request can return <code>200</code>, and your assumption can still be false.</p>\n\n<p>Ollama uses equally careful wording: it supports <strong>parts</strong> of the OpenAI API. vLLM documents its own list of supported and additional parameters.</p>\n\n<p>Compatibility is a spectrum, not a boolean.</p>\n\n<h2>\n\n\nThe migration that “only changes base_url”\n</h2>\n\n<p>Suppose this prototype works:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight python\"><code><span class=\"n\">client</span> <span class=\"o\">=</span> <span class=\"nc\">OpenAI</span><span class=\"p\">(</span>\n<span class=\"n\">api_key</span><span class=\"o\">=</span><span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">[</span><span class=\"sh\">\"</span><span class=\"s\">GATEWAY_API_KEY</span><span class=\"sh\">\"</span><span class=\"p\">],</span>\n<span class=\"n\">base_url</span><span class=\"o\">=</span><span class=\"n\">os</span><span class=\"p\">.</span><span class=\"n\">environ</span><span class=\"p\">[</span><span class=\"sh\">\"</span><span class=\"s\">LLM_BASE_URL</span><span class=\"sh\">\"</span><span class=\"p\">],</span>\n<span class=\"p\">)</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Changing an environment variable may be enough for basic chat completion.</p>\n\n<p>Then production adds:</p>\n\n<ul>\n<li>parallel tool calls;</li>\n<li>strict JSON schemas;</li>\n<li>images or PDFs;</li>\n<li>prompt caching;</li>\n<li>token-level streaming;</li>\n<li>provider-specific safety controls;</li>\n<li>detailed usage accounting.</li>\n</ul>\n\n<p>Now the lowest common denominator begins to cost you.</p>\n\n<p>The abstraction was not free. You deferred the translation work to a gateway—and accepted its fidelity limits.</p>\n\n<h2>\n\n\nWhich approach should you choose?\n</h2>\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>Situation</th>\n<th>Sensible default</th>\n<th>Main trade-off</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Claude-first production app</td>\n<td>Native Anthropic SDK</td>\n<td>Best Claude feature coverage; tighter vendor coupling</td>\n</tr>\n<tr>\n<td>OpenAI-first production app</td>\n<td>Native OpenAI SDK</td>\n<td>Best OpenAI feature coverage; tighter vendor coupling</td>\n</tr>\n<tr>\n<td>Model evaluation</td>\n<td>OpenAI-compatible surface or gateway</td>\n<td>Fast switching; feature comparisons may be incomplete</td>\n</tr>\n<tr>\n<td>Mostly portable text generation</td>\n<td>Common gateway contract</td>\n<td>Simple integration; lowest-common-denominator risk</td>\n</tr>\n<tr>\n<td>Heavy tools, streaming, or multimodal use</td>\n<td>Native provider adapters behind your own interface</td>\n<td>More code; explicit and testable behavior</td>\n</tr>\n<tr>\n<td>Local development</td>\n<td>Ollama or vLLM compatibility endpoint</td>\n<td>Convenient; confirm exactly which features are supported</td>\n</tr>\n</tbody>\n</table></div>\n\n<p>For a serious multi-provider application, I prefer a small internal interface whose implementation uses native provider SDKs.</p>\n\n<p>For example:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight python\"><code><span class=\"kn\">from</span> <span class=\"n\">typing</span> <span class=\"kn\">import</span> <span class=\"n\">Protocol</span>\n\n<span class=\"k\">class</span> <span class=\"nc\">TextModel</span><span class=\"p\">(</span><span class=\"n\">Protocol</span><span class=\"p\">):</span>\n<span class=\"k\">def</span> <span class=\"nf\">generate</span><span class=\"p\">(</span><span class=\"n\">self</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span><span class=\"p\">:</span> <span class=\"bp\">...</span>\n\n<span class=\"k\">class</span> <span class=\"nc\">ClaudeModel</span><span class=\"p\">:</span>\n<span class=\"k\">def</span> <span class=\"nf\">generate</span><span class=\"p\">(</span><span class=\"n\">self</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span><span class=\"p\">:</span>\n<span class=\"c1\"># Native Anthropic SDK implementation\n</span><span class=\"bp\">...</span>\n\n<span class=\"k\">class</span> <span class=\"nc\">OpenAIModel</span><span class=\"p\">:</span>\n<span class=\"k\">def</span> <span class=\"nf\">generate</span><span class=\"p\">(</span><span class=\"n\">self</span><span class=\"p\">,</span> <span class=\"n\">prompt</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span><span class=\"p\">:</span>\n<span class=\"c1\"># Native OpenAI SDK implementation\n</span><span class=\"bp\">...</span>\n</code></pre>\n\n</div>\n\n\n\n<p>This is more work than changing <code>base_url</code>, but it makes the lossy parts visible. Your domain code depends on your contract, while provider-specific capabilities remain available inside each adapter.</p>\n\n<p>If you choose a universal gateway instead, create contract tests for every feature you rely on:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>✓ plain text\n✓ streaming\n✓ tool call arguments\n✓ strict structured output\n✓ image input\n✓ stop reasons\n✓ usage accounting\n✓ retryable error classification\n</code></pre>\n\n</div>\n\n\n\n<p>Do not test only whether the first “Hello” request succeeds.</p>\n\n<h2>\n\n\nThe mental model to keep\n</h2>\n\n<p>When someone says:</p>\n\n<blockquote>\n<p>“We use the OpenAI SDK.”</p>\n</blockquote>\n\n<p>Translate it to:</p>\n\n<blockquote>\n<p>“This code uses a client designed around an OpenAI API shape.”</p>\n</blockquote>\n\n<p>It does <strong>not</strong> automatically mean:</p>\n\n<ul>\n<li>OpenAI hosts the endpoint;</li>\n<li>GPT generated the answer;</li>\n<li>every OpenAI feature is supported;</li>\n<li>prompts never pass through a gateway;</li>\n<li>switching providers is lossless.</li>\n</ul>\n\n<p>Remember the chain:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>SDK → API contract → endpoint/router → serving runtime → model\n</code></pre>\n\n</div>\n\n\n\n<p>The SDK speaks a protocol.</p>\n\n<p>The endpoint receives and may route the request.</p>\n\n<p>The serving stack runs—or delegates—the generation work.</p>\n\n<p>The model generates token probabilities.</p>\n\n<p>Once those responsibilities are separate in your head, “OpenAI SDK calling Claude” stops looking like magic. It becomes what it always was: one client speaking a compatible network contract to an endpoint that knows how to reach Claude.</p>\n\n\n\n\n<p>What broke first when you switched LLM providers: <strong>streaming, tool calls, structured output, or usage accounting?</strong></p>\n\n<p>Share the failure mode in the comments. Those edge cases are where “compatible” gets interesting.</p>\n\n<h2>\n\n\nReferences\n</h2>\n\n<ul>\n<li><a href=\"https://platform.claude.com/docs/en/cli-sdks-libraries/libraries/openai-sdk\" rel=\"noopener noreferrer\">Anthropic: OpenAI SDK compatibility</a></li>\n<li><a href=\"https://huggingface.co/docs/transformers/main_classes/text_generation\" rel=\"noopener noreferrer\">Hugging Face Transformers: Generation</a></li>\n<li><a href=\"https://docs.vllm.ai/en/latest/serving/openai_compatible_server.html\" rel=\"noopener noreferrer\">vLLM: OpenAI-Compatible Server</a></li>\n<li><a href=\"https://docs.ollama.com/api/openai-compatibility\" rel=\"noopener noreferrer\">Ollama: OpenAI compatibility</a></li>\n</ul>\n\n<p><em>Disclosure: This article is based on the author's original technical material and subject-matter knowledge. AI was used to help restructure the article, edit the English, and create the cover image. The technical claims and cited sources were reviewed before publication.</em></p>","content":"","published":"Sat, 25 Jul 2026 15:14:22 +0000","author":"www.aekanun.com","guid":"https://dev.to/aekanun/i-used-the-openai-sdk-and-claude-answered-heres-why-1l8o","created_at":"2026-07-25T17:27:41.996322","last_synchronized":"2026-07-25T17:27:41.996322","sentiment":{"sentiment":"Positive","score":0.2455,"details":{"neg":0.034,"neu":0.933,"pos":0.033,"compound":0.2455}}},{"feed_name":"DEV Community","feed_url":"https://dev.to/feed","title":"A Typosquat Package Almost Got My Keys: Dissecting the Attack Safely","link":"https://dev.to/pavelespitia/a-typosquat-package-almost-got-my-keys-dissecting-the-attack-safely-4pbn","description":"<p>A recruiter DM led to a \"take-home\" repo. Standard stuff, or so it looked. I cloned it, opened package.json, and one line stopped me: a dependency named clx-cookieparser. The real package is cookie-parser. That extra clx- prefix and the missing hyphen were the whole attack. I never installed it. Here is how I read it apart without running a single line, and the checklist that has saved me twice now.</p>\n\n<p>I do smart contract security, but the boring truth is most attacks against developers do not touch the chain at all. They touch your machine, your environment variables, and your wallet files. This one wanted all three.</p>\n\n<h2>\n\n\nThe bait\n</h2>\n\n<p>The repo was framed as a coding assessment for a \"client.\" Clean README, a couple of real features, tests that passed. The kind of thing you skim and trust because it looks like work, not like a trap. That framing is the point. You are in \"let me finish this task\" mode, not \"let me audit a stranger's code\" mode.</p>\n\n<p>The dependency list had mostly normal packages. Express, a test runner, a couple of utilities. And then clx-cookieparser, sitting in the middle like it belonged. Typosquatting works because your eyes autocorrect. You read \"cookie parser,\" your brain fills in the canonical name, and you move on.</p>\n\n<p>I did not move on, mostly out of habit. I keep npm configured so that installing is not a one-command reflex, which buys me time to look before anything executes.</p>\n\n<h2>\n\n\nWhy I could read it without running it\n</h2>\n\n<p>Two settings do the heavy lifting here, and I recommend both to everyone:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight shell\"><code><span class=\"c\"># never run install/postinstall scripts automatically</span>\nnpm config <span class=\"nb\">set </span>ignore-scripts <span class=\"nb\">true</span>\n\n<span class=\"c\"># with pnpm, add a cooldown so brand-new versions can't hit you instantly</span>\npnpm config <span class=\"nb\">set </span>minimumReleaseAge 1440 <span class=\"c\"># 24 hours</span>\n</code></pre>\n\n</div>\n\n\n\n<p><code>ignore-scripts</code> matters because the classic move is a <code>postinstall</code> hook that fires the moment you run install. Turn that off and cloning plus reading is safe, because nothing runs on its own. The cooldown matters because a lot of these malicious versions get yanked within hours of publication once someone reports them, so a 24-hour delay quietly dodges the freshest poison.</p>\n\n<p>So I read. Reading is not running. That distinction is the entire safety model.</p>\n\n<h2>\n\n\nWhat the payload was shaped like\n</h2>\n\n<p>I want to be careful here, so I am describing structure, not handing anyone a recipe. No real payload code.</p>\n\n<p>The package had two layers. The first layer, the code visible in the published tarball, was almost boring. It wrapped a real cookie-parsing function so the thing actually worked if you used it. That is camouflage. If the library breaks your app, you rip it out and the attacker loses. If it works, you keep it and stop looking.</p>\n\n<p>The interesting part was a second dependency the first package pulled in, and here is the tell that made my neck prickle: that second dependency was present in the resolved dependency tree but did not appear in the lockfile the repo shipped. In other words, the code referenced a package that the committed lockfile did not account for. A supply chain that does not reconcile with its own lockfile is lying to you about something.</p>\n\n<p>That second-stage package is where the real behavior lived. Reading its structure (not executing it), the intent was obvious from the surface it reached for:</p>\n\n<ul>\n<li>It looked for environment variables and process env, the place people stash API keys, tokens, and RPC URLs.</li>\n<li>It probed common wallet and keystore file locations in the home directory.</li>\n<li>It assembled that data and prepared to ship it outbound to a remote endpoint.</li>\n</ul>\n\n<p>Two-stage is a deliberate design. Stage one is quiet and passes a casual glance. Stage two carries the theft and hides behind an install-time or first-run trigger, one step removed from the package you actually named in your file. You have to follow the thread to see it.</p>\n\n<h2>\n\n\nThe signals that gave it away\n</h2>\n\n<p>I did not need a fancy tool for the first pass. I needed to read like the code was guilty until proven innocent. The signals, roughly in the order they hit me:</p>\n\n<ol>\n<li>The name. clx-cookieparser is not cookie-parser. Any prefix, swapped hyphen, or singular/plural flip on a popular package name is a five-alarm reason to stop.</li>\n<li>Lockfile mismatch. A dependency resolving in the tree that the committed lockfile does not describe means the manifest and reality disagree. Legitimate projects reconcile.</li>\n<li>Obfuscation and entropy. The second stage had chunks of high-entropy strings, the dense base64-looking blobs and hex that normal utility code just does not carry. Human-written cookie parsing is low entropy and readable. A wall of encoded bytes is a place to hide behavior from a reader.</li>\n<li>Reach that does not match purpose. A cookie parser has no business reading your home directory or touching env beyond what it is handed. Capability that exceeds the stated job is intent.</li>\n</ol>\n\n<p>For the entropy check you do not need to run anything either. You can eyeball it, or score strings statically. A rough sketch of the idea:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight python\"><code><span class=\"kn\">import</span> <span class=\"n\">math</span>\n<span class=\"kn\">from</span> <span class=\"n\">collections</span> <span class=\"kn\">import</span> <span class=\"n\">Counter</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">shannon_entropy</span><span class=\"p\">(</span><span class=\"n\">s</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">float</span><span class=\"p\">:</span>\n<span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">s</span><span class=\"p\">:</span>\n<span class=\"k\">return</span> <span class=\"mf\">0.0</span>\n<span class=\"n\">counts</span> <span class=\"o\">=</span> <span class=\"nc\">Counter</span><span class=\"p\">(</span><span class=\"n\">s</span><span class=\"p\">)</span>\n<span class=\"n\">n</span> <span class=\"o\">=</span> <span class=\"nf\">len</span><span class=\"p\">(</span><span class=\"n\">s</span><span class=\"p\">)</span>\n<span class=\"k\">return</span> <span class=\"o\">-</span><span class=\"nf\">sum</span><span class=\"p\">((</span><span class=\"n\">c</span> <span class=\"o\">/</span> <span class=\"n\">n</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"n\">math</span><span class=\"p\">.</span><span class=\"nf\">log2</span><span class=\"p\">(</span><span class=\"n\">c</span> <span class=\"o\">/</span> <span class=\"n\">n</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">c</span> <span class=\"ow\">in</span> <span class=\"n\">counts</span><span class=\"p\">.</span><span class=\"nf\">values</span><span class=\"p\">())</span>\n\n<span class=\"c1\"># high entropy long string literals in a \"utility\" package are a smell\n</span><span class=\"k\">for</span> <span class=\"n\">line</span> <span class=\"ow\">in</span> <span class=\"nf\">open</span><span class=\"p\">(</span><span class=\"sh\">\"</span><span class=\"s\">suspicious.js</span><span class=\"sh\">\"</span><span class=\"p\">):</span>\n<span class=\"k\">for</span> <span class=\"n\">token</span> <span class=\"ow\">in</span> <span class=\"n\">line</span><span class=\"p\">.</span><span class=\"nf\">split</span><span class=\"p\">(</span><span class=\"sh\">'\"'</span><span class=\"p\">):</span>\n<span class=\"k\">if</span> <span class=\"nf\">len</span><span class=\"p\">(</span><span class=\"n\">token</span><span class=\"p\">)</span> <span class=\"o\">&gt;</span> <span class=\"mi\">120</span> <span class=\"ow\">and</span> <span class=\"nf\">shannon_entropy</span><span class=\"p\">(</span><span class=\"n\">token</span><span class=\"p\">)</span> <span class=\"o\">&gt;</span> <span class=\"mf\">4.5</span><span class=\"p\">:</span>\n<span class=\"nf\">print</span><span class=\"p\">(</span><span class=\"sh\">\"</span><span class=\"s\">high-entropy blob:</span><span class=\"sh\">\"</span><span class=\"p\">,</span> <span class=\"n\">token</span><span class=\"p\">[:</span><span class=\"mi\">40</span><span class=\"p\">],</span> <span class=\"sh\">\"</span><span class=\"s\">...</span><span class=\"sh\">\"</span><span class=\"p\">)</span>\n</code></pre>\n\n</div>\n\n\n\n<p>That is reading, scoring, and flagging. Never executing.</p>\n\n<h2>\n\n\nThe checklist that saved me\n</h2>\n\n<p>This is the routine now, and it takes maybe five minutes on a fresh repo:</p>\n\n<ul>\n<li>Read package.json dependencies out loud. Any name that is close-but-not-exact to a popular package gets verified against the real registry name before anything else.</li>\n<li>Diff the dependency tree against the lockfile. Anything present in one and missing from the other is a stop sign.</li>\n<li>Never let install scripts run by default. <code>ignore-scripts true</code> globally, opt in per project only when you trust it.</li>\n<li>Grep for high-entropy string literals and unexpected network or filesystem calls in dependencies before install, not after.</li>\n<li>Assume any repo from an unsolicited recruiter is hostile until proven otherwise. The social framing is part of the exploit.</li>\n</ul>\n\n<p>I ended up feeding the repo through Argus Lens, the static repo scanner I built (lens.noctis.biz) partly because I got tired of doing this by hand. It flags build-time execution, lockfile-missing dependencies, and obfuscation without cloning or installing. But the tool just automates the checklist above. The checklist is the actual defense, and you can run it with your eyes.</p>\n\n<p>I did not lose anything this time. The uncomfortable part is how close normal, get-the-task-done behavior came to running it. One <code>npm install</code> on autopilot and I would have shipped my env vars to someone.</p>\n\n<p>What is your default posture when a stranger sends you a repo to run: trust and clone, or hostile until proven safe?</p>","content":"","published":"Sat, 25 Jul 2026 15:12:20 +0000","author":"Pavel Espitia","guid":"https://dev.to/pavelespitia/a-typosquat-package-almost-got-my-keys-dissecting-the-attack-safely-4pbn","created_at":"2026-07-25T17:27:41.996322","last_synchronized":"2026-07-25T17:27:41.996322","sentiment":{"sentiment":"Positive","score":0.9651,"details":{"neg":0.055,"neu":0.873,"pos":0.072,"compound":0.9651}}},{"feed_name":"DEV Community","feed_url":"https://dev.to/feed","title":"How to Track What Hedge Funds Are Buying — Directly From Your AI Agent","link":"https://dev.to/james-alphasmo/how-to-track-what-hedge-funds-are-buying-directly-from-your-ai-agent-2jij","description":"<h2>\n\n\nWhat if your AI agent could track what Warren Buffett is buying — in real time?\n</h2>\n\n<p>That is exactly what the <strong>AlphaSMO MCP server</strong> unlocks.</p>\n\n<p>You have probably heard about the <strong>Model Context Protocol (MCP)</strong> — the open standard that lets AI agents like Claude, ChatGPT, and Cursor talk to external tools and APIs. Instead of hallucinating answers or being stuck with stale training data, an MCP-equipped agent can reach out to live data sources and pull back real, structured information mid-conversation.</p>\n\n<p>But most MCP servers today are built for developer tools — file systems, databases, GitHub, Postgres. Few bring <strong>real-world financial intelligence</strong> to your AI agent.</p>\n\n<p>That is where AlphaSMO comes in.</p>\n\n\n\n\n<h2>\n\n\nWhat is AlphaSMO?\n</h2>\n\n<p><a href=\"https://alphasmo.com\" rel=\"noopener noreferrer\">AlphaSMO</a> is a platform that processes <strong>SEC 13F filings</strong> — the quarterly reports that every hedge fund, mutual fund, and institutional investor managing over $100M must file with the SEC — and turns them into clean, queryable data.</p>\n\n<p>Think of it as <em>\"Bloomberg Terminal data, but for AI agents\"</em>.</p>\n\n<p>The AlphaSMO MCP server exposes <strong>7 tools</strong> that your AI agent can call directly:</p>\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>Tool</th>\n<th>What It Does</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>search_institutions</code></td>\n<td>Search 5,000+ institutional investors by name (e.g. \"Berkshire\", \"Bridgewater\")</td>\n</tr>\n<tr>\n<td><code>get_institution_profile</code></td>\n<td>Get an institution's AUM, holding count, and personality scores (concentration, turnover, sector conviction)</td>\n</tr>\n<tr>\n<td><code>get_institution_holdings</code></td>\n<td>See exactly which stocks a fund holds — ticker, value, weight, and buy/sell action vs last quarter</td>\n</tr>\n<tr>\n<td><code>get_stock_overview</code></td>\n<td>Full institutional ownership picture for any stock: holder count, net flow, top holders by value and conviction</td>\n</tr>\n<tr>\n<td><code>get_stock_flows</code></td>\n<td>Stocks ranked by net institutional buy or sell flow in the latest quarter</td>\n</tr>\n<tr>\n<td><code>get_insider_activity</code></td>\n<td>Form 4 insider trading: what officers, directors, and 10% owners are doing with their own shares</td>\n</tr>\n<tr>\n<td><code>get_smart_money_convergence</code></td>\n<td>🚀 <strong>The flagship signal</strong>: tickers where 13F institutions AND company insiders are buying at the same time</td>\n</tr>\n</tbody>\n</table></div>\n\n\n\n\n<h2>\n\n\nWhy 13F Data Matters for AI Agents\n</h2>\n\n<p>Every quarter, over 5,000 institutional investors managing <strong>$45+ trillion in assets</strong> must disclose their holdings to the SEC. This is not speculation or rumor — it is legally mandated transparency.</p>\n\n<p>The catch? Raw 13F filings are text-based, CUSIP-coded, and virtually unreadable without processing. That is why most retail investors never touch them.</p>\n\n<p>AlphaSMO does the heavy lifting:</p>\n\n<ul>\n<li>Resolves cryptic CUSIP identifiers to real ticker symbols</li>\n<li>Calculates net institutional flow (who is buying vs selling)</li>\n<li>Computes behavioral \"personality scores\" for each institution (concentration, turnover, sector conviction)</li>\n<li>Cross-references 13F filings with Form 4 insider transactions</li>\n</ul>\n\n<p>And now, through the MCP server, your AI agent gets all of this <strong>in a single function call</strong>.</p>\n\n\n\n\n<h2>\n\n\nThe Secret Weapon: Smart Money Convergence\n</h2>\n\n<p>Most financial data APIs give you one signal at a time — either institutional flows <em>or</em> insider trading. AlphaSMO is the only MCP server that <strong>combines both into a single convergence signal</strong>.</p>\n\n<p><code>get_smart_money_convergence</code> returns tickers where:</p>\n\n<ol>\n<li>✅ Multiple 13F institutions are increasing their positions, <strong>AND</strong>\n</li>\n<li>✅ Company insiders are buying their own stock on the open market</li>\n</ol>\n\n<p>When the people who manage billions <em>and</em> the people who run the company are both buying — that is a signal worth paying attention to.<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight json\"><code><span class=\"p\">{</span><span class=\"w\">\n</span><span class=\"nl\">\"ticker\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"NVDA\"</span><span class=\"p\">,</span><span class=\"w\">\n</span><span class=\"nl\">\"issuer\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"NVIDIA CORP\"</span><span class=\"p\">,</span><span class=\"w\">\n</span><span class=\"nl\">\"institutional_buy_score\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"mi\">92</span><span class=\"p\">,</span><span class=\"w\">\n</span><span class=\"nl\">\"insider_confidence\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"mi\">85</span><span class=\"p\">,</span><span class=\"w\">\n</span><span class=\"nl\">\"convergence_signal\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"STRONG_BUY\"</span><span class=\"w\">\n</span><span class=\"p\">}</span><span class=\"w\">\n</span></code></pre>\n\n</div>\n\n\n\n<p>Read more about the convergence methodology <a href=\"https://alphasmo.com/tw/smart-money\" rel=\"noopener noreferrer\">on the AlphaSMO website</a>.</p>\n\n\n\n\n<h2>\n\n\nOne Command to Get Started\n</h2>\n\n<p>No signup. No API key. No credit card. Just run:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight shell\"><code>npx alphasmo convergence <span class=\"nt\">--limit</span> 5\n</code></pre>\n\n</div>\n\n\n\n<p>This prints a table of the top tickers where smart money is converging <em>right now</em>. Here is what real output looks like:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>TickerIssuerNet Flow (USD) Confidence\nBRK-A BERKSHIRE HATHAWAY INC-CL A247,455,256,37994\nNVDANVIDIA CORP 52,103,888,15592\nMETAMETA PLATFORMS INC38,033,517,69690\nAMZNAMAZON COM INC35,611,305,94588\nAVGOBROADCOM INC31,209,512,39587\n</code></pre>\n\n</div>\n\n\n\n<p>Your AI agent sees the EXACT same structured data.</p>\n\n\n\n\n<h2>\n\n\nWiring It Into Your AI Agent\n</h2>\n\n<h3>\n\n\nClaude Code (recommended)\n</h3>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight shell\"><code>claude mcp add alphasmo <span class=\"nt\">--</span> npx <span class=\"nt\">-y</span> alphasmo@latest mcp\n</code></pre>\n\n</div>\n\n\n\n<h3>\n\n\nClaude Desktop / Cursor / Any MCP Client\n</h3>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight json\"><code><span class=\"p\">{</span><span class=\"w\">\n</span><span class=\"nl\">\"mcpServers\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p\">{</span><span class=\"w\">\n</span><span class=\"nl\">\"alphasmo\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p\">{</span><span class=\"w\">\n</span><span class=\"nl\">\"command\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"npx\"</span><span class=\"p\">,</span><span class=\"w\">\n</span><span class=\"nl\">\"args\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p\">[</span><span class=\"s2\">\"alphasmo\"</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"s2\">\"mcp\"</span><span class=\"p\">],</span><span class=\"w\">\n</span><span class=\"nl\">\"env\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p\">{</span><span class=\"w\">\n</span><span class=\"nl\">\"ALPHASMO_API_KEY\"</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s2\">\"your-free-key-here\"</span><span class=\"w\">\n</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">}</span><span class=\"w\">\n</span><span class=\"p\">}</span><span class=\"w\">\n</span></code></pre>\n\n</div>\n\n\n\n<h3>\n\n\nProgrammatic Use (TypeScript)\n</h3>\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight typescript\"><code><span class=\"k\">import</span> <span class=\"p\">{</span> <span class=\"nx\">AlphasmoClient</span> <span class=\"p\">}</span> <span class=\"k\">from</span> <span class=\"dl\">\"</span><span class=\"s2\">alphasmo</span><span class=\"dl\">\"</span><span class=\"p\">;</span>\n\n<span class=\"kd\">const</span> <span class=\"nx\">client</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"nc\">AlphasmoClient</span><span class=\"p\">();</span>\n<span class=\"kd\">const</span> <span class=\"nx\">convergence</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">client</span><span class=\"p\">.</span><span class=\"nf\">getSmartMoneyConvergence</span><span class=\"p\">({</span> <span class=\"na\">limit</span><span class=\"p\">:</span> <span class=\"mi\">5</span> <span class=\"p\">});</span>\n<span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nf\">log</span><span class=\"p\">(</span><span class=\"nx\">convergence</span><span class=\"p\">);</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Python client also available — see <a href=\"https://alphasmo.com/developer\" rel=\"noopener noreferrer\">alphasmo.com/developer</a>.</p>\n\n\n\n\n<h2>\n\n\nReal-World Use Cases\n</h2>\n\n<p>Here is what you can ask your AI agent once the AlphaSMO MCP server is connected:</p>\n\n<blockquote>\n<p><em>\"What stocks are institutions buying the most right now?\"</em></p>\n\n<p><em>\"Show me Berkshire Hathaway's latest 13F holdings, sorted by position size.\"</em></p>\n\n<p><em>\"Are insiders buying or selling NVDA? Give me the confidence score.\"</em></p>\n\n<p><em>\"Find tickers where BOTH hedge funds and company executives are accumulating.\"</em></p>\n\n<p><em>\"Which sectors is Bridgewater overweight in this quarter?\"</em></p>\n</blockquote>\n\n<p>Each question triggers a real API call behind the scenes — no hallucinated answers, no stale training data.</p>\n\n\n\n\n<h2>\n\n\nWhy AlphaSMO Is Unique\n</h2>\n\n<p>There are plenty of financial data APIs. There are plenty of MCP servers. But AlphaSMO is the <strong>only MCP server that bridges institutional 13F data and insider trading into a unified, AI-ready interface</strong>.</p>\n\n<p>Key differentiators:</p>\n\n<ul>\n<li>🆓 <strong>Free tier with no signup</strong> — run <code>npx alphasmo</code> right now, no account needed</li>\n<li>📊 <strong>13F + Form 4 combined</strong> — two independent signal sources, one API</li>\n<li>🤖 <strong>Built for MCP first</strong> — not a REST API with an MCP wrapper bolted on; the MCP tools are first-class</li>\n<li>🎯 <strong>Personality scores</strong> — know if an institution is a concentrated conviction investor or an index-hugging closet tracker</li>\n<li>📦 <strong>One dependency</strong> — <code>npx alphasmo mcp</code>. No database to run, no data to ingest</li>\n</ul>\n\n\n\n\n<h2>\n\n\nWhat is Under the Hood\n</h2>\n\n<p>The AlphaSMO MCP server is open source (MIT license):</p>\n\n<ul>\n<li>📂 GitHub: <a href=\"https://github.com/alphasmo/alphasmo-tools\" rel=\"noopener noreferrer\">github.com/alphasmo/alphasmo-tools</a>\n</li>\n<li>📦 npm: <a href=\"https://www.npmjs.com/package/alphasmo\" rel=\"noopener noreferrer\">npmjs.com/package/alphasmo</a>\n</li>\n<li>🌐 Platform: <a href=\"https://alphasmo.com\" rel=\"noopener noreferrer\">alphasmo.com</a>\n</li>\n<li>📖 API Docs: <a href=\"https://alphasmo.com/developer/docs\" rel=\"noopener noreferrer\">alphasmo.com/developer/docs</a>\n</li>\n</ul>\n\n<p>Built with the official <code>@modelcontextprotocol/sdk</code>, Zod schema validation, and structured content support for both TypeScript and Python MCP clients.</p>\n\n\n\n\n<h2>\n\n\nGet Your Free API Key\n</h2>\n\n<p>Anonymous access works out of the box (rate-limited by IP). To raise your limit, grab a free key at <strong><a href=\"https://alphasmo.com/developer\" rel=\"noopener noreferrer\">alphasmo.com/developer</a></strong>.</p>\n\n<p>Then set it and forget it:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight shell\"><code><span class=\"nb\">export </span><span class=\"nv\">ALPHASMO_API_KEY</span><span class=\"o\">=</span>ask_live_...\n</code></pre>\n\n</div>\n\n\n\n\n\n\n<h2>\n\n\nThe Bottom Line\n</h2>\n\n<p>AI agents are only as good as the data they can access. MCP is the protocol that connects them to the real world. And AlphaSMO is the MCP server that connects them to <strong>the $45 trillion world of institutional money flows</strong>.</p>\n\n<p>Stop asking your AI to guess what smart money is doing. Give it the tools to know.<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight shell\"><code>npx alphasmo convergence <span class=\"nt\">--limit</span> 10\n</code></pre>\n\n</div>\n\n\n\n\n\n\n<p><em>Disclaimer: This article is for informational and educational purposes only. It does not constitute investment advice. Past institutional behavior does not guarantee future results. 13F data has a 45-day filing delay. Always do your own research.</em></p>","content":"","published":"Sat, 25 Jul 2026 15:07:18 +0000","author":"James from AlphaSMO","guid":"https://dev.to/james-alphasmo/how-to-track-what-hedge-funds-are-buying-directly-from-your-ai-agent-2jij","created_at":"2026-07-25T17:27:41.996322","last_synchronized":"2026-07-25T17:27:41.996322","sentiment":{"sentiment":"Positive","score":0.9865,"details":{"neg":0.027,"neu":0.924,"pos":0.049,"compound":0.9865}}},{"feed_name":"DEV Community","feed_url":"https://dev.to/feed","title":"ENS Shows Why Reputation Systems Are Harder Than Scores","link":"https://dev.to/antfarm-official/ens-shows-why-reputation-systems-are-harder-than-scores-1fl6","description":"<p>Most reputation systems start with a simple idea: take someone’s public activity, turn it into a number, and make that number useful. It sounds clean until you try to build it. A wallet is not a person. A name is not always a stable identity. A GitHub account, a deployment address, a DAO vote, and an ENS name may all point to the same developer, or they may point to five different people. The hard part is not creating a score. The hard part is proving what the score actually refers to.</p>\n\n<p>ENS is interesting here because it gives developers something Web3 badly needs: a human-readable anchor. Instead of asking people to trust a random address, we can start with a name, a resolver, a history of records, and the addresses attached to that name over time. That does not solve reputation by itself, but it gives the system a more honest starting point. A reputation layer should not pretend that every address is equal. Some addresses are disposable. Some are long-lived. Some are operational wallets. Some are social identities. ENS helps separate those cases, but only if the system treats it as evidence, not as proof.</p>\n\n<p>The first real technical problem is provenance. If a developer claims a contract deployment, a pull request, a DAO proposal, or an audit contribution, the system needs to understand where that evidence came from and how it was verified. Was the contract deployed by an address currently linked to the ENS name? Was it linked at the time of deployment? Did the developer sign a message? Was there an attestation from another party? Did the claim come from an indexer, a GitHub integration, or manual input? Without provenance, a reputation system becomes a collection of screenshots with a nicer UI.</p>\n\n<p>The second problem is change over time. People rotate wallets. ENS records get updated. Teams share deployer accounts. Contributors leave projects. Smart contracts get upgraded. A good system cannot just read the current state and act like it explains the past. It needs snapshots, timestamps, and a clear model for historical ownership. If vitalik.eth pointed to one address today and another address two years ago, reputation logic should not flatten that into a single truth. Time is part of the data model. Ignoring it creates false credit, missing credit, and sometimes very convincing fraud.</p>\n\n<p>Then there is scoring, which is usually where systems become weak. A score is only useful if someone can understand why it changed. Counting deployments is easy, but meaningless without context. Deploying ten toy contracts is not the same as maintaining one protocol that holds real value. DAO participation can be thoughtful, spammy, delegated, or purely symbolic. GitHub commits can be meaningful, automated, or cosmetic. ENS can help connect signals, but it cannot decide their weight. The scoring layer needs explainability, confidence levels, decay, dispute handling, and a way to show uncertainty without hiding behind a clean number.</p>\n\n<p>The better approach is to treat reputation as a graph of claims, not a single profile badge. ENS can be one node. Wallets, contracts, repositories, attestations, audits, governance activity, and project roles can be other nodes. Each edge should say what connects two things, when that connection was observed, and how strong the evidence is. That makes the system more boring to build, but much harder to fake. In the end, a useful developer reputation system is not about ranking people loudly. It is about making technical history easier to verify, easier to question, and harder to rewrite.</p>","content":"","published":"Sat, 25 Jul 2026 15:06:59 +0000","author":"Hiren Kava","guid":"https://dev.to/antfarm-official/ens-shows-why-reputation-systems-are-harder-than-scores-1fl6","created_at":"2026-07-25T17:27:41.996322","last_synchronized":"2026-07-25T17:27:41.996322","sentiment":{"sentiment":"Positive","score":0.997,"details":{"neg":0.07,"neu":0.761,"pos":0.17,"compound":0.997}}},{"feed_name":"DEV Community","feed_url":"https://dev.to/feed","title":"ALKA lazer silahi ekonomisi: yonlendirilmis enerji savunmayi neden ucuzlatiyor","link":"https://dev.to/tolgatascimuhendislik/alka-lazer-silahi-ekonomisi-yonlendirilmis-enerji-savunmayi-neden-ucuzlatiyor-58d7","description":"<p>ALKA lazer silahı, savunmanın en sessiz ama en belirleyici tarafını değiştiriyor: maliyet eğrisini. Bin liralık bir drone'a milyon liralık bir füzeyle karşılık verildiğinde, matematiksel olarak ateş eden taraf kaybeder. ROKETSAN'ın yönlendirilmiş enerji silahı ALKA, atış maliyetini pratikte elektriğe indirerek bu denklemi tersine çevirir.</p>\n\n<p>\n\n</p>\n\n<h2>\n\n\nALKA maliyet eğrisini neden tersine çeviriyor?\n</h2>\n\n<p>Klasik hava savunmasında her atışın somut bir bedeli vardır: füze üretilir, stoklanır, ateşlenince tükenir. Sorun, hedefin bu bedelle orantısız biçimde ucuz olmasıdır. Ucuz ve çok sayıda üretilen mini drone'lar, savunmayı her karşılıkta biraz daha yorar. On drone, on pahalı füze demektir; yüz drone, yüz füze. Saldıran taraf ucuzu çoğaltarak pahalıyı tüketir.</p>\n\n<p>ALKA bu mantığı kırar. Bir yönlendirilmiş enerji silahı olarak fiziksel bir mühimmat harcamaz; ışığı hedefe yollar. Bir atışın maliyeti, o atışı üreten elektrik enerjisine iner. Böylece savunan taraf, saldıranın ölçek avantajını nötrler: hedef ne kadar ucuz olursa olsun, karşılık da o kadar ucuza verilebilir.</p>\n\n<h2>\n\n\nALKA hedefi mermisiz nasıl vuruyor?\n</h2>\n\n<p>Sistem iki katman hâlinde çalışır ve bu katmanlaşma maliyet mantığının doğal uzantısıdır. İlk katman elektromanyetik karıştırmadır: drone ile operatör arasındaki bağlantıyı keser. Tehditlerin büyük bölümü tek bir fiziksel hasar bile gerekmeden, en ucuz katmanda etkisiz kalır. Bağını kaybeden drone görevini sürdüremez.</p>\n\n<p>İkinci katman devreye ancak gerektiğinde girer: yoğunlaştırılmış lazer. Işın, hedefin üzerinde madeni para büyüklüğünde bir noktaya odaklanır ve o noktayı saniyeler içinde eritir. Ucuz karıştırmanın yetmediği durumlar için ayrılmış, daha keskin ama yine mühimmatsız bir yanıttır. Sistem mimarisi bu haliyle, <a href=\"https://www.youtube.com/watch?v=9AEaDq4PSqo\" rel=\"noopener noreferrer\">yönlendirilmiş enerji silahı</a> kavramının maliyet tarafını somutlaştırır.</p>\n\n<p>Bu katmanlı yapının ekonomik zekası, en pahalı yanıtı en sona saklamasıdır. Karıştırma katmanı hiçbir mühimmat gerektirmez ve birçok tehdidi en ucuz noktada bitirdiği için, yüksek güçlü lazer yalnızca gerçekten gereken hedeflere ayrılır. Böylece sistem, gelen tehdit hangi düzeydeyse ona denk maliyette bir karşılık üretir; her hedefe aynı pahalı yanıtı vermek zorunda kalmaz. Drone sürüsü gibi çok sayıda ve eşzamanlı tehditte bu ölçeklenebilirlik, savunmanın nefes almasını sağlayan asıl unsurdur.</p>\n\n<h2>\n\n\nALKA'nın gücü ve menzili ölçeğe nasıl bağlanıyor?\n</h2>\n\n<p>Maliyet avantajı, gücün kademelenmesiyle birlikte anlam kazanır. ALKA 2,5 kW ile yaklaşık 750 metre, 5 kW ile yaklaşık 1.500 metre menzile ulaşır; bugün güç 10 kW'ın üzerine, ilk sürüme göre yaklaşık dört kat seviyeye çıkmıştır. Daha yüksek güç, daha uzaktaki bir hedefe erişmek ya da aynı hedefi daha kısa sürede devre dışı bırakmak demektir. Kritik olan şu: güç ve menzil büyürken birim atış maliyeti düşük kalır, çünkü tüketilen şey hâlâ mühimmat değil elektriktir. Ölçek büyüdükçe pahalılaşan füze mantığının tersine, burada kapasite artışı maliyet eğrisini yukarı itmez. Sistem 2023'ten bu yana seri üretimdedir ve Türk Silahlı Kuvvetleri'nin tek enerji silahıdır; ilk örneği 2019'da görülmüştür. Öncelikli hedefi mini ve mikro İHA'lar ile drone sürüleridir.</p>\n\n<h2>\n\n\nALKA'nın sınırları maliyet denklemini nasıl değiştiriyor?\n</h2>\n\n<p>Hiçbir sistem bedava değildir; ALKA'nın bedeli de fiziktedir. Asıl mühendislik zorluğu gücü üretmek değil, o gücü kilometrelerce uzaktaki hızlı bir hedefin aynı küçük noktasında sabit tutmaktır. Bunu hassas takip, titreşim yalıtımı ve adaptif optik birlikte sağlar; ışın hedefte birkaç santimetre kaysa aktarılan enerji dağılır ve eritme etkisi düşer. İkinci kısıt atmosferdir: yağmur, sis ve toz ışını saçarak menzili düşürür. Bu iki kısıt, düşük atış maliyetinin bir \"her koşulda geçerli üstünlük\" olmadığını hatırlatır; avantaj koşullara bağlıdır.</p>\n\n<p>Bu yüzden ALKA klasik hava savunmanın yerini almaz, onu tamamlar. Doğru okuma, sistemi tek başına bir çözüm değil, maliyet eğrisinin en yorucu ucunu — ucuz ve çok sayıda drone tehdidini — üstlenen bir katman olarak görmektir. Pahalı füzeler, gerçekten pahalı hedeflere ayrılır; ucuz tehditlere ucuz karşılık verilir. Maliyet avantajı, sistemin doğru katmanda ve uygun koşulda kullanılmasıyla gerçeğe döner.</p>\n\n<h2>\n\n\nSıkça sorulan sorular\n</h2>\n\n<p><strong>ALKA neden füzeye göre daha ucuz?</strong><br />\nFüze her atışta tükenen fiziksel bir mühimmattır. ALKA ise ışık yollar; bir atışın maliyeti pratikte elektrik enerjisidir. Ucuz ve çok sayıdaki hedeflere karşı bu fark birikimli avantaja dönüşür.</p>\n\n<p><strong>ALKA hangi tehditlere karşı tasarlandı?</strong><br />\nÖncelikle mini ve mikro İHA'lar ile drone sürülerine karşı. Bu tehditler ucuz, çok sayıda ve hızlı oldukları için orantısız maliyet baskısı yaratır.</p>\n\n<p><strong>ALKA klasik hava savunmanın yerini alır mı?</strong><br />\nHayır. Özellikle küçük ve ucuz hava tehditlerine karşı maliyet-etkin bir katman ekler; mevcut sistemleri tamamlar.</p>\n\n<p><strong>ALKA'nın en büyük kısıtı nedir?</strong><br />\nAtmosfer. Yağmur, sis ve toz, lazerin etkili menzilini düşürür. Ayrıca ışını hızlı hedefte sabit tutmak yüksek hassasiyet gerektirir.</p>\n\n<p><strong>ALKA seri üretimde mi?</strong><br />\nEvet, 2023'ten bu yana. Türk Silahlı Kuvvetleri'nin tek enerji silahı olarak tanımlanır; ilk örneği 2019'da ortaya çıkmıştır.</p>\n\n\n\n\n<p>Kaynaklar: ROKETSAN ürün sayfası, Anadolu Ajansı, savunma basını ve açık kaynak literatür.</p>","content":"","published":"Sat, 25 Jul 2026 15:02:39 +0000","author":"Tolga Taşçı | Mühendislik","guid":"https://dev.to/tolgatascimuhendislik/alka-lazer-silahi-ekonomisi-yonlendirilmis-enerji-savunmayi-neden-ucuzlatiyor-58d7","created_at":"2026-07-25T17:27:41.996322","last_synchronized":"2026-07-25T17:27:41.996322","sentiment":{"sentiment":"Neutral","score":0.0,"details":{"neg":0.0,"neu":1.0,"pos":0.0,"compound":0.0}}},{"feed_name":"DEV Community","feed_url":"https://dev.to/feed","title":"Hola dev.to 👋 Soy Ignicion y vengo a compartir lo que voy aprendiendo","link":"https://dev.to/yosoyignicion/hola-devto-soy-ignicion-y-vengo-a-compartir-lo-que-voy-aprendiendo-2he2","description":"<h1>\n\n\nHola dev.to 👋 Soy Ignicion y vengo a compartir lo que voy aprendiendo\n</h1>\n\n<p><strong>O más bien... lo que voy rompiendo.</strong> Que suele ser más divertido 😅</p>\n\n\n\n\n<h2>\n\n\n¿Quién soy?\n</h2>\n\n<p>Un tío de <strong>Castellón de la Plana</strong> 🇪🇸 que un día decidió que la inteligencia artificial no iba a ser algo \"del futuro\". Iba a ser algo <strong>que yo pudiera ejecutar en mi propio ordenador, sin nubes, sin APIs de pago, sin depender de nadie.</strong></p>\n\n<p>Y aquí estoy. Unos cuantos proyectos después, con más bugs que aciertos y más lecciones aprendidas de las que caben en un README.</p>\n\n\n\n\n<h2>\n\n\n¿Qué hago?\n</h2>\n\n<p>Básicamente construyo cosas. Algunas funcionan, otras... bueno, me enseñan cosas 😂</p>\n\n<ul>\n<li>🧠 <strong>Orchestrator-X</strong> — Un SaaS de auditoría web que usa IA local para analizar páginas y darte un informe técnico. Sin enviar tus datos a ningún lado.</li>\n<li>🎨 <strong>NexusStudio</strong> — Editor de diseño vectorial de escritorio con IA integrada (sí, local también)</li>\n<li>🛠️ <strong>Awesome-IgniSky</strong> — Mi ecosistema de herramientas para trabajar con agentes de IA</li>\n<li>🖼️ <strong>AssetShrink</strong> — Optimizador masivo de imágenes (porque reducir 100 PNGs a mano NO es plan)</li>\n<li>Y más cositas en <a href=\"https://github.com/yosoyignicion\" rel=\"noopener noreferrer\">mi GitHub</a>\n</li>\n</ul>\n\n\n\n\n<h2>\n\n\n¿Por qué dev.to?\n</h2>\n\n<p>Porque me he pasado <strong>años programando en silencio</strong> y ya va siendo hora de compartir lo que aprendo. Además, he descubierto que:</p>\n\n<blockquote>\n<p><em>Explicar algo a otros es la mejor forma de entenderlo tú mismo.</em></p>\n</blockquote>\n\n<p>Así que aquí me tienes. Sin pretensiones. Sin postureo. Solo un dev compartiendo su viaje.</p>\n\n\n\n\n<h2>\n\n\nLo que voy a ir publicando\n</h2>\n\n<p>Nada de \"10 tips para ser mejor programador\". Prometo. Más bien:</p>\n\n<ul>\n<li>🧪 <strong>Experimentos reales</strong> — \"Probé 3 LLMs locales y esto fue lo que pasó\"</li>\n<li>💥 <strong>Fallos épicos</strong> — Porque se aprende más de un <code>rm -rf</code> mal puesto que de un tutorial</li>\n<li>🛠️ <strong>Setups y herramientas</strong> — Lo que uso en mi día a día para trabajar con IA</li>\n<li>🤔 <strong>Reflexiones</strong> — Sobre hacia dónde va esto de la IA, sin hype, sin vender humo</li>\n<li>🌍 <strong>Contenido en español</strong> — Porque el contenido técnico de calidad en español brilla por su ausencia</li>\n</ul>\n\n\n\n\n<h2>\n\n\nMi stack (por si te da curiosidad)\n</h2>\n\n<p>No soy de un solo lenguaje. Me gusta usar la herramienta correcta para cada problema:</p>\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>Lenguaje</th>\n<th>Para qué lo uso</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>C++17</td>\n<td>Motores nativos, cuando necesito velocidad de verdad</td>\n</tr>\n<tr>\n<td>Python</td>\n<td>Scripting, IA local, herramientas CLI</td>\n</tr>\n<tr>\n<td>TypeScript</td>\n<td>Configuración y tooling</td>\n</tr>\n<tr>\n<td>JavaScript</td>\n<td>Lógica cliente, conversión de medios</td>\n</tr>\n<tr>\n<td>Tailwind + HTML</td>\n<td>UIs rápidas que no dan vergüenza ajena</td>\n</tr>\n</tbody>\n</table></div>\n\n<p>Todo sobre Linux 🐧. Todo con LLMs locales (Ollama, LM Studio). <strong>Privacidad primero.</strong></p>\n\n\n\n\n<h2>\n\n\nTambién tengo un blog 🔥\n</h2>\n\n<p>Acabo de lanzar <strong>IgnicionDev RESEARCH LAB</strong> — <a href=\"https://igniciondev-blog.pages.dev\" rel=\"noopener noreferrer\">igniciondev-blog.pages.dev</a> — donde publico deep dives técnicos más largos. El blog y dev.to son complementarios: aquí vengo a charlar, allí voy a fondo.</p>\n\n<p>Si te interesa la IA, los sistemas, el \"vibe coding\" bien hecho, o simplemente ver a alguien aprender en público... creo que te va a gustar.</p>\n\n\n\n\n<h2>\n\n\nVamos al grano\n</h2>\n\n<p>No me enrollo más. Si has llegado hasta aquí, dime hola en los comentarios 👇</p>\n\n<ul>\n<li>¿En qué andas trabajando?</li>\n<li>¿Usas IA en tu día a día como dev?</li>\n<li>¿Eres team local LLM o team API?</li>\n</ul>\n\n<p>Nos leemos 🚀</p>\n\n\n\n\n<p><em>PD: Si ves que algún día publico algo y me equivoco estrepitosamente, dímelo. Se aprende corrigiendo, no escondiendo los errores.</em></p>","content":"","published":"Sat, 25 Jul 2026 15:01:45 +0000","author":"Ignicion","guid":"https://dev.to/yosoyignicion/hola-devto-soy-ignicion-y-vengo-a-compartir-lo-que-voy-aprendiendo-2he2","created_at":"2026-07-25T17:27:41.996322","last_synchronized":"2026-07-25T17:27:41.996322","sentiment":{"sentiment":"Negative","score":-0.9892,"details":{"neg":0.071,"neu":0.917,"pos":0.012,"compound":-0.9892}}},{"feed_name":"DEV Community","feed_url":"https://dev.to/feed","title":"We instrumented an AI agent swarm with SigNoz, and its own telemetry told us we were wrong about almost everything","link":"https://dev.to/himanshu_748/we-instrumented-an-ai-agent-swarm-with-signoz-and-its-own-telemetry-told-us-we-were-wrong-about-3fip","description":"<p>Built for the WeMakeDevs Agents of SigNoz hackathon, July 2026.</p>\n\n<p><a class=\"article-body-image-wrapper\" href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9sslzrvxjmpqz4unvdon.png\"><img alt=\"DevSwarm Mission Control: the swarm graph, the live trace river and the hangar of everything it has built\" height=\"501\" src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9sslzrvxjmpqz4unvdon.png\" width=\"800\" /></a><br />\n<em>Mission Control. The graph is the swarm, the river underneath it is the live span stream, and every bar deep-links into that trace in SigNoz.</em></p>\n\n<p>DevSwarm turns one prompt into a working full-stack app. Five open-weight models plan it, build it, review it and repair their own routing. Nothing it produces is trusted blindly, and every step is an OpenTelemetry span in SigNoz, including the steps that go wrong.</p>\n\n<p>We built the observability first, expecting it to prove the thing worked.</p>\n\n<p>It did something more useful. It spent a week proving that almost everything we believed about our own system was wrong. We blamed a model for a limit we had set ourselves. We blamed a provider outage on the model. We assumed our review agent was our strongest link when it was measurably the weakest. And we spent days writing a design system that, when we finally measured it, was making the output worse.</p>\n\n<p>Not one of those was found by reading the code again. Every single one came off a span event, a dashboard row or a benchmark that disagreed with us.</p>\n\n<p>So this is not an architecture post. It is six times the telemetry told us we were wrong, with the queries.</p>\n\n<p>The current numbers, all read live out of SigNoz rather than typed into a slide: 22 generations, 188 traced model calls across 8 models, 2.84 million tokens, 225 critic catches, 19 fallback promotions and 24 generated apps each reporting under their own service name.</p>\n<h2>\n\n\nWhat the swarm actually is\n</h2>\n\n<p>Five roles, each on the open-weight model that measured best for that job:</p>\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>role</th>\n<th>model</th>\n<th>job</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>planner</td>\n<td>GLM-5.2</td>\n<td>turn a prompt into a typed build plan and a locked API contract</td>\n</tr>\n<tr>\n<td>frontend</td>\n<td>GLM-5.2</td>\n<td>one self-contained index.html against that contract</td>\n</tr>\n<tr>\n<td>backend</td>\n<td>Qwen3-Coder-480B</td>\n<td>one Express server against the same contract</td>\n</tr>\n<tr>\n<td>critic</td>\n<td>Kimi-K2.7-Code</td>\n<td>review both, gate the merge, route catches back to their owner</td>\n</tr>\n<tr>\n<td>doctor</td>\n<td>GLM-5.2</td>\n<td>read the swarm's own traces and repair its model routing</td>\n</tr>\n</tbody>\n</table></div>\n\n<p><a class=\"article-body-image-wrapper\" href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgwxc8ylw20rwodipyfe7.png\"><img alt=\"The DevSwarm landing page\" height=\"501\" src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgwxc8ylw20rwodipyfe7.png\" width=\"800\" /></a><br />\n<em>Every number on our own landing page is a live ClickHouse query against the trace store. Marketing copy that drifts from the telemetry is impossible by construction.</em></p>\n\n<p>Everything is served through Hugging Face Inference Providers. There are zero closed-model API calls in the system, which turned out to matter for reasons we did not anticipate (see the provider section below).</p>\n\n<p>The critic is the load-bearing part. Frontend and backend are generated in parallel from the same contract, then an independent model reviews both for contract conformance, security and runtime bugs. Real catches route back to the agent that owns them, that agent patches its own file and the critic re-reviews only the delta. Two regeneration rounds, then it ships with an honest verdict either way.</p>\n<h2>\n\n\nWhy we instrumented before we polished\n</h2>\n\n<p>A multi-agent system fails in ways a single-model tool does not. A call can succeed while producing an unusable artifact. A fallback can rescue a request so smoothly that nobody notices the primary is dead. Latency can triple because one role silently started thinking twice as long. None of that shows up in a request log.</p>\n\n<p>So the very first thing that worked in this project was not code generation. It was a trace.</p>\n\n<p>SigNoz is self-hosted through Foundry, which is a single-config install. Our <code>casting.yaml</code> and <code>casting.yaml.lock</code> are committed to the repo so the deployment is reproducible by anyone, judges included.</p>\n<h2>\n\n\nThree signals, and what each one carries\n</h2>\n\n<p><strong>Traces.</strong> Every model call is a span named <code>llm.&lt;role&gt;</code> carrying GenAI semantic conventions:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"nx\">span</span><span class=\"p\">.</span><span class=\"nf\">setAttributes</span><span class=\"p\">({</span>\n<span class=\"dl\">'</span><span class=\"s1\">gen_ai.operation.name</span><span class=\"dl\">'</span><span class=\"p\">:</span> <span class=\"dl\">'</span><span class=\"s1\">chat</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n<span class=\"dl\">'</span><span class=\"s1\">gen_ai.request.model</span><span class=\"dl\">'</span><span class=\"p\">:</span> <span class=\"nx\">model</span><span class=\"p\">,</span>\n<span class=\"dl\">'</span><span class=\"s1\">gen_ai.usage.input_tokens</span><span class=\"dl\">'</span><span class=\"p\">:</span> <span class=\"nx\">usage</span><span class=\"p\">.</span><span class=\"nx\">prompt_tokens</span><span class=\"p\">,</span>\n<span class=\"dl\">'</span><span class=\"s1\">gen_ai.usage.output_tokens</span><span class=\"dl\">'</span><span class=\"p\">:</span> <span class=\"nx\">usage</span><span class=\"p\">.</span><span class=\"nx\">completion_tokens</span><span class=\"p\">,</span>\n<span class=\"dl\">'</span><span class=\"s1\">devswarm.role</span><span class=\"dl\">'</span><span class=\"p\">:</span> <span class=\"nx\">role</span>\n<span class=\"p\">});</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Two span events do the heavy diagnostic lifting. <code>fallback_promotion</code> records that a primary failed, which model took over and the verbatim reason. <code>critic_catch</code> records every issue the review agent found, with its target and severity. Both are events rather than separate spans on purpose: they belong to the call they describe, and they survive in the trace even when the call ultimately succeeds.</p>\n\n<p><strong>Metrics.</strong> Six counters and a histogram, because some questions are time series questions rather than trace questions: <code>devswarm.tokens</code>, <code>devswarm.llm.calls</code>, <code>devswarm.llm.duration</code>, <code>devswarm.fallback.promotions</code>, <code>devswarm.critic.catches</code>, <code>devswarm.generations</code>, <code>devswarm.refinements</code>. Labelled by role, model and outcome.</p>\n\n<p><strong>Logs.</strong> Structured records for the things a human reads during an incident: a fallback promoting, a doctor diagnosis, a generation completing with its verdict and catch count. Same resource attributes as the traces, so a log line and a span line up.</p>\n\n<p>The whole trace layer is now extracted into a small library, <a href=\"https://github.com/himanshu748/otel-swarm\" rel=\"noopener noreferrer\">otel-swarm</a>, that any multi-agent system can drop in. DevSwarm consumes it as a real dependency, which means if the library breaks, our own dashboards go dark first. That felt like the honest way to ship it.</p>\n\n<p><a class=\"article-body-image-wrapper\" href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft1bxuwby7j6yseftcvue.png\"><img alt=\"A generation trace in SigNoz, nested agent and llm spans\" height=\"384\" src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft1bxuwby7j6yseftcvue.png\" width=\"800\" /></a><br />\n<em>One generation as a flame graph. Planner, then frontend and backend in parallel, then the critic.</em></p>\n\n<p><a class=\"article-body-image-wrapper\" href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe0bn6up4gc619owsbk5g.png\"><img alt=\"Structured logs in SigNoz showing fallback promotions and generation verdicts\" height=\"445\" src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe0bn6up4gc619owsbk5g.png\" width=\"800\" /></a><br />\n<em>The log stream during a rough run. WARN lines are fallback promotions, each naming the model that failed and why.</em></p>\n<h2>\n\n\nReading the swarm out of ClickHouse\n</h2>\n\n<p>Four dashboards, all committed as JSON in <code>observability/dashboards/</code>. The one we actually live in is Command Center: is the swarm healthy, and if not, which role.</p>\n\n<p>Two queries worth sharing, because span events in ClickHouse are not obvious the first time.</p>\n\n<p>Role health, straight off the trace store:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight sql\"><code><span class=\"k\">SELECT</span> <span class=\"n\">attributes_string</span><span class=\"p\">[</span><span class=\"s1\">'devswarm.role'</span><span class=\"p\">]</span> <span class=\"k\">AS</span> <span class=\"k\">role</span><span class=\"p\">,</span>\n <span class=\"k\">count</span><span class=\"p\">()</span> <span class=\"k\">AS</span> <span class=\"n\">calls</span><span class=\"p\">,</span>\n <span class=\"n\">countIf</span><span class=\"p\">(</span><span class=\"n\">statusCode</span> <span class=\"o\">=</span> <span class=\"mi\">2</span><span class=\"p\">)</span> <span class=\"k\">AS</span> <span class=\"n\">errors</span><span class=\"p\">,</span>\n <span class=\"n\">round</span><span class=\"p\">(</span><span class=\"n\">quantile</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">.</span><span class=\"mi\">95</span><span class=\"p\">)(</span><span class=\"n\">durationNano</span><span class=\"p\">)</span> <span class=\"o\">/</span> <span class=\"mi\">1</span><span class=\"n\">e9</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"k\">AS</span> <span class=\"n\">p95_s</span><span class=\"p\">,</span>\n <span class=\"k\">sum</span><span class=\"p\">(</span><span class=\"n\">attributes_number</span><span class=\"p\">[</span><span class=\"s1\">'gen_ai.usage.input_tokens'</span><span class=\"p\">]</span>\n <span class=\"o\">+</span> <span class=\"n\">attributes_number</span><span class=\"p\">[</span><span class=\"s1\">'gen_ai.usage.output_tokens'</span><span class=\"p\">])</span> <span class=\"k\">AS</span> <span class=\"n\">tokens</span>\n<span class=\"k\">FROM</span> <span class=\"n\">signoz_traces</span><span class=\"p\">.</span><span class=\"n\">distributed_signoz_index_v3</span>\n<span class=\"k\">WHERE</span> <span class=\"n\">serviceName</span> <span class=\"o\">=</span> <span class=\"s1\">'devswarm'</span> <span class=\"k\">AND</span> <span class=\"n\">name</span> <span class=\"k\">LIKE</span> <span class=\"s1\">'llm.%'</span>\n<span class=\"k\">GROUP</span> <span class=\"k\">BY</span> <span class=\"k\">role</span> <span class=\"k\">ORDER</span> <span class=\"k\">BY</span> <span class=\"n\">calls</span> <span class=\"k\">DESC</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Fallback promotions over time, which requires reaching into the events array:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight sql\"><code><span class=\"k\">SELECT</span> <span class=\"n\">toStartOfInterval</span><span class=\"p\">(</span><span class=\"nb\">timestamp</span><span class=\"p\">,</span> <span class=\"n\">INTERVAL</span> <span class=\"mi\">30</span> <span class=\"k\">MINUTE</span><span class=\"p\">)</span> <span class=\"k\">AS</span> <span class=\"n\">ts</span><span class=\"p\">,</span>\n <span class=\"n\">attributes_string</span><span class=\"p\">[</span><span class=\"s1\">'devswarm.role'</span><span class=\"p\">]</span> <span class=\"k\">AS</span> <span class=\"k\">role</span><span class=\"p\">,</span>\n <span class=\"k\">count</span><span class=\"p\">()</span> <span class=\"k\">AS</span> <span class=\"n\">value</span>\n<span class=\"k\">FROM</span> <span class=\"n\">signoz_traces</span><span class=\"p\">.</span><span class=\"n\">distributed_signoz_index_v3</span>\n<span class=\"k\">WHERE</span> <span class=\"n\">serviceName</span> <span class=\"o\">=</span> <span class=\"s1\">'devswarm'</span>\n<span class=\"k\">AND</span> <span class=\"n\">arrayExists</span><span class=\"p\">(</span><span class=\"n\">e</span> <span class=\"o\">-&gt;</span> <span class=\"n\">e</span> <span class=\"k\">LIKE</span> <span class=\"s1\">'%fallback_promotion%'</span><span class=\"p\">,</span> <span class=\"n\">events</span><span class=\"p\">)</span>\n<span class=\"k\">GROUP</span> <span class=\"k\">BY</span> <span class=\"n\">ts</span><span class=\"p\">,</span> <span class=\"k\">role</span> <span class=\"k\">ORDER</span> <span class=\"k\">BY</span> <span class=\"n\">ts</span>\n</code></pre>\n\n</div>\n\n\n\n<p>One design decision we are glad about: the marketing numbers on our own landing page are fetched from these same queries at request time. The page cannot drift from the telemetry, because there is only one source of both.</p>\n\n<p><a class=\"article-body-image-wrapper\" href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft179tlrsj58ztorz3x7h.png\"><img alt=\"The DevSwarm Command Center dashboard in SigNoz\" height=\"419\" src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft179tlrsj58ztorz3x7h.png\" width=\"799\" /></a><br />\n<em>Command Center. Top row answers \"is the swarm healthy\", the role-health table answers \"which role\", while the fallback chart should trend to zero.</em></p>\n\n<p><a class=\"article-body-image-wrapper\" href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbvwqe1g1qpins69dq5jf.png\"><img alt=\"The LLM Economics dashboard in SigNoz\" height=\"384\" src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbvwqe1g1qpins69dq5jf.png\" width=\"800\" /></a><br />\n<em>LLM Economics: tokens and latency per role and per model, which is how we caught the frontend role burning two thirds of its budget on reasoning.</em></p>\n\n<h2>\n\n\nWhat one app actually costs\n</h2>\n\n<p>Every token in the table below came off a span. This is one real run, the letterpress site above, priced at the rates the Hugging Face router itself reports for the providers we use.</p>\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>role</th>\n<th>model</th>\n<th>in</th>\n<th>out</th>\n<th>cost</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>frontend</td>\n<td>GLM-5.2</td>\n<td>27,855</td>\n<td>38,888</td>\n<td>$0.2101</td>\n</tr>\n<tr>\n<td>critic</td>\n<td>Kimi-K2.7-Code</td>\n<td>50,996</td>\n<td>14,539</td>\n<td>$0.1066</td>\n</tr>\n<tr>\n<td>planner</td>\n<td>GLM-5.2</td>\n<td>670</td>\n<td>4,090</td>\n<td>$0.0189</td>\n</tr>\n<tr>\n<td>backend</td>\n<td>Qwen3-Coder-480B</td>\n<td>2,800</td>\n<td>3,778</td>\n<td>$0.0069</td>\n</tr>\n<tr>\n<td></td>\n<td></td>\n<td><strong>82,321</strong></td>\n<td><strong>61,295</strong></td>\n<td><strong>$0.34</strong></td>\n</tr>\n</tbody>\n</table></div>\n\n<p>Thirty four cents for a designed marketing site with a working Express backend, a waitlist that validates email and rejects duplicates, plus its own OpenTelemetry wiring. Across passing runs the range is about 18 cents to 56 cents.</p>\n\n<p>Two things in that table surprised us.</p>\n\n<p>The critic costs fifteen times what the backend author costs. Reviewing the code is dramatically more expensive than writing it, because review means reading both artifacts in full, twice, while the backend agent writes one file once. Nobody budgets for that. If you are building a review gate into an agent system, it is not a rounding error on top of generation, it is a third of your bill.</p>\n\n<p>And a failed run costs more than a successful one. Our worst generations burned 232,000 tokens hitting the regeneration ceiling, against 74,000 for the cleanest pass. So convergence is not only a quality metric, it is the cost metric. Fixing the contract-format bug in finding five did more for our unit economics than any model swap we made.</p>\n\n<h2>\n\n\nAlerts that wake an agent instead of a human\n</h2>\n\n<p>This is the part of the build we are proudest of, and it is a genuinely small amount of code.</p>\n\n<p>Two alert rules live in <code>observability/alerts/</code>: a fallback-usage spike and a critic catch-rate flatline. Both notify a webhook channel called <code>swarm-doctor</code>, which points at <code>POST /api/doctor/webhook</code> on the swarm itself.</p>\n\n<p>When an alert fires, the Doctor wakes up, queries the swarm's own traces for the last hour and decides what to do about the routing table. It promotes a backup model, resets a recovered primary or does nothing, then explains itself in plain English using the numbers it just read. Its first real diagnosis, verbatim:</p>\n\n<blockquote>\n<p>\"The critic role is the clear problem area: its primary triggered 6 fallback promotions out of 9 calls (67%) with a 22% error rate and p95 latency of 242s. Backend, planner and doctor are healthy.\"</p>\n</blockquote>\n\n<p>The Doctor's own model calls are traced too, so the healer is exactly as observable as the patient.</p>\n\n<p><a class=\"article-body-image-wrapper\" href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsbfrpmda6t129jpmn3kp.png\"><img alt=\"Mission Control showing the Swarm Doctor's diagnosis panel\" height=\"501\" src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsbfrpmda6t129jpmn3kp.png\" width=\"800\" /></a><br />\n<em>The Doctor reporting a healthy swarm. It read 180 minutes of its own traces to say so, and it is honest about sample size: \"call volume is very low, so latency figures are not statistically meaningful\".</em></p>\n\n<p>Two hard-won SigNoz API notes, since we lost hours to both:</p>\n\n<p>Alert rules must be created against <code>/api/v2/rules</code> with <code>schemaVersion: v2alpha1</code>, a <code>notificationSettings</code> block and at least one channel. The v1 endpoint accepts the request and returns <code>\"alert rule is not valid\"</code> with no indication of which field is wrong. Dashboards, by contrast, go to <code>/api/v1/dashboards</code> with a <code>SIGNOZ-API-KEY</code> header and behave exactly as documented.</p>\n\n<p>Also: a cold Docker restart can leave ClickHouse replicas read-only until Keeper reconnects. It usually self-heals within a minute. If it does not, <code>SYSTEM RESTORE REPLICA</code> per table clears it.</p>\n\n<h2>\n\n\nApps that are born observable\n</h2>\n\n<p>Every app the swarm generates ships instrumented. Alongside <code>index.html</code> and <code>server.js</code>, each generated folder gets an <code>otel.mjs</code> bootstrap, a <code>package.json</code>, and a <code>signoz-dashboard.json</code> scoped to that app's own service name. If a <code>SIGNOZ_API_TOKEN</code> is configured, the dashboard is created in SigNoz at generation time, before the user has opened the preview.</p>\n\n<p>So the generated app appears in SigNoz as its own service, with RED metrics and a routes table, seconds after it exists. Twenty four of them are in our instance right now.</p>\n\n<p>Worth saying because people assume otherwise: there is no image model anywhere in this pipeline. The swarm generates 227 inline SVG elements across the 26 apps it has built, an average of 8.7 per app, and every one of them was written as markup by a language model. The Vandercook press in the screenshot above is hand-drawn SVG, not a generated image. The only assets we ever image-generated are DevSwarm's own favicon and social card, which are branding for the tool rather than anything the swarm produces.</p>\n\n<p><a class=\"article-body-image-wrapper\" href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fforp67ml0koev7bulj1b.png\"><img alt=\"Quoin and Roller, a letterpress site generated from one sentence\" height=\"501\" src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fforp67ml0koev7bulj1b.png\" width=\"800\" /></a><br />\n<em>One of the outputs. The Vandercook press is inline SVG the model drew itself, and this app reports to SigNoz under its own service name from the moment it boots.</em></p>\n\n<p>One caveat we had to learn the hard way: an app only emits spans while its backend is actually running. Early on our preview served the frontend statically, so the generated Express server never booted and the app silently fell back to localStorage. The preview looked perfect and the service page was almost empty. That mismatch, two spans where there should have been dozens, is what gave the bug away. Previews now spawn the real server as a child process and proxy to it.</p>\n\n<h2>\n\n\nSix things our own telemetry told us we had wrong\n</h2>\n\n<p>This is the section I would want to read, so it is the longest one.</p>\n\n<p><strong>1. A \"model limitation\" was a stale constant we wrote ourselves.</strong></p>\n\n<p>Traces showed every frontend failure as <code>finish: length</code>, truncating full-page HTML. We concluded GLM-5.2's provider capped completions at 16384 tokens and moved the role to another model. The cap was real when we found it. It was also in our own config, and when the provider limit later lifted, our constant kept enforcing a limit that no longer existed. The average frontend artifact needs about 19,000 output tokens. We had guaranteed truncation and blamed the model for a fortnight.</p>\n\n<p><strong>2. The real cause was provider roulette, visible only in the span event text.</strong></p>\n\n<p>After removing our own cap, GLM still failed intermittently with a 400: <code>max_completion_tokens is limited to 16384 for glm-5.2</code>. The Hugging Face router load-balances a model across every provider serving it, and their limits disagree. We probed all seven: scaleway caps at 16384, featherless at 32768, novita and zai-org at 131072, while together, fireworks-ai and deepinfra accept 200000 or more. Unpinned, roughly one request in seven hit the strict provider and died instantly. Pinning the model to one provider produced our first ever generation with zero fallbacks. That entire diagnosis came out of the <code>reason</code> attribute on a <code>fallback_promotion</code> event.</p>\n\n<p><strong>3. Our span attribute was hiding the failures we most wanted to see.</strong></p>\n\n<p>When a primary failed, our code overwrote <code>gen_ai.request.model</code> on the span with the fallback's name. It seemed tidy. It meant every dashboard row attributed the primary's failure, and its wasted latency, to the fallback that cleaned up after it. We spent an afternoon convinced the critic's backup model was slow and error-prone. Isolating them in a benchmark showed the opposite: the backup was fine at 7 seconds, and the primary was the problem. If you take one implementation detail from this post, take this one. Record the model you attempted, and put the promotion in an event, not on top of the attribute you will later group by.</p>\n\n<p><strong>4. Our review gate was the weakest model in the swarm.</strong></p>\n\n<p>We had never benchmarked the critic, so we built one: a generated app with three documented contract defects, three runs per model, scored on defect recall. DeepSeek-V4-Pro, our incumbent primary, found 2 of 9. One run burned its entire 32768-token budget and returned nothing parseable. Kimi-K2.7-Code found 8 of 9 and was consistent across runs. The clearest pattern in the data was that on a review task, reasoning volume tracks defect recall: the terse models answered in under 250 output tokens and missed real bugs.</p>\n\n<p><strong>5. A 20 percent pass rate was one missing sentence in the contract.</strong></p>\n\n<p>Our plans specified field names and types but never formats, ranges or nullability. So the backend rejected <code>rating: 0</code> while the frontend sent 0 as its default, and the backend demanded <code>YYYY-MM-DD</code> while the frontend sent full ISO strings. Three consecutive generations hit the regeneration ceiling on exactly this class of disagreement. The fix was making the planner write a binding rules string per field, for example <code>\"integer 0 to 5 inclusive, where 0 means unrated and is a valid value\"</code>. Both builders now read the same sentence. Catches dropped from 9 to 3 and the next run passed.</p>\n\n<p><strong>6. Our design system was making the output worse.</strong></p>\n\n<p>We wrote a careful design guide so generated apps would not look like generated apps. Then we measured it: same model, same prompt, the only variable being whether the guide was attached. Without it, 14 inline SVGs, 3 animations and a deliberate typeface pairing. With it, 5 SVGs, 1 animation and Courier New. Three of our own rules did that. \"System font stack is fine\" told the model not to bother choosing type. \"Cut any animation that does not serve the subject\" read as licence to strip ornament. And our frontend prompt banned all external requests, which silently banned Google Fonts, so it could not have chosen a real typeface even if it wanted to. We had written a list of prohibitions, which is good at preventing bad output and bad at producing good output.</p>\n\n<p><a class=\"article-body-image-wrapper\" href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe8ameuufkzpzemy0972q.png\"><img alt=\"Generated bookshelf app using system fonts and rainbow card colours\" height=\"501\" src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe8ameuufkzpzemy0972q.png\" width=\"800\" /></a><br />\n<em>Before. Mono labels from a system stack, and card colours the backend invented at random.</em></p>\n\n<p><a class=\"article-body-image-wrapper\" href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fflnchfpeeg7stwtb4cj8.png\"><img alt=\"The same prompt generating a bookshelf app with Fraunces and a drawn SVG shelf\" height=\"501\" src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fflnchfpeeg7stwtb4cj8.png\" width=\"800\" /></a><br />\n<em>After. Same model, same prompt, three rules removed from our guide: Fraunces display type, a drawn logo mark, filter chips carrying live counts, plus the books rendered as spines on a shelf.</em></p>\n\n<p>There was a second layer to that one. The guide had a generous section for marketing sites, licensing scroll reveals, entrance sequences and layered depth, plus a stingy section for apps. Every app the swarm built was being held to a deliberately plainer standard than every site, and nobody had noticed because the sites looked great.</p>\n\n<h2>\n\n\nHow this was built\n</h2>\n\n<p>DevSwarm was built with Claude Code, which is worth stating plainly rather than leaving as an inference. An AI coding agent helped build an AI coding agent, and the hackathon rules ask entrants to declare assistant use, so here it is.</p>\n\n<p>It is also relevant to the point of this post. Every finding in the section above started as a confident, wrong belief held by both of us, human and assistant alike. The stale token cap, the model we blamed for a provider's limit, the review gate we assumed was our strongest link, the design system we were sure was helping. None of those were resolved by reasoning harder about the code. They were resolved by a span event, a dashboard row or a benchmark disagreeing with us.</p>\n\n<p>That is the argument for instrumenting an agent system early. When you are building with agents, and with an agent, the telemetry is the only participant in the conversation with no opinion to defend.</p>\n\n<h2>\n\n\nWhat is worth copying from this build\n</h2>\n\n<p>If you are instrumenting an agent system, three things paid for themselves immediately.</p>\n\n<p>Put the reason text in the span event. Not a code, not an enum, the actual provider error string. Both of our worst bugs were solved by reading that field, and neither would have been visible in a metric.</p>\n\n<p>Never overwrite an attribute you intend to group by. Add, do not replace.</p>\n\n<p>Make your product read its own telemetry. Our landing page statistics, our Doctor's diagnosis and our dashboards all run the same queries against the same trace store. It removes a whole category of drift, and it turns your observability stack from a debugging tool into a feature.</p>\n\n<h2>\n\n\nThe uncomfortable part\n</h2>\n\n<p>The through line of every finding above is the same, and I have thought about it more than I expected to.</p>\n\n<p>Not one of these was a hard problem. A stale constant. A provider with a different limit. An attribute overwritten in the wrong place. A missing sentence in a contract. Three over-cautious lines in a style guide. Any of them would have been a five minute fix if we had known. Together they cost us most of a week and made the system look, from the outside, like the models were letting us down.</p>\n\n<p>They were not. Every single time, the model did exactly what our configuration told it to do. The failure was always upstream of the model, in something we had written and then stopped looking at.</p>\n\n<p>I think that is the actual lesson of building with agents, and it is not a comfortable one. The debugging skill is not prompt engineering. It is being willing to believe your instrumentation over your own memory of what you configured three days ago. We only got there because the telemetry kept producing numbers that made our explanations impossible.</p>\n\n<p>If you are building something similar, I would genuinely like to know whether your experience matches. My suspicion is that a lot of \"the model is not good enough\" is actually \"my config is stale and I have no way to see it\".</p>\n\n<h2>\n\n\nLinks\n</h2>\n\n<ul>\n<li>DevSwarm: <a href=\"https://github.com/himanshu748/devswarm\" rel=\"noopener noreferrer\">github.com/himanshu748/devswarm</a>\n</li>\n<li>otel-swarm, the extracted instrumentation library: <a href=\"https://github.com/himanshu748/otel-swarm\" rel=\"noopener noreferrer\">github.com/himanshu748/otel-swarm</a>\n</li>\n<li>SigNoz: <a href=\"https://signoz.io\" rel=\"noopener noreferrer\">signoz.io</a>\n</li>\n<li>The hackathon: <a href=\"https://www.wemakedevs.org/hackathons/signoz\" rel=\"noopener noreferrer\">Agents of SigNoz</a> by WeMakeDevs</li>\n</ul>\n\n<p>Dashboards, alert rules and the Foundry <code>casting.yaml</code> are all in the repo under <code>observability/</code>, so the whole SigNoz side of this is reproducible rather than described.</p>","content":"","published":"Sat, 25 Jul 2026 15:01:20 +0000","author":"Himanshu Kumar","guid":"https://dev.to/himanshu_748/we-instrumented-an-ai-agent-swarm-with-signoz-and-its-own-telemetry-told-us-we-were-wrong-about-3fip","created_at":"2026-07-25T17:27:41.996322","last_synchronized":"2026-07-25T17:27:41.996322","sentiment":{"sentiment":"Negative","score":-0.9966,"details":{"neg":0.051,"neu":0.913,"pos":0.037,"compound":-0.9966}}},{"feed_name":"DEV Community","feed_url":"https://dev.to/feed","title":"“What does it mean to me?” — and eight days of finding out what we had wrong","link":"https://dev.to/apexgridtech/what-does-it-mean-to-me-and-eight-days-of-finding-out-what-we-had-wrong-33fc","description":"<p>An advisor asked me one question that changed how we write.</p>\n\n<p><em>\" What does it mean to me?\"</em></p>\n\n<p>Chinedu Nwafor was reading one of our Asotele briefings. The figures were correct. Every one was sourced and dated. And still, that question stood there, unanswered — because a number tells you what happened, not what to do about it.</p>\n\n<p>We had been reporting, not communicating. Precision without meaning is just decorated data.</p>\n\n<p>So we rebuilt the \"What it means\" section of every brief around his question. Not <em>\" inflation printed 15.9%\"</em>, but what that does to a contract, a margin, a decision. The test we now apply to a line is whether a reader could act on it — accuracy is the floor, not the product.</p>\n\n<p>One good question was worth more than a month of our own review. It also set the tone for the eight days since, which were mostly spent finding out what we had been getting wrong.</p>\n\n<h2>\n\n\nFour things we were publishing incorrectly\n</h2>\n\n<p><strong>The policy rate.</strong> For an extended period the briefings carried the wrong Monetary Policy Rate — and not one wrong value but an unstable one. Across the affected period the notes showed 22.0%, 20.0%, 27.5% and 26.5% in different places. The correct rate is <strong>26.50%</strong> , set at the 304th MPC meeting in February and held since. </p>\n\n<p>The cause: the rate was being read automatically from general news coverage, which refers to current, historical and expected rates in the same article, and the extraction could not tell them apart. We replaced that route with a maintained record of numbered policy decisions, and corrected <strong>82 briefings</strong> covering 27 April to 21 July — including the twenty that already showed the right figure, because correcting only the ones that differed would leave a record a reader cannot trust as a whole.</p>\n\n<p><strong>A satellite product that was not real.</strong> We build a night-lights economic momentum series from NASA VIIRS tiles. Work on a gas-flare mask surfaced something worse than flares: the most recent month's tiles were corrupt, and the readable local copy was <strong>byte-identical to the previous month</strong> — the same data, relabelled. The momentum product was reporting a month that did not exist. It is withdrawn pending a clean re-fetch. <strong>Licence labels being silently destroyed.</strong> Every passage in our corpus carries a canonical licence label; only sixteen are permitted. We found 208 passages carrying none. The root cause was worse than the symptom: four ingest scripts declared the correct licence as a constant and never wrote it onto the chunk. Because one of them <em>replaces</em> passages in place rather than appending, it did not merely add unlabelled material — it <strong>stripped the label off 503 passages that already had one</strong>. </p>\n\n<p>We fixed the scripts, backfilled, and then did the structural work: a write guard that validates every passage at the moment of persistence and refuses the write if any lack a permitted licence, leaving the previous corpus intact. It now covers <strong>42 writers</strong> , including the streaming ones that rewrite the whole corpus line by line. The corpus stands at <strong>1,178,899 passages, zero unlabelled, zero outside the whitelist</strong>.</p>\n\n<p><strong>Data written to a database nobody reads.</strong> Our collection machine kept its own copy of the records. A weekly regulatory feed had been writing newly licensed lenders into that copy — not the live one. The loss was four records when we found it, and growing every week, and invisible to every health check because the job succeeded every time. Collection may now run anywhere; writing to the records happens only on the machine that owns them. </p>\n\n<p>None of these were caught by a monitor. Three were caught by doing unrelated work nearby, and one by an advisor.</p>\n\n<h2>\n\n\nWhat advisors caught\n</h2>\n\n<p>Oluwaseun Adeosun's review turned up a genuine bug in our parallel-market FX handling, which led to a venue depth-and-coherence gate on the index and two estimator fixes — including one worth naming, because it is the kind of error that looks fine forever: the RiskMetrics decay factor of 0.94 is a <strong>daily</strong> convention, and we were applying it to three-hourly observations. That gives a volatility memory roughly eight times too short. It now derives from the observed gap between captures, so it self-corrects if the cadence changes.</p>\n\n<p>A separate advisor flag led to an inflation correction earlier in the period.</p>\n\n<p>This is the argument for an advisory committee stated more plainly than we could state it ourselves: four of the defects above were ours to find and we found them late; two came from outside and came faster.</p>\n\n<h2>\n\n\nWhat shipped\n</h2>\n\n<p>Alongside the corrections:</p>\n\n<p>- <strong>Nigeria 's only published freight rate card.</strong> Every commercial courier gates its pricing — GIG returns 401 on all price endpoints, others answer \"talk to sales\", and no regulator publishes a tariff for private carriers. NIPOST, as a statutory body, publishes its full schedule. We ingested all of it: <strong>18,787 rows</strong> , centred on a <strong>37×37 city-to-city cargo matrix</strong>. Lagos–Ibadan ₦1,400; Lagos–Maiduguri ₦11,550. An eightfold spread across one country, and a structural constraint on where a business can profitably serve. - <strong>Road freight risk</strong> , from FRSC data via the Bureau of Statistics — 21,092 rows to Q1 2026. Commercial vehicles are 72% of vehicles involved in crashes; trucks, tankers and trailers alone are about a quarter, a share stable across six quarters. - <strong>Postal services by state</strong> , 2019–2025 — one of very few state-level formalisation proxies Nigeria publishes. - <strong>Non-oil export parity</strong> , translating world commodity prices into naira proceeds per tonne at both official and street rates, so an exporter can see how much of their price is the crop and how much is the exchange rate. - <strong>Agricultural conditions</strong> , reworked into a reasoned read rather than a readout: import-substitution gaps, rainfall anomalies, and a weather-outlier synthesis. - <strong>A live courier price series</strong> , sampled daily. The NIPOST tariff is an administered price — it steps when NIPOST revises it and otherwise never moves, which makes it a benchmark and not a signal. It cannot tell you whether diesel is feeding through to freight, or whether a corridor is disrupted. That needs a price that moves.</p>\n\n<h2>\n\n\nThe wall behind the question\n</h2>\n\n<p>Chinedu's question stayed with me for a reason beyond the rewrite: communication has a barrier harder than clarity, which is language itself.</p>\n\n<p>Nigeria's economic information is published almost entirely in English. Much of the population most affected by it does not transact in English. For them, <em>\" what does it mean to me?\"</em> cannot be answered at all — not because the data is wrong, but because it never arrives in a language they use.</p>\n\n<p>So we are building Asotele to answer economic questions in Hausa, Yorùbá, Igbo and Nigerian Pidgin. Four sentiment classifiers are trained and shipped. And we have hit the wall every honest translation effort hits: the grammar is easy, the terminology is not.</p>\n\n<p>One model rendered <strong>\" inflation\"</strong> in Yorùbá as <strong>\" rubbish.\"</strong></p>\n\n<h3>\n\n\nA mistake we shipped ourselves\n</h3>\n\n<p>It would be comfortable to leave it there. The more useful story is our own.</p>\n\n<p>An early version of our terminology pipeline accepted candidate translations automatically when they were well attested in real Nigerian-language text. If speakers use a phrase often, the reasoning went, it is probably right.</p>\n\n<p>It accepted <strong>_onye na-azụ</strong>_ — literally <em>\" person who buys\"</em> — as the Igbo for <strong>borrower</strong>.</p>\n\n<p>It is a real phrase, and well attested, and wrong in the way that matters most: a reader would understand every word and take away the opposite meaning. Seven terms had been accepted that way. We reverted all seven, and changed the rule. Attestation may now only <strong>reject</strong> a proposed term; it can never approve one. Approval requires a speaker. There is no configuration in which the machine has the final say on a word.</p>\n\n<p>That is why this needs people rather than more computing. It is not a scale problem.</p>\n\n<h2>\n\n\nAn open invitation\n</h2>\n\n<p>If you are a linguist — or simply someone who loves a Nigerian language and believes economic knowledge should reach people in their own tongue — we would like your help.</p>\n\n<p>- <strong>One term at a time.</strong> A batch is twenty, a few minutes. Answers save as you go. - <strong>Skip anything you are unsure of.</strong> We would far rather have a gap than a confident guess. Skipping is a button, not a failure. - <strong>Nothing publishes until two speakers agree.</strong> Nothing rests on one person's judgement, including yours. - <strong>Public credit if you want it</strong> , and none if you would rather not be named.</p>\n\n<p>We are not asking for bulk translation or for unpaid annotation at volume. If a batch ever feels like data entry rather than judgement, we have designed it wrongly and want to be told.</p>\n\n<p>There is a 99-second walkthrough — sign-in to sign-out — on the <a href=\"///language-review.html\">language review page</a>, along with the application.</p>\n\n<h2>\n\n\nOne more thing, since this is a progress log\n</h2>\n\n<p>We applied to the LINGUA Africa open call in June to fund exactly this: professional linguistic review, finance-domain fine-tuning, and Igbo and Pidgin content. <strong>We were not selected.</strong></p>\n\n<p>It was the largest single item on our funding list, so it is worth saying plainly rather than leaving a page quietly describing funded work that is no longer funded. What changes is that the terminology review now runs on volunteers rather than paid reviewers — slower, and in one respect better, because the terms that come out of it are ours to publish rather than licensed from anyone.</p>\n\n<p>Language is power. It should reach everyone.</p>\n\n<p>---</p>\n\n<p><em>Asotele is built by Apex Grid Technologies, a Nigerian registered company.</em></p>","content":"An advisor asked me one question that changed how we write.\n\" What does it mean to me?\"\nChinedu Nwafor was reading one of our Asotele briefings. The figures were correct. Every one was sourced and dated. And still, that question stood there, unanswered — because a number tells you what happened, not what to do about it.\nWe had been reporting, not communicating. Precision without meaning is just decorated data.\nSo we rebuilt the \"What it means\" section of every brief around his question. Not \" inflation printed 15.9%\", but what that does to a contract, a margin, a decision. The test we now apply to a line is whether a reader could act on it — accuracy is the floor, not the product.\nOne good question was worth more than a month of our own review. It also set the tone for the eight days since, which were mostly spent finding out what we had been getting wrong.\nFour things we were publishing incorrectly\nThe policy rate. For an extended period the briefings carried the wrong Monetary Policy Rate — and not one wrong value but an unstable one. Across the affected period the notes showed 22.0%, 20.0%, 27.5% and 26.5% in different places. The correct rate is 26.50% , set at the 304th MPC meeting in February and held since.\nThe cause: the rate was being read automatically from general news coverage, which refers to current, historical and expected rates in the same article, and the extraction could not tell them apart. We replaced that route with a maintained record of numbered policy decisions, and corrected 82 briefings covering 27 April to 21 July — including the twenty that already showed the right figure, because correcting only the ones that differed would leave a record a reader cannot trust as a whole.\nA satellite product that was not real. We build a night-lights economic momentum series from NASA VIIRS tiles. Work on a gas-flare mask surfaced something worse than flares: the most recent month's tiles were corrupt, and the readable local copy was byte-identical to the previous month — the same data, relabelled. The momentum product was reporting a month that did not exist. It is withdrawn pending a clean re-fetch. Licence labels being silently destroyed. Every passage in our corpus carries a canonical licence label; only sixteen are permitted. We found 208 passages carrying none. The root cause was worse than the symptom: four ingest scripts declared the correct licence as a constant and never wrote it onto the chunk. Because one of them replaces passages in place rather than appending, it did not merely add unlabelled material — it stripped the label off 503 passages that already had one.\nWe fixed the scripts, backfilled, and then did the structural work: a write guard that validates every passage at the moment of persistence and refuses the write if any lack a permitted licence, leaving the previous corpus intact. It now covers 42 writers , including the streaming ones that rewrite the whole corpus line by line. The corpus stands at 1,178,899 passages, zero unlabelled, zero outside the whitelist.\nData written to a database nobody reads. Our collection machine kept its own copy of the records. A weekly regulatory feed had been writing newly licensed lenders into that copy — not the live one. The loss was four records when we found it, and growing every week, and invisible to every health check because the job succeeded every time. Collection may now run anywhere; writing to the records happens only on the machine that owns them.\nNone of these were caught by a monitor. Three were caught by doing unrelated work nearby, and one by an advisor.\nWhat advisors caught\nOluwaseun Adeosun's review turned up a genuine bug in our parallel-market FX handling, which led to a venue depth-and-coherence gate on the index and two estimator fixes — including one worth naming, because it is the kind of error that looks fine forever: the RiskMetrics decay factor of 0.94 is a daily convention, and we were applying it to three-hourly observations. That gives a volatility memory roughly eight times too short. It now derives from the observed gap between captures, so it self-corrects if the cadence changes.\nA separate advisor flag led to an inflation correction earlier in the period.\nThis is the argument for an advisory committee stated more plainly than we could state it ourselves: four of the defects above were ours to find and we found them late; two came from outside and came faster.\nWhat shipped\nAlongside the corrections:\n- Nigeria 's only published freight rate card. Every commercial courier gates its pricing — GIG returns 401 on all price endpoints, others answer \"talk to sales\", and no regulator publishes a tariff for private carriers. NIPOST, as a statutory body, publishes its full schedule. We ingested all of it: 18,787 rows , centred on a 37×37 city-to-city cargo matrix. Lagos–Ibadan ₦1,400; Lagos–Maiduguri ₦11,550. An eightfold spread across one country, and a structural constraint on where a business can profitably serve. - Road freight risk , from FRSC data via the Bureau of Statistics — 21,092 rows to Q1 2026. Commercial vehicles are 72% of vehicles involved in crashes; trucks, tankers and trailers alone are about a quarter, a share stable across six quarters. - Postal services by state , 2019–2025 — one of very few state-level formalisation proxies Nigeria publishes. - Non-oil export parity , translating world commodity prices into naira proceeds per tonne at both official and street rates, so an exporter can see how much of their price is the crop and how much is the exchange rate. - Agricultural conditions , reworked into a reasoned read rather than a readout: import-substitution gaps, rainfall anomalies, and a weather-outlier synthesis. - A live courier price series , sampled daily. The NIPOST tariff is an administered price — it steps when NIPOST revises it and otherwise never moves, which makes it a benchmark and not a signal. It cannot tell you whether diesel is feeding through to freight, or whether a corridor is disrupted. That needs a price that moves.\nThe wall behind the question\nChinedu's question stayed with me for a reason beyond the rewrite: communication has a barrier harder than clarity, which is language itself.\nNigeria's economic information is published almost entirely in English. Much of the population most affected by it does not transact in English. For them, \" what does it mean to me?\" cannot be answered at all — not because the data is wrong, but because it never arrives in a language they use.\nSo we are building Asotele to answer economic questions in Hausa, Yorùbá, Igbo and Nigerian Pidgin. Four sentiment classifiers are trained and shipped. And we have hit the wall every honest translation effort hits: the grammar is easy, the terminology is not.\nOne model rendered \" inflation\" in Yorùbá as \" rubbish.\"\nA mistake we shipped ourselves\nIt would be comfortable to leave it there. The more useful story is our own.\nAn early version of our terminology pipeline accepted candidate translations automatically when they were well attested in real Nigerian-language text. If speakers use a phrase often, the reasoning went, it is probably right.\nIt accepted _onye na-azụ_ — literally \" person who buys\" — as the Igbo for borrower.\nIt is a real phrase, and well attested, and wrong in the way that matters most: a reader would understand every word and take away the opposite meaning. Seven terms had been accepted that way. We reverted all seven, and changed the rule. Attestation may now only reject a proposed term; it can never approve one. Approval requires a speaker. There is no configuration in which the machine has the final say on a word.\nThat is why this needs people rather than more computing. It is not a scale problem.\nAn open invitation\nIf you are a linguist — or simply someone who loves a Nigerian language and believes economic knowledge should reach people in their own tongue — we would like your help.\n- One term at a time. A batch is twenty, a few minutes. Answers save as you go. - Skip anything you are unsure of. We would far rather have a gap than a confident guess. Skipping is a button, not a failure. - Nothing publishes until two speakers agree. Nothing rests on one person's judgement, including yours. - Public credit if you want it , and none if you would rather not be named.\nWe are not asking for bulk translation or for unpaid annotation at volume. If a batch ever feels like data entry rather than judgement, we have designed it wrongly and want to be told.\nThere is a 99-second walkthrough — sign-in to sign-out — on the language review page, along with the application.\nOne more thing, since this is a progress log\nWe applied to the LINGUA Africa open call in June to fund exactly this: professional linguistic review, finance-domain fine-tuning, and Igbo and Pidgin content. We were not selected.\nIt was the largest single item on our funding list, so it is worth saying plainly rather than leaving a page quietly describing funded work that is no longer funded. What changes is that the terminology review now runs on volunteers rather than paid reviewers — slower, and in one respect better, because the terms that come out of it are ours to publish rather than licensed from anyone.\nLanguage is power. It should reach everyone.\n---\nAsotele is built by Apex Grid Technologies, a Nigerian registered company.\nTop comments (0)","published":"Sat, 25 Jul 2026 15:00:02 +0000","author":"Francis Oyakhire","guid":"https://dev.to/apexgridtech/what-does-it-mean-to-me-and-eight-days-of-finding-out-what-we-had-wrong-33fc","created_at":"2026-07-25T17:27:41.996322","last_synchronized":"2026-07-25T17:27:41.996322","sentiment":{"sentiment":"Positive","score":0.994,"details":{"neg":0.045,"neu":0.882,"pos":0.074,"compound":0.994}}},{"feed_name":"DEV Community","feed_url":"https://dev.to/feed","title":"When the exit code lies: fork retractions, lost transactions, and trusting the chain in midnight-node","link":"https://dev.to/wbaxterh/when-the-exit-code-lies-fork-retractions-lost-transactions-and-trusting-the-chain-in-1b1h","description":"<p>Your transaction tool reports <code>FAILED_TO_FINALIZE</code>. Your CI marks the job red. Your retry logic fires. But the block explorer says the transaction finalized 22 seconds after you sent it.</p>\n\n<p>The exit code lied.</p>\n\n<p>This is a debugging story from <a href=\"https://github.com/midnightntwrk/midnight-node\" rel=\"noopener noreferrer\">midnight-node</a> — Midnight's Substrate-based node — about how a transaction watcher loses track of a transaction during a chain fork, why the failure is invisible in tests, and the pattern that fixes it: <strong>on timeout, ask the chain itself, not the stream that was supposed to tell you about it.</strong> The fix landed as <a href=\"https://github.com/midnightntwrk/midnight-node/pull/1927\" rel=\"noopener noreferrer\">PR #1927</a> for <a href=\"https://github.com/midnightntwrk/midnight-node/issues/1854\" rel=\"noopener noreferrer\">issue #1854</a>.</p>\n\n<h2>\n\n\nForks are normal, your tooling forgets that\n</h2>\n\n<p>Midnight produces blocks with AURA every 6 seconds and finalizes them with GRANDPA a couple of blocks later. Between production and finality, the chain is allowed to disagree with itself: two validators can build competing blocks, and one branch eventually wins. A transaction included in the losing branch is <em>retracted</em> — kicked back to the transaction pool — and normally re-included in a block on the winning branch a few seconds later.</p>\n\n<p>From the chain's perspective this is routine housekeeping. From your tooling's perspective, it's a trap.</p>\n\n<p>The toolkit's <code>send</code> command watches a submitted transaction with <a href=\"https://github.com/paritytech/subxt\" rel=\"noopener noreferrer\">subxt</a>'s watch stream, which emits status events: <code>InBestBlock</code>, <code>NoLongerInBestBlock</code>, <code>InFinalizedBlock</code>, and so on. The sender's logic was:</p>\n\n<ol>\n<li>Wait for the tx to appear in a best block.</li>\n<li>Then wait (with a timeout) for <code>InFinalizedBlock</code>.</li>\n<li>Timeout expires → report <code>FAILED_TO_FINALIZE</code>, exit non-zero.</li>\n</ol>\n\n<p>Here's the failure mode from the issue, reconstructed from logs:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>t+0stx submitted, InBestBlock (block A)\nt+6sfork: block A retracted → NoLongerInBestBlock\nt+8stx re-included in block B on the winning branch\nt+22s block B finalized✅ tx is permanently on chain\nt+60s watcher's finalization timeout expires\n→ exit: FAILED_TO_FINALIZE❌\n</code></pre>\n\n</div>\n\n\n\n<p>Two things went wrong. First, <code>NoLongerInBestBlock</code> was being silently swallowed — the logs showed nothing at the moment the interesting thing happened. Second, after the retraction, the watch stream never surfaced the re-inclusion; the watcher waited out its timeout on a dead branch while the transaction quietly finalized elsewhere.</p>\n\n<h2>\n\n\nWhy a wrong exit code is worse than a crash\n</h2>\n\n<p>If this tool only fed dashboards, a spurious failure would cost an engineer an eyebrow raise. But callers make decisions based on exit codes. The sharpest edge in our case: reward-claim transactions with <em>at-most-once</em> semantics. A wrapper script sees the non-zero exit, assumes the claim never landed, and retries — resubmitting an operation that already succeeded. The exit code isn't diagnostics; it's an API, and it was returning wrong answers.</p>\n\n<p>This is a general lesson worth internalizing: <strong>any tool that reports transaction outcomes is part of someone's correctness argument.</strong> Treat its outputs with the same rigor as consensus code.</p>\n\n<h2>\n\n\nThe fix: the chain gets the last word\n</h2>\n\n<p>The watch stream is a convenience, not a source of truth. The source of truth is the finalized chain, and it's sitting right there behind an RPC. So on watch timeout, the sender now scans finalized blocks, newest first, looking for the extrinsic hash:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight rust\"><code><span class=\"k\">pub</span> <span class=\"k\">async</span> <span class=\"k\">fn</span> <span class=\"nf\">find_in_finalized_chain</span><span class=\"p\">(</span>\n<span class=\"n\">client</span><span class=\"p\">:</span> <span class=\"o\">&amp;</span><span class=\"n\">MidnightNodeClient</span><span class=\"p\">,</span>\n<span class=\"n\">extrinsic_hash_hex</span><span class=\"p\">:</span> <span class=\"o\">&amp;</span><span class=\"nb\">str</span><span class=\"p\">,</span>\n<span class=\"n\">max_depth</span><span class=\"p\">:</span> <span class=\"nb\">u32</span><span class=\"p\">,</span>\n<span class=\"p\">)</span> <span class=\"k\">-&gt;</span> <span class=\"nb\">Option</span><span class=\"o\">&lt;</span><span class=\"nb\">String</span><span class=\"o\">&gt;</span> <span class=\"p\">{</span>\n<span class=\"k\">let</span> <span class=\"k\">mut</span> <span class=\"n\">hash</span> <span class=\"o\">=</span> <span class=\"n\">client</span><span class=\"py\">.rpc</span><span class=\"nf\">.chain_get_finalized_head</span><span class=\"p\">()</span><span class=\"k\">.await</span><span class=\"nf\">.ok</span><span class=\"p\">()</span><span class=\"o\">?</span><span class=\"p\">;</span>\n\n<span class=\"k\">for</span> <span class=\"n\">_</span> <span class=\"k\">in</span> <span class=\"mi\">0</span><span class=\"o\">..</span><span class=\"n\">max_depth</span> <span class=\"p\">{</span>\n<span class=\"k\">let</span> <span class=\"n\">block</span> <span class=\"o\">=</span> <span class=\"k\">match</span> <span class=\"n\">client</span><span class=\"py\">.rpc</span><span class=\"nf\">.chain_get_block</span><span class=\"p\">(</span><span class=\"nf\">Some</span><span class=\"p\">(</span><span class=\"n\">hash</span><span class=\"p\">))</span><span class=\"k\">.await</span> <span class=\"p\">{</span>\n<span class=\"nf\">Ok</span><span class=\"p\">(</span><span class=\"nf\">Some</span><span class=\"p\">(</span><span class=\"n\">b</span><span class=\"p\">))</span> <span class=\"k\">=&gt;</span> <span class=\"n\">b</span><span class=\"p\">,</span>\n<span class=\"n\">_</span> <span class=\"k\">=&gt;</span> <span class=\"k\">return</span> <span class=\"nb\">None</span><span class=\"p\">,</span>\n<span class=\"p\">};</span>\n\n<span class=\"k\">for</span> <span class=\"n\">ext</span> <span class=\"k\">in</span> <span class=\"o\">&amp;</span><span class=\"n\">block</span><span class=\"py\">.block.extrinsics</span> <span class=\"p\">{</span>\n<span class=\"k\">let</span> <span class=\"n\">ext_hash</span> <span class=\"o\">=</span>\n<span class=\"nd\">format!</span><span class=\"p\">(</span><span class=\"s\">\"0x{}\"</span><span class=\"p\">,</span> <span class=\"nn\">hex</span><span class=\"p\">::</span><span class=\"nf\">encode</span><span class=\"p\">(</span><span class=\"nn\">sp_crypto_hashing</span><span class=\"p\">::</span><span class=\"nf\">blake2_256</span><span class=\"p\">(</span><span class=\"o\">&amp;</span><span class=\"n\">ext</span><span class=\"na\">.0</span><span class=\"p\">)));</span>\n<span class=\"k\">if</span> <span class=\"n\">ext_hash</span> <span class=\"o\">==</span> <span class=\"n\">extrinsic_hash_hex</span> <span class=\"p\">{</span>\n<span class=\"k\">return</span> <span class=\"nf\">Some</span><span class=\"p\">(</span><span class=\"nf\">hash_to_str</span><span class=\"p\">(</span><span class=\"n\">hash</span><span class=\"p\">));</span>\n<span class=\"p\">}</span>\n<span class=\"p\">}</span>\n\n<span class=\"k\">if</span> <span class=\"n\">block</span><span class=\"py\">.block.header.number</span> <span class=\"o\">==</span> <span class=\"mi\">0</span> <span class=\"p\">{</span>\n<span class=\"k\">return</span> <span class=\"nb\">None</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n<span class=\"n\">hash</span> <span class=\"o\">=</span> <span class=\"n\">block</span><span class=\"py\">.block.header.parent_hash</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n<span class=\"nb\">None</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>A few design notes:</p>\n\n<ul>\n<li>\n<strong>Bounded depth</strong> (64 blocks below the finalized head — comfortably past any plausible retraction-to-finalization window at 6-second blocks). An unbounded walk to genesis on a wrong hash would turn one bug into a different one.</li>\n<li>\n<strong>Extrinsic identity is just a hash.</strong> A Substrate extrinsic's hash is the blake2-256 of its encoded bytes, so the scan needs no indexer, no storage queries — fetch blocks, hash extrinsics, compare.</li>\n<li>\n<strong>Fail open, loudly.</strong> RPC errors during the scan log a warning and return <code>None</code> — the sender then reports the timeout as before. The fallback can only <em>upgrade</em> a false failure into a truthful success, never mask a real one.</li>\n</ul>\n\n<p>With the scan in place, the outcome reporting becomes honest:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight rust\"><code><span class=\"k\">let</span> <span class=\"n\">message</span> <span class=\"o\">=</span> <span class=\"k\">if</span> <span class=\"n\">finalized_block_hash</span><span class=\"nf\">.is_some</span><span class=\"p\">()</span> <span class=\"p\">{</span>\n<span class=\"k\">if</span> <span class=\"n\">finalized</span><span class=\"nf\">.is_some</span><span class=\"p\">()</span> <span class=\"p\">{</span> <span class=\"s\">\"FINALIZED\"</span> <span class=\"p\">}</span> <span class=\"k\">else</span> <span class=\"p\">{</span> <span class=\"s\">\"FINALIZED_AFTER_RETRACTION\"</span> <span class=\"p\">}</span>\n<span class=\"p\">}</span> <span class=\"k\">else</span> <span class=\"p\">{</span>\n<span class=\"s\">\"FAILED_TO_FINALIZE\"</span>\n<span class=\"p\">};</span>\n</code></pre>\n\n</div>\n\n\n\n<p><code>FINALIZED_AFTER_RETRACTION</code> exits zero — because the transaction <em>is on chain</em> — but it's a distinct message, so operators can see how often forks are eating their watch streams. And the retraction itself is no longer swallowed: the moment <code>NoLongerInBestBlock</code> arrives, the sender logs <code>tx retracted from best block; watching for re-inclusion</code>.</p>\n\n<p>The timeouts also became configurable (<code>MN_SEND_BEST_BLOCK_TIMEOUT</code> / <code>MN_SEND_FINALIZED_TIMEOUT</code>, in seconds) for slow or fault-injected environments — as environment variables rather than CLI flags, partly to keep the change surface away from other in-flight CLI work.</p>\n\n<h2>\n\n\nTesting what you can, admitting what you can't\n</h2>\n\n<p>Here's the honest part: <strong>you cannot deterministically force a fork retraction in CI.</strong> Retractions emerge from validator timing races; no RPC call produces one on demand. A test suite that claims to cover the retraction path end-to-end would be lying the same way the exit code was.</p>\n\n<p>What you <em>can</em> do is split the fix so the untestable part is trivially small (a timeout branch calling one function) and the testable part carries the logic. The e2e test for the scan uses a trick worth stealing: <strong>every Substrate block already contains a transaction you didn't send</strong> — the timestamp inherent that the block author injects. So the test:</p>\n\n<ol>\n<li>runs against a real node, grabs a finalized block, and takes its timestamp-inherent extrinsic;</li>\n<li>hashes those bytes and asserts <code>find_in_finalized_chain</code> locates that extrinsic at exactly that block (positive case, using an extrinsic the test never had to submit);</li>\n<li>asserts a fabricated hash comes back <code>None</code> after the bounded walk (negative case).</li>\n</ol>\n\n<p>No fault injection, no mocked RPC — the scan is exercised against genuinely finalized blocks, and the only unverified wiring is a five-line <code>match</code>.</p>\n\n<h2>\n\n\nTakeaways\n</h2>\n\n<ul>\n<li>\n<strong>Watch streams are best-effort; finality is a fact.</strong> When a stream and the chain can disagree, reconcile against the chain before reporting failure.</li>\n<li>\n<strong>Exit codes are API.</strong> If a caller might retry based on your answer, a false negative is a correctness bug, not a cosmetic one.</li>\n<li>\n<strong>Never swallow the interesting event.</strong> The silent <code>NoLongerInBestBlock</code> was the difference between a 5-minute diagnosis and a mystery.</li>\n<li>\n<strong>Bound your fallbacks.</strong> A recovery path with no depth limit is a new incident waiting to happen.</li>\n<li>\n<strong>Be honest about test coverage.</strong> Shrink the untestable wiring instead of pretending the test forces the failure.</li>\n</ul>\n\n<p>The fix is open for review at <a href=\"https://github.com/midnightntwrk/midnight-node/pull/1927\" rel=\"noopener noreferrer\">midnightntwrk/midnight-node#1927</a> — feedback welcome. If you're building on <a href=\"https://midnight.network\" rel=\"noopener noreferrer\">Midnight</a> and your tooling watches transactions, go check what your code does with <code>NoLongerInBestBlock</code>. There's a decent chance the answer is \"nothing,\" and now you know why that matters.</p>","content":"Your transaction tool reports FAILED_TO_FINALIZE\n. Your CI marks the job red. Your retry logic fires. But the block explorer says the transaction finalized 22 seconds after you sent it.\nThe exit code lied.\nThis is a debugging story from midnight-node — Midnight's Substrate-based node — about how a transaction watcher loses track of a transaction during a chain fork, why the failure is invisible in tests, and the pattern that fixes it: on timeout, ask the chain itself, not the stream that was supposed to tell you about it. The fix landed as PR #1927 for issue #1854.\nForks are normal, your tooling forgets that\nMidnight produces blocks with AURA every 6 seconds and finalizes them with GRANDPA a couple of blocks later. Between production and finality, the chain is allowed to disagree with itself: two validators can build competing blocks, and one branch eventually wins. A transaction included in the losing branch is retracted — kicked back to the transaction pool — and normally re-included in a block on the winning branch a few seconds later.\nFrom the chain's perspective this is routine housekeeping. From your tooling's perspective, it's a trap.\nThe toolkit's send\ncommand watches a submitted transaction with subxt's watch stream, which emits status events: InBestBlock\n, NoLongerInBestBlock\n, InFinalizedBlock\n, and so on. The sender's logic was:\n- Wait for the tx to appear in a best block.\n- Then wait (with a timeout) for\nInFinalizedBlock\n. - Timeout expires → report\nFAILED_TO_FINALIZE\n, exit non-zero.\nHere's the failure mode from the issue, reconstructed from logs:\nt+0s tx submitted, InBestBlock (block A)\nt+6s fork: block A retracted → NoLongerInBestBlock\nt+8s tx re-included in block B on the winning branch\nt+22s block B finalized ✅ tx is permanently on chain\nt+60s watcher's finalization timeout expires\n→ exit: FAILED_TO_FINALIZE ❌\nTwo things went wrong. First, NoLongerInBestBlock\nwas being silently swallowed — the logs showed nothing at the moment the interesting thing happened. Second, after the retraction, the watch stream never surfaced the re-inclusion; the watcher waited out its timeout on a dead branch while the transaction quietly finalized elsewhere.\nWhy a wrong exit code is worse than a crash\nIf this tool only fed dashboards, a spurious failure would cost an engineer an eyebrow raise. But callers make decisions based on exit codes. The sharpest edge in our case: reward-claim transactions with at-most-once semantics. A wrapper script sees the non-zero exit, assumes the claim never landed, and retries — resubmitting an operation that already succeeded. The exit code isn't diagnostics; it's an API, and it was returning wrong answers.\nThis is a general lesson worth internalizing: any tool that reports transaction outcomes is part of someone's correctness argument. Treat its outputs with the same rigor as consensus code.\nThe fix: the chain gets the last word\nThe watch stream is a convenience, not a source of truth. The source of truth is the finalized chain, and it's sitting right there behind an RPC. So on watch timeout, the sender now scans finalized blocks, newest first, looking for the extrinsic hash:\npub async fn find_in_finalized_chain(\nclient: &MidnightNodeClient,\nextrinsic_hash_hex: &str,\nmax_depth: u32,\n) -> Option<String> {\nlet mut hash = client.rpc.chain_get_finalized_head().await.ok()?;\nfor _ in 0..max_depth {\nlet block = match client.rpc.chain_get_block(Some(hash)).await {\nOk(Some(b)) => b,\n_ => return None,\n};\nfor ext in &block.block.extrinsics {\nlet ext_hash =\nformat!(\"0x{}\", hex::encode(sp_crypto_hashing::blake2_256(&ext.0)));\nif ext_hash == extrinsic_hash_hex {\nreturn Some(hash_to_str(hash));\n}\n}\nif block.block.header.number == 0 {\nreturn None;\n}\nhash = block.block.header.parent_hash;\n}\nNone\n}\nA few design notes:\n- Bounded depth (64 blocks below the finalized head — comfortably past any plausible retraction-to-finalization window at 6-second blocks). An unbounded walk to genesis on a wrong hash would turn one bug into a different one.\n- Extrinsic identity is just a hash. A Substrate extrinsic's hash is the blake2-256 of its encoded bytes, so the scan needs no indexer, no storage queries — fetch blocks, hash extrinsics, compare.\n-\nFail open, loudly. RPC errors during the scan log a warning and return\nNone\n— the sender then reports the timeout as before. The fallback can only upgrade a false failure into a truthful success, never mask a real one.\nWith the scan in place, the outcome reporting becomes honest:\nlet message = if finalized_block_hash.is_some() {\nif finalized.is_some() { \"FINALIZED\" } else { \"FINALIZED_AFTER_RETRACTION\" }\n} else {\n\"FAILED_TO_FINALIZE\"\n};\nFINALIZED_AFTER_RETRACTION\nexits zero — because the transaction is on chain — but it's a distinct message, so operators can see how often forks are eating their watch streams. And the retraction itself is no longer swallowed: the moment NoLongerInBestBlock\narrives, the sender logs tx retracted from best block; watching for re-inclusion\n.\nThe timeouts also became configurable (MN_SEND_BEST_BLOCK_TIMEOUT\n/ MN_SEND_FINALIZED_TIMEOUT\n, in seconds) for slow or fault-injected environments — as environment variables rather than CLI flags, partly to keep the change surface away from other in-flight CLI work.\nTesting what you can, admitting what you can't\nHere's the honest part: you cannot deterministically force a fork retraction in CI. Retractions emerge from validator timing races; no RPC call produces one on demand. A test suite that claims to cover the retraction path end-to-end would be lying the same way the exit code was.\nWhat you can do is split the fix so the untestable part is trivially small (a timeout branch calling one function) and the testable part carries the logic. The e2e test for the scan uses a trick worth stealing: every Substrate block already contains a transaction you didn't send — the timestamp inherent that the block author injects. So the test:\n- runs against a real node, grabs a finalized block, and takes its timestamp-inherent extrinsic;\n- hashes those bytes and asserts\nfind_in_finalized_chain\nlocates that extrinsic at exactly that block (positive case, using an extrinsic the test never had to submit); - asserts a fabricated hash comes back\nNone\nafter the bounded walk (negative case).\nNo fault injection, no mocked RPC — the scan is exercised against genuinely finalized blocks, and the only unverified wiring is a five-line match\n.\nTakeaways\n- Watch streams are best-effort; finality is a fact. When a stream and the chain can disagree, reconcile against the chain before reporting failure.\n- Exit codes are API. If a caller might retry based on your answer, a false negative is a correctness bug, not a cosmetic one.\n-\nNever swallow the interesting event. The silent\nNoLongerInBestBlock\nwas the difference between a 5-minute diagnosis and a mystery. - Bound your fallbacks. A recovery path with no depth limit is a new incident waiting to happen.\n- Be honest about test coverage. Shrink the untestable wiring instead of pretending the test forces the failure.\nThe fix is open for review at midnightntwrk/midnight-node#1927 — feedback welcome. If you're building on Midnight and your tooling watches transactions, go check what your code does with NoLongerInBestBlock\n. There's a decent chance the answer is \"nothing,\" and now you know why that matters.\nTop comments (0)","published":"Sat, 25 Jul 2026 14:58:57 +0000","author":"Wes Huber","guid":"https://dev.to/wbaxterh/when-the-exit-code-lies-fork-retractions-lost-transactions-and-trusting-the-chain-in-1b1h","created_at":"2026-07-25T17:27:41.996322","last_synchronized":"2026-07-25T17:27:41.996322","sentiment":{"sentiment":"Negative","score":-0.9968,"details":{"neg":0.107,"neu":0.83,"pos":0.063,"compound":-0.9968}}},{"feed_name":"DEV Community","feed_url":"https://dev.to/feed","title":"How to Control Page Breaks in HTML to PDF Output","link":"https://dev.to/accreditly/how-to-control-page-breaks-in-html-to-pdf-output-1maj","description":"<p>You render an invoice that looks right in the browser, open the PDF and find line item 14 cut in half by a page boundary. The description sits at the foot of page one and the amount sits at the top of page two. Or a certificate splits down the middle. Or a section heading is stranded alone at the bottom of a page while its content starts overleaf.</p>\n\n<p>Chrome is doing exactly what it was asked to do: fill a page, start another, repeat. It has no opinion about which parts of your document belong together. You supply that opinion in CSS, and it takes about six lines. This post originally appeared on the HTML to Image blog as <a href=\"https://html2img.com/articles/html-to-pdf-page-breaks/\" rel=\"noopener noreferrer\">How to control page breaks in HTML to PDF output</a>.</p>\n\n<p>Everything here applies to any Chrome based pipeline: Puppeteer's <code>page.pdf()</code>, Playwright, or a rendering API. The properties are standard CSS, not vendor extensions.</p>\n\n<h2>\n\n\nThe three properties that do the work\n</h2>\n\n<p>There is no <code>avoid_splitting_rows</code> flag on any HTML to PDF tool worth using, because the renderer is a browser and browsers already have a standard for this. CSS Fragmentation gives you three properties:</p>\n\n<div class=\"table-wrapper-paragraph\"><table>\n<thead>\n<tr>\n<th>Property</th>\n<th>What it does</th>\n<th>Values you will actually use</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>break-inside</code></td>\n<td>Controls splitting <em>within</em> an element</td>\n<td>\n<code>avoid</code>, <code>auto</code>\n</td>\n</tr>\n<tr>\n<td><code>break-before</code></td>\n<td>Forces or prevents a break <em>before</em> an element</td>\n<td>\n<code>page</code>, <code>avoid</code>, <code>auto</code>\n</td>\n</tr>\n<tr>\n<td><code>break-after</code></td>\n<td>Forces or prevents a break <em>after</em> an element</td>\n<td>\n<code>page</code>, <code>avoid</code>, <code>auto</code>\n</td>\n</tr>\n</tbody>\n</table></div>\n\n<p>The older <code>page-break-inside</code>, <code>page-break-before</code> and <code>page-break-after</code> still work. The spec defines them as legacy aliases and Chrome maps them onto the modern equivalents, so an existing stylesheet does not need rewriting. New code should use the short forms.</p>\n\n<p>These properties do nothing on screen. Continuous media has no page boundaries to avoid, so the browser computes them and moves on. They only mean something once the content is being cut into pages.</p>\n\n<h2>\n\n\nPut them in your normal stylesheet, not in <a class=\"mentioned-user\" href=\"https://dev.to/media\">@media</a> print\n</h2>\n\n<p>This is the bit that catches people out.</p>\n\n<p>A rule hidden inside <code>@media print</code> only fires if the renderer is actually emulating print media. Puppeteer's <code>page.pdf()</code> does emulate print, so <code>@media print</code> blocks apply there. Plenty of rendering APIs do not, including the one I use, which renders PDFs with your screen CSS so the PDF matches the PNG of the same input.</p>\n\n<p>The fragmentation properties do not care either way. They are ordinary declarations that sit in the cascade like any other, get ignored on continuous media, and take effect when the document paginates. So write them unconditionally:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight css\"><code><span class=\"c\">/* Applied when the document paginates. Inert in the browser. */</span>\n<span class=\"nc\">.invoice-row</span><span class=\"o\">,</span>\n<span class=\"nc\">.line-items</span> <span class=\"nt\">tr</span><span class=\"o\">,</span>\n<span class=\"nc\">.card</span><span class=\"o\">,</span>\n<span class=\"nt\">figure</span> <span class=\"p\">{</span>\n<span class=\"nl\">break-inside</span><span class=\"p\">:</span> <span class=\"nb\">avoid</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Nothing about the page changes in a browser tab. Here is what it buys you in the PDF: six 300px cards with no rules at all fill each page to the edge and split whichever card straddles the boundary. Add <code>break-inside: avoid</code> to <code>.card</code> and the renderer fits three per page, leaves the leftover space at the foot of the page and starts card four cleanly on the next one.</p>\n\n<h2>\n\n\nKeeping an invoice together\n</h2>\n\n<p>Line items are the classic failure. A row that splits is not just ugly, it is a document where a quantity and its price appear on different pages, which is the sort of thing an accounts department notices before it notices your typography.<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight css\"><code><span class=\"nc\">.line-items</span> <span class=\"nt\">tr</span> <span class=\"p\">{</span>\n<span class=\"nl\">break-inside</span><span class=\"p\">:</span> <span class=\"nb\">avoid</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n\n<span class=\"c\">/* Never leave a heading alone at the foot of a page */</span>\n<span class=\"nt\">h2</span><span class=\"o\">,</span>\n<span class=\"nt\">h3</span> <span class=\"p\">{</span>\n<span class=\"nl\">break-after</span><span class=\"p\">:</span> <span class=\"nb\">avoid</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n\n<span class=\"c\">/* Totals, payment terms and the signature block stay as one unit */</span>\n<span class=\"nc\">.totals</span><span class=\"o\">,</span>\n<span class=\"nc\">.payment-terms</span><span class=\"o\">,</span>\n<span class=\"nc\">.signature</span> <span class=\"p\">{</span>\n<span class=\"nl\">break-inside</span><span class=\"p\">:</span> <span class=\"nb\">avoid</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p><code>break-after: avoid</code> on headings is the quiet win. It tells the renderer that a heading may not be the last thing on a page, so if the following block will not fit, the heading travels with it. Two declarations remove an entire class of report that looks like it was assembled by accident.</p>\n\n<h2>\n\n\nForcing a break where you want one\n</h2>\n\n<p><code>break-before: page</code> starts a new page unconditionally. It is how you get one certificate per attendee, one statement per customer or one section per page out of a single render:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight html\"><code><span class=\"nt\">&lt;style&gt;</span>\n<span class=\"nc\">.sheet</span> <span class=\"p\">{</span> <span class=\"nl\">break-before</span><span class=\"p\">:</span> <span class=\"n\">page</span><span class=\"p\">;</span> <span class=\"p\">}</span>\n<span class=\"nc\">.sheet</span><span class=\"nd\">:first-child</span> <span class=\"p\">{</span> <span class=\"nl\">break-before</span><span class=\"p\">:</span> <span class=\"nb\">auto</span><span class=\"p\">;</span> <span class=\"p\">}</span>\n<span class=\"nt\">&lt;/style&gt;</span>\n\n<span class=\"nt\">&lt;div</span> <span class=\"na\">class=</span><span class=\"s\">\"sheet\"</span><span class=\"nt\">&gt;</span>…certificate for Priya…<span class=\"nt\">&lt;/div&gt;</span>\n<span class=\"nt\">&lt;div</span> <span class=\"na\">class=</span><span class=\"s\">\"sheet\"</span><span class=\"nt\">&gt;</span>…certificate for Tom…<span class=\"nt\">&lt;/div&gt;</span>\n<span class=\"nt\">&lt;div</span> <span class=\"na\">class=</span><span class=\"s\">\"sheet\"</span><span class=\"nt\">&gt;</span>…certificate for Andreas…<span class=\"nt\">&lt;/div&gt;</span>\n</code></pre>\n\n</div>\n\n\n\n<p>The <code>:first-child</code> reset matters. Without it the first sheet forces a break before itself and you ship a PDF with a blank first page. I have seen that one survive into production more than once.</p>\n\n<p>It also turns a batch job into a single render. One request that returns a 40 page document beats 40 requests, and every page is still real vector text rather than a picture of a certificate.</p>\n\n<h2>\n\n\nDo not assume table headers repeat\n</h2>\n\n<p>Chrome is supposed to redraw <code>&lt;thead&gt;</code> at the top of every page a table spans. Test that assumption against your own pipeline before you rely on it. When I measured this on the renderer I use, the header appeared once at the start and every page after that carried bare rows.</p>\n\n<p>The fix is to chunk in your template rather than hope in your CSS. Split the rows into page sized groups and give each group its own table:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"kd\">const</span> <span class=\"nx\">PER_PAGE</span> <span class=\"o\">=</span> <span class=\"mi\">24</span><span class=\"p\">;</span>\n\n<span class=\"kd\">const</span> <span class=\"nx\">chunks</span> <span class=\"o\">=</span> <span class=\"p\">[];</span>\n<span class=\"k\">for </span><span class=\"p\">(</span><span class=\"kd\">let</span> <span class=\"nx\">i</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">;</span> <span class=\"nx\">i</span> <span class=\"o\">&lt;</span> <span class=\"nx\">rows</span><span class=\"p\">.</span><span class=\"nx\">length</span><span class=\"p\">;</span> <span class=\"nx\">i</span> <span class=\"o\">+=</span> <span class=\"nx\">PER_PAGE</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n<span class=\"nx\">chunks</span><span class=\"p\">.</span><span class=\"nf\">push</span><span class=\"p\">(</span><span class=\"nx\">rows</span><span class=\"p\">.</span><span class=\"nf\">slice</span><span class=\"p\">(</span><span class=\"nx\">i</span><span class=\"p\">,</span> <span class=\"nx\">i</span> <span class=\"o\">+</span> <span class=\"nx\">PER_PAGE</span><span class=\"p\">));</span>\n<span class=\"p\">}</span>\n\n<span class=\"kd\">const</span> <span class=\"nx\">html</span> <span class=\"o\">=</span> <span class=\"nx\">chunks</span><span class=\"p\">.</span><span class=\"nf\">map</span><span class=\"p\">((</span><span class=\"nx\">chunk</span><span class=\"p\">,</span> <span class=\"nx\">index</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span> <span class=\"s2\">`\n&lt;table class=\"ledger</span><span class=\"p\">${</span><span class=\"nx\">index</span> <span class=\"o\">&gt;</span> <span class=\"mi\">0</span> <span class=\"p\">?</span> <span class=\"dl\">'</span><span class=\"s1\"> continued</span><span class=\"dl\">'</span> <span class=\"p\">:</span> <span class=\"dl\">''</span><span class=\"p\">}</span><span class=\"s2\">\"&gt;\n&lt;thead&gt;\n&lt;tr&gt;&lt;th&gt;Date&lt;/th&gt;&lt;th&gt;Reference&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;th&gt;Amount&lt;/th&gt;&lt;/tr&gt;\n&lt;/thead&gt;\n&lt;tbody&gt;\n</span><span class=\"p\">${</span><span class=\"nx\">chunk</span><span class=\"p\">.</span><span class=\"nf\">map</span><span class=\"p\">(</span><span class=\"nx\">r</span> <span class=\"o\">=&gt;</span> <span class=\"s2\">`\n&lt;tr&gt;\n&lt;td&gt;</span><span class=\"p\">${</span><span class=\"nx\">r</span><span class=\"p\">.</span><span class=\"nx\">date</span><span class=\"p\">}</span><span class=\"s2\">&lt;/td&gt;&lt;td&gt;</span><span class=\"p\">${</span><span class=\"nx\">r</span><span class=\"p\">.</span><span class=\"nx\">ref</span><span class=\"p\">}</span><span class=\"s2\">&lt;/td&gt;&lt;td&gt;</span><span class=\"p\">${</span><span class=\"nx\">r</span><span class=\"p\">.</span><span class=\"nx\">description</span><span class=\"p\">}</span><span class=\"s2\">&lt;/td&gt;&lt;td&gt;</span><span class=\"p\">${</span><span class=\"nx\">r</span><span class=\"p\">.</span><span class=\"nx\">amount</span><span class=\"p\">}</span><span class=\"s2\">&lt;/td&gt;\n&lt;/tr&gt;`</span><span class=\"p\">).</span><span class=\"nf\">join</span><span class=\"p\">(</span><span class=\"dl\">''</span><span class=\"p\">)}</span><span class=\"s2\">\n&lt;/tbody&gt;\n&lt;/table&gt;\n`</span><span class=\"p\">).</span><span class=\"nf\">join</span><span class=\"p\">(</span><span class=\"dl\">''</span><span class=\"p\">);</span>\n</code></pre>\n\n</div>\n\n\n\n\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight css\"><code><span class=\"nc\">.ledger.continued</span> <span class=\"p\">{</span>\n<span class=\"nl\">break-before</span><span class=\"p\">:</span> <span class=\"n\">page</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n\n<span class=\"nc\">.ledger</span> <span class=\"nt\">tr</span> <span class=\"p\">{</span>\n<span class=\"nl\">break-inside</span><span class=\"p\">:</span> <span class=\"nb\">avoid</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>You now control exactly where the table breaks, every page carries its own header, and you can label the continued ones. Pick <code>PER_PAGE</code> by rendering once and counting, then leave it alone.</p>\n\n<h2>\n\n\nRunning footers with fixed positioning\n</h2>\n\n<p>Chrome repeats fixed position elements on every page of a printed document, which gives you a running footer without the <code>@page</code> margin box machinery that browsers never implemented:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight css\"><code><span class=\"nc\">.doc-footer</span> <span class=\"p\">{</span>\n<span class=\"nl\">position</span><span class=\"p\">:</span> <span class=\"nb\">fixed</span><span class=\"p\">;</span>\n<span class=\"nl\">bottom</span><span class=\"p\">:</span> <span class=\"m\">0</span><span class=\"p\">;</span>\n<span class=\"nl\">left</span><span class=\"p\">:</span> <span class=\"m\">0</span><span class=\"p\">;</span>\n<span class=\"nl\">right</span><span class=\"p\">:</span> <span class=\"m\">0</span><span class=\"p\">;</span>\n<span class=\"nl\">padding</span><span class=\"p\">:</span> <span class=\"m\">12px</span> <span class=\"m\">24px</span><span class=\"p\">;</span>\n<span class=\"nl\">border-top</span><span class=\"p\">:</span> <span class=\"m\">1px</span> <span class=\"nb\">solid</span> <span class=\"m\">#e2e8f0</span><span class=\"p\">;</span>\n<span class=\"nl\">font-size</span><span class=\"p\">:</span> <span class=\"m\">12px</span><span class=\"p\">;</span>\n<span class=\"nl\">color</span><span class=\"p\">:</span> <span class=\"m\">#64748b</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n\n<span class=\"c\">/* Reserve the space so content never slides under it */</span>\n<span class=\"nt\">body</span> <span class=\"p\">{</span>\n<span class=\"nl\">padding-bottom</span><span class=\"p\">:</span> <span class=\"m\">64px</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>That covers a company registration number, a document reference or a confidentiality line on every page.</p>\n\n<p>What you cannot get this way is a page number. Page counters live in <code>@page</code> margin boxes, which Chrome has never supported, so <code>content: counter(page)</code> renders nothing. Number the sheets yourself in the template where you already know how many there are, or reach for a print focused engine like Paged.js or a dedicated PDF service.</p>\n\n<h2>\n\n\nFlex and grid children fragment fine now\n</h2>\n\n<p>There is an old reflex that says flexbox and grid children ignore <code>break-inside</code>, so people rewrite a working card layout in floats or tables before generating a PDF. That reflex is out of date. Modern Chrome fragments both properly, and a column of flex children or grid items with <code>break-inside: avoid</code> paginates the same way block elements do: the item that will not fit moves to the next page whole.</p>\n\n<p>I tested this rather than trusting my memory of it, and so should you, because the answer changed somewhere around the LayoutNG work and most of the advice online predates that.</p>\n\n<h2>\n\n\nThe starter stylesheet\n</h2>\n\n<p>Drop this into any document you intend to render as a PDF and most pagination complaints disappear before anyone files them:<br />\n</p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight css\"><code><span class=\"nt\">tr</span><span class=\"o\">,</span>\n<span class=\"nt\">figure</span><span class=\"o\">,</span>\n<span class=\"nt\">blockquote</span><span class=\"o\">,</span>\n<span class=\"nc\">.card</span><span class=\"o\">,</span>\n<span class=\"nc\">.totals</span><span class=\"o\">,</span>\n<span class=\"nc\">.signature</span> <span class=\"p\">{</span>\n<span class=\"nl\">break-inside</span><span class=\"p\">:</span> <span class=\"nb\">avoid</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n\n<span class=\"nt\">h1</span><span class=\"o\">,</span> <span class=\"nt\">h2</span><span class=\"o\">,</span> <span class=\"nt\">h3</span><span class=\"o\">,</span> <span class=\"nt\">h4</span> <span class=\"p\">{</span>\n<span class=\"nl\">break-after</span><span class=\"p\">:</span> <span class=\"nb\">avoid</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n\n<span class=\"nc\">.sheet</span> <span class=\"p\">{</span>\n<span class=\"nl\">break-before</span><span class=\"p\">:</span> <span class=\"n\">page</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n\n<span class=\"nc\">.sheet</span><span class=\"nd\">:first-child</span> <span class=\"p\">{</span>\n<span class=\"nl\">break-before</span><span class=\"p\">:</span> <span class=\"nb\">auto</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre>\n\n</div>\n\n\n\n<p>Then render the real document, not a sample of three rows, and look at every page boundary. Pagination bugs only appear at length, which is why they tend to ship.</p>\n\n<p>The full version of this post, including the notes on page size and what happens to content wider than the page, is on the HTML to Image blog: <a href=\"https://html2img.com/articles/html-to-pdf-page-breaks/\" rel=\"noopener noreferrer\">How to control page breaks in HTML to PDF output</a>.</p>\n\n<p>What is your worst PDF pagination bug? Mine is still the blank first page that nobody noticed for two months. Share yours in the comments.</p>","content":"You render an invoice that looks right in the browser, open the PDF and find line item 14 cut in half by a page boundary. The description sits at the foot of page one and the amount sits at the top of page two. Or a certificate splits down the middle. Or a section heading is stranded alone at the bottom of a page while its content starts overleaf.\nChrome is doing exactly what it was asked to do: fill a page, start another, repeat. It has no opinion about which parts of your document belong together. You supply that opinion in CSS, and it takes about six lines. This post originally appeared on the HTML to Image blog as How to control page breaks in HTML to PDF output.\nEverything here applies to any Chrome based pipeline: Puppeteer's page.pdf()\n, Playwright, or a rendering API. The properties are standard CSS, not vendor extensions.\nThe three properties that do the work\nThere is no avoid_splitting_rows\nflag on any HTML to PDF tool worth using, because the renderer is a browser and browsers already have a standard for this. CSS Fragmentation gives you three properties:\n| Property | What it does | Values you will actually use |\n|---|---|---|\nbreak-inside |\nControls splitting within an element |\navoid , auto\n|\nbreak-before |\nForces or prevents a break before an element |\npage , avoid , auto\n|\nbreak-after |\nForces or prevents a break after an element |\npage , avoid , auto\n|\nThe older page-break-inside\n, page-break-before\nand page-break-after\nstill work. The spec defines them as legacy aliases and Chrome maps them onto the modern equivalents, so an existing stylesheet does not need rewriting. New code should use the short forms.\nThese properties do nothing on screen. Continuous media has no page boundaries to avoid, so the browser computes them and moves on. They only mean something once the content is being cut into pages.\nPut them in your normal stylesheet, not in @media print\nThis is the bit that catches people out.\nA rule hidden inside @media print\nonly fires if the renderer is actually emulating print media. Puppeteer's page.pdf()\ndoes emulate print, so @media print\nblocks apply there. Plenty of rendering APIs do not, including the one I use, which renders PDFs with your screen CSS so the PDF matches the PNG of the same input.\nThe fragmentation properties do not care either way. They are ordinary declarations that sit in the cascade like any other, get ignored on continuous media, and take effect when the document paginates. So write them unconditionally:\n/* Applied when the document paginates. Inert in the browser. */\n.invoice-row,\n.line-items tr,\n.card,\nfigure {\nbreak-inside: avoid;\n}\nNothing about the page changes in a browser tab. Here is what it buys you in the PDF: six 300px cards with no rules at all fill each page to the edge and split whichever card straddles the boundary. Add break-inside: avoid\nto .card\nand the renderer fits three per page, leaves the leftover space at the foot of the page and starts card four cleanly on the next one.\nKeeping an invoice together\nLine items are the classic failure. A row that splits is not just ugly, it is a document where a quantity and its price appear on different pages, which is the sort of thing an accounts department notices before it notices your typography.\n.line-items tr {\nbreak-inside: avoid;\n}\n/* Never leave a heading alone at the foot of a page */\nh2,\nh3 {\nbreak-after: avoid;\n}\n/* Totals, payment terms and the signature block stay as one unit */\n.totals,\n.payment-terms,\n.signature {\nbreak-inside: avoid;\n}\nbreak-after: avoid\non headings is the quiet win. It tells the renderer that a heading may not be the last thing on a page, so if the following block will not fit, the heading travels with it. Two declarations remove an entire class of report that looks like it was assembled by accident.\nForcing a break where you want one\nbreak-before: page\nstarts a new page unconditionally. It is how you get one certificate per attendee, one statement per customer or one section per page out of a single render:\n<style>\n.sheet { break-before: page; }\n.sheet:first-child { break-before: auto; }\n</style>\n<div class=\"sheet\">…certificate for Priya…</div>\n<div class=\"sheet\">…certificate for Tom…</div>\n<div class=\"sheet\">…certificate for Andreas…</div>\nThe :first-child\nreset matters. Without it the first sheet forces a break before itself and you ship a PDF with a blank first page. I have seen that one survive into production more than once.\nIt also turns a batch job into a single render. One request that returns a 40 page document beats 40 requests, and every page is still real vector text rather than a picture of a certificate.\nDo not assume table headers repeat\nChrome is supposed to redraw <thead>\nat the top of every page a table spans. Test that assumption against your own pipeline before you rely on it. When I measured this on the renderer I use, the header appeared once at the start and every page after that carried bare rows.\nThe fix is to chunk in your template rather than hope in your CSS. Split the rows into page sized groups and give each group its own table:\nconst PER_PAGE = 24;\nconst chunks = [];\nfor (let i = 0; i < rows.length; i += PER_PAGE) {\nchunks.push(rows.slice(i, i + PER_PAGE));\n}\nconst html = chunks.map((chunk, index) => `\n<table class=\"ledger${index > 0 ? ' continued' : ''}\">\n<thead>\n<tr><th>Date</th><th>Reference</th><th>Description</th><th>Amount</th></tr>\n</thead>\n<tbody>\n${chunk.map(r => `\n<tr>\n<td>${r.date}</td><td>${r.ref}</td><td>${r.description}</td><td>${r.amount}</td>\n</tr>`).join('')}\n</tbody>\n</table>\n`).join('');\n.ledger.continued {\nbreak-before: page;\n}\n.ledger tr {\nbreak-inside: avoid;\n}\nYou now control exactly where the table breaks, every page carries its own header, and you can label the continued ones. Pick PER_PAGE\nby rendering once and counting, then leave it alone.\nRunning footers with fixed positioning\nChrome repeats fixed position elements on every page of a printed document, which gives you a running footer without the @page\nmargin box machinery that browsers never implemented:\n.doc-footer {\nposition: fixed;\nbottom: 0;\nleft: 0;\nright: 0;\npadding: 12px 24px;\nborder-top: 1px solid #e2e8f0;\nfont-size: 12px;\ncolor: #64748b;\n}\n/* Reserve the space so content never slides under it */\nbody {\npadding-bottom: 64px;\n}\nThat covers a company registration number, a document reference or a confidentiality line on every page.\nWhat you cannot get this way is a page number. Page counters live in @page\nmargin boxes, which Chrome has never supported, so content: counter(page)\nrenders nothing. Number the sheets yourself in the template where you already know how many there are, or reach for a print focused engine like Paged.js or a dedicated PDF service.\nFlex and grid children fragment fine now\nThere is an old reflex that says flexbox and grid children ignore break-inside\n, so people rewrite a working card layout in floats or tables before generating a PDF. That reflex is out of date. Modern Chrome fragments both properly, and a column of flex children or grid items with break-inside: avoid\npaginates the same way block elements do: the item that will not fit moves to the next page whole.\nI tested this rather than trusting my memory of it, and so should you, because the answer changed somewhere around the LayoutNG work and most of the advice online predates that.\nThe starter stylesheet\nDrop this into any document you intend to render as a PDF and most pagination complaints disappear before anyone files them:\ntr,\nfigure,\nblockquote,\n.card,\n.totals,\n.signature {\nbreak-inside: avoid;\n}\nh1, h2, h3, h4 {\nbreak-after: avoid;\n}\n.sheet {\nbreak-before: page;\n}\n.sheet:first-child {\nbreak-before: auto;\n}\nThen render the real document, not a sample of three rows, and look at every page boundary. Pagination bugs only appear at length, which is why they tend to ship.\nThe full version of this post, including the notes on page size and what happens to content wider than the page, is on the HTML to Image blog: How to control page breaks in HTML to PDF output.\nWhat is your worst PDF pagination bug? Mine is still the blank first page that nobody noticed for two months. Share yours in the comments.\nTop comments (0)","published":"Sat, 25 Jul 2026 14:54:41 +0000","author":"Accreditly","guid":"https://dev.to/accreditly/how-to-control-page-breaks-in-html-to-pdf-output-1maj","created_at":"2026-07-25T17:27:41.996322","last_synchronized":"2026-07-25T17:27:41.996322","sentiment":{"sentiment":"Negative","score":-0.9473,"details":{"neg":0.032,"neu":0.943,"pos":0.025,"compound":-0.9473}}},{"feed_name":"DEV Community","feed_url":"https://dev.to/feed","title":"HBM4: The Memory Revolution Driving AI’s Next Frontier","link":"https://dev.to/snehaliteng/hbm4-the-memory-revolution-driving-ais-next-frontier-18bm","description":"<div class=\"crayons-card c-embed text-styles text-styles--secondary\">\n<div class=\"c-embed__content\">\n<div class=\"c-embed__body flex items-center justify-between\">\n<a class=\"c-link fw-bold flex items-center\" href=\"https://snehaliteng.github.io/blog/article.html?slug=hbm4-the-memory-revolution-driving-ais-next-frontier\" rel=\"noopener noreferrer\">\n<span class=\"mr-2\">snehaliteng.github.io</span>\n\n\n</a>\n</div>\n</div>\n</div>\n\n\n<p>🔮 HBM4: The Memory Powering AI’s Next Leap<br />\nThe future of AI and HPC isn’t just about compute—it’s about memory. Enter HBM4 (High Bandwidth Memory 4), the next-generation standard that doubles the throughput of HBM3e and sets the stage for trillion-parameter models, multimodal reasoning, and scientific breakthroughs.</p>\n\n<p>🚀 What Makes HBM4 Different?<br />\nBandwidth Explosion: Up to 3.3 TB/s per stack, thanks to a 2048-bit bus.</p>\n\n<p>Higher Capacity: 36–48 GB per stack, reducing the need for multiple stacks.</p>\n\n<p>Efficiency Gains: &gt;20% better energy per bit compared to HBM3e.</p>\n\n<p>Future-Proof Design: Tailored for AI workloads where HBM3 hits its limits.</p>\n\n<p>⚡ Key Specs at a Glance<br />\nFeature HBM3e HBM4<br />\nBandwidth per stack ~1.2 TB/s 2.8–3.3 TB/s<br />\nBus width 1024-bit2048-bit<br />\nCapacity24–36 GB36–48 GB<br />\nPin speed 9.2 Gb/s11–13 Gb/s</p>\n\n<p>🌐 Why It Matters<br />\nAI Training &amp; Inference: Enables ultra-long context windows and real-time multimodal reasoning.</p>\n\n<p>HPC &amp; Research: Climate modeling, drug discovery, and quantum simulations at unprecedented scale.</p>\n\n<p>Enterprise &amp; Cloud: Hyperscale clusters ready for next-gen workloads.</p>\n\n<p>⚠️ Challenges Ahead<br />\nPackaging Costs: 2.5D/3D integration raises manufacturing complexity.</p>\n\n<p>Thermal Management: Higher density demands advanced cooling solutions.</p>\n\n<p>Supply Chain: Ramp-up expected in 2026–2027, with limited availability initially.</p>\n\n<p>💡 The Big Picture<br />\nHBM4 isn’t just an upgrade—it’s the memory foundation for the AI era. With double the bandwidth, larger capacities, and better efficiency, it empowers enterprises to train larger models, run multimodal AI, and tackle scientific challenges at scale.</p>\n\n<p>👉 Call-to-Action for LinkedIn Readers:<br />\nHBM4 will define the next generation of compute platforms. If you’re in cloud, AI, or semiconductor industries, now is the time to explore how HBM4 can reshape your architecture strategy.</p>\n\n<p>💬 What do you think—will HBM4 become the de facto standard for AI accelerators by 2027, or will HBM3e remain dominant longer? Share your thoughts below!</p>","content":"🔮 HBM4: The Memory Powering AI’s Next Leap\nThe future of AI and HPC isn’t just about compute—it’s about memory. Enter HBM4 (High Bandwidth Memory 4), the next-generation standard that doubles the throughput of HBM3e and sets the stage for trillion-parameter models, multimodal reasoning, and scientific breakthroughs.\n🚀 What Makes HBM4 Different?\nBandwidth Explosion: Up to 3.3 TB/s per stack, thanks to a 2048-bit bus.\nHigher Capacity: 36–48 GB per stack, reducing the need for multiple stacks.\nEfficiency Gains: >20% better energy per bit compared to HBM3e.\nFuture-Proof Design: Tailored for AI workloads where HBM3 hits its limits.\n⚡ Key Specs at a Glance\nFeature HBM3e HBM4\nBandwidth per stack ~1.2 TB/s 2.8–3.3 TB/s\nBus width 1024-bit 2048-bit\nCapacity 24–36 GB 36–48 GB\nPin speed 9.2 Gb/s 11–13 Gb/s\n🌐 Why It Matters\nAI Training & Inference: Enables ultra-long context windows and real-time multimodal reasoning.\nHPC & Research: Climate modeling, drug discovery, and quantum simulations at unprecedented scale.\nEnterprise & Cloud: Hyperscale clusters ready for next-gen workloads.\n⚠️ Challenges Ahead\nPackaging Costs: 2.5D/3D integration raises manufacturing complexity.\nThermal Management: Higher density demands advanced cooling solutions.\nSupply Chain: Ramp-up expected in 2026–2027, with limited availability initially.\n💡 The Big Picture\nHBM4 isn’t just an upgrade—it’s the memory foundation for the AI era. With double the bandwidth, larger capacities, and better efficiency, it empowers enterprises to train larger models, run multimodal AI, and tackle scientific challenges at scale.\n👉 Call-to-Action for LinkedIn Readers:\nHBM4 will define the next generation of compute platforms. If you’re in cloud, AI, or semiconductor industries, now is the time to explore how HBM4 can reshape your architecture strategy.\n💬 What do you think—will HBM4 become the de facto standard for AI accelerators by 2027, or will HBM3e remain dominant longer? Share your thoughts below!\nTop comments (0)","published":"Sat, 25 Jul 2026 14:53:37 +0000","author":"snehaliteng","guid":"https://dev.to/snehaliteng/hbm4-the-memory-revolution-driving-ais-next-frontier-18bm","created_at":"2026-07-25T17:27:41.996322","last_synchronized":"2026-07-25T17:27:41.996322","sentiment":{"sentiment":"Positive","score":0.9635,"details":{"neg":0.005,"neu":0.924,"pos":0.071,"compound":0.9635}}},{"feed_name":"Engadget","feed_url":"https://www.engadget.com/rss.xml","title":"The downsides of switching from Android to iPhone","link":"https://www.engadget.com/2222494/downsides-switching-from-android-to-iphone/","description":"Here are a few things to know if you're leaving Android for iPhone.","content":"The downsides of switching from Android to iPhone\nHere are a few things to know if you're leaving Android for iPhone.\niOS has a lot going for it. The fluid performance, the tight integration between hardware and software and the overall premium build quality of iPhones have convinced loads of Android users to make the switch to iPhones. If you've been tempted, you're not alone.\nBut certain habits and workflows you've built on Android simply won't carry over. Not because iPhones are worse — because they're not — but because the two platforms are simply different. So, if you're thinking about making the jump from Android to iOS, let's pull back the veil on some of those differences so there are fewer surprises on day one.\nHow iOS handles notifications\nIf you're coming from Android, one of the more frustrating adjustments on iPhone is how much control you lose over notifications. Marking a message as read straight from your lock screen or sending a smart reply? No longer possible. Liking someone's text to signal you've read it? Also not available anymore. Android also lets you choose exactly the type of notifications you want from apps, such as allowing a shopping app to give you information about deliveries, but no marketing messages, for instance. iOS doesn't offer as much depth when it comes to how notifications are handled, largely offering per-app toggles. This lets you choose whether the banners, sounds and badges are on or off.\nGetting used to a new navigation style\nWhen you're using an Android device, swiping from either edge of the screen works as a back gesture. This works consistently in every app, so you don't have to adapt your style. iOS does offer a similar left-edge swipe, but only some apps support it. Some even bury the back function inside a small button at the top-left or top-right corner of the screen, which is quite a lot of reaching, especially if you don't have large hands.\nSure, it's not an absolutely massive thing to concern yourself about, but muscle memory is a powerful thing to overcome.\nNew typing experience\nMany long-term Android users find the iOS keyboard frustrating at first. Autocorrect can feel aggressive and imprecise, and text prediction often feels less capable. While third-party keyboards like Gboard or SwiftKey are available on iOS, Apple restricts how much system access they're given. You'll have to give the apps \"Full Access\" for expanded functionality, and even then the keyboards can't be used to tap into certain secure text fields. iOS will automatically force-switch SwiftKey back to the native Apple keyboard if you want to enter a password or a credit card number, for instance.\niPhones aren't very customizable\niOS is a lot more customizable nowadays than it used to be, allowing you to tint app icons with custom colors, change controls on the lock screen, and so on. Since Android allows you to install custom launchers, you can completely redesign how your phone looks and operates.\nIt takes a while to charge\nPhones nowadays have decent battery life, but you still have to give them a daily recharge. Charging speed is a consistent weak point for iPhones when compared to Android devices. The newest iPhones support up to 40W wired charging, but Samsung's S26 Ultra bumped its charging up to 60W, so you'll get to around 75 percent in half an hour.\nEven with the upgrades, both Samsung and Apple lag behind some other brands in the Android world. Chinese manufacturers like OnePlus and Xiaomi are shipping phones with 80W to 120W wired charging.\nNone of this means the iPhone is a downgrade. You still get the fluid performance, the tight hardware and software integration and the premium build quality that pull so many people toward Apple in the first place. What it does mean is that the switch comes with real trade-offs. Whether those trade-offs are worth it depends entirely on what you value most in a phone.","published":"Sat, 25 Jul 2026 14:30:00 +0000","author":"staff@engadget.com (Gabriela Vătu)","guid":"https://www.engadget.com/2222494/downsides-switching-from-android-to-iphone/","created_at":"2026-07-25T17:27:43.618443","last_synchronized":"2026-07-25T17:27:43.618443","sentiment":{"sentiment":"Neutral","score":0.0,"details":{"neg":0.0,"neu":1.0,"pos":0.0,"compound":0.0}}},{"feed_name":"Mashable","feed_url":"http://feeds.mashable.com/Mashable","title":"Midjourney announces surprise purchase of popular horoscope app Co-Star","link":"https://mashable.com/tech/midjourney-announces-surprise-purchase-of-popular-horoscope-app-co-star","description":"What does a generative AI company want with one of the most popular horoscope apps? A lot, as it turns out.","content":"Midjourney announces surprise purchase of popular horoscope app Co-Star\nOne of the world's leading generative AI companies, with products licensed by tech powerhouses like Meta, has just announced its acquisition of one of the most popular astrology apps — and we didn't have that on our Bingo cards in 2026.\nBanu Guler, founder and CEO of Co-Star Astrology, took to X recently to announce her company's acquisition by AI lab Midjourney, best known for making the generative AI tools used by popular services like Discord and Facebook. And because, on its surface, this is an odd acquisition to say the least, Guler went into some detail about the shared origins of these two companies:\nThis Tweet is currently unavailable. It might be loading or has been removed.\nIn a nod to the general public's fear of artificial intelligence (a recent Reuters/Ipsos poll found that 71% of Americans worry that their job will be replaced by AI), Guler took pains to distinguish the Co-Star/Midjourney partnership by pointing to the shared interests she has with Midjourney founder David Holz as well as his lab's commitment to \"beauty, imagination, and supporting human thriving with technology.\"\nYou May Also Like\nAnd to further reassure Guler's users, most of whom are young and therefore no strangers to tech-induced anxiety, Midjourney used its announcement to reiterate that Guler would retain \"complete control\" of Co-Star.\nBut what does a generative AI company want with an astrology app?\nBecause of the company's forays into health, including the development of a full-body ultrasonic scanner, as well as Midjourney Medical, a novel concept that would see the AI company create a series of spas focused around combining older technology (saunas, hot tubs and cold plunges) with the aforementioned ultrasonic scanning technology, it's possible that this is just another step towards a more holistic approach to healthcare, a kind of mind-body-spirit gambit that would cover all our bases.\nAs a result of the partnership, Banu Guler will also become Midjourney's new Chief Design Officer, so we should expect to see some of the seamless design touches that made Co-Star such a standout success incorporated into the Midjourney ecosystem.\nTopics Apps & Software","published":"Sat, 25 Jul 2026 14:43:19 +0000","author":"","guid":"01rrjksJ0VXLXtkIUWhFxzw","created_at":"2026-07-25T17:27:47.968270","last_synchronized":"2026-07-25T17:27:47.968270","sentiment":{"sentiment":"Positive","score":0.5256,"details":{"neg":0.0,"neu":0.812,"pos":0.188,"compound":0.5256}}},{"feed_name":"TechRepublic","feed_url":"https://www.techrepublic.com/rssfeeds/articles/","title":"Nvidia Signs $1.5B Deal to Expand US AI Chip Packaging","link":"https://www.techrepublic.com/article/news-nvidia-amkor-us-ai-chip-packaging-deal/","description":"<p>Nvidia’s $1.5 billion Amkor deal will expand US AI chip packaging capacity and could help ease a growing semiconductor supply bottleneck.</p>\n<p>The post <a href=\"https://www.techrepublic.com/article/news-nvidia-amkor-us-ai-chip-packaging-deal/\">Nvidia Signs $1.5B Deal to Expand US AI Chip Packaging</a> appeared first on <a href=\"https://www.techrepublic.com\">TechRepublic</a>.</p>","content":"This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.\nYou can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.","published":"Fri, 24 Jul 2026 16:12:29 +0000","author":"Kezia Jungco","guid":"https://www.techrepublic.com/article/news-nvidia-amkor-us-ai-chip-packaging-deal/","created_at":"2026-07-25T17:27:48.578202","last_synchronized":"2026-07-25T17:27:48.578202","sentiment":{"sentiment":"Positive","score":0.8591,"details":{"neg":0.0,"neu":0.747,"pos":0.253,"compound":0.8591}}},{"feed_name":"TechRepublic","feed_url":"https://www.techrepublic.com/rssfeeds/articles/","title":"What Singapore’s $913M Scam Problem Says About FCC SIM ID Plans","link":"https://www.techrepublic.com/article/news-singapore-scam-fcc-sim-id-plans-apac/","description":"<p>Singapore has required SIM identity checks since 2005. As the FCC proposes similar US rules, Singapore's scam data shows verification alone isn't enough.</p>\n<p>The post <a href=\"https://www.techrepublic.com/article/news-singapore-scam-fcc-sim-id-plans-apac/\">What Singapore&#8217;s $913M Scam Problem Says About FCC SIM ID Plans</a> appeared first on <a href=\"https://www.techrepublic.com\">TechRepublic</a>.</p>","content":"This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.\nYou can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.","published":"Fri, 24 Jul 2026 15:02:08 +0000","author":"Joseph Ofonagoro","guid":"https://www.techrepublic.com/article/news-singapore-scam-fcc-sim-id-plans-apac/","created_at":"2026-07-25T17:27:48.578202","last_synchronized":"2026-07-25T17:27:48.578202","sentiment":{"sentiment":"Negative","score":-0.9022,"details":{"neg":0.242,"neu":0.758,"pos":0.0,"compound":-0.9022}}},{"feed_name":"TechRepublic","feed_url":"https://www.techrepublic.com/rssfeeds/articles/","title":"Hundreds of AI Startups Are Pushing Back Against Washington","link":"https://www.techrepublic.com/article/news-us-startups-chinese-open-weight-ai-model-ban/","description":"<p>Nearly 200 U.S. startups are urging the Trump administration to avoid broad restrictions on Chinese open-weight AI models, arguing a ban would increase costs and hurt innovation.</p>\n<p>The post <a href=\"https://www.techrepublic.com/article/news-us-startups-chinese-open-weight-ai-model-ban/\">Hundreds of AI Startups Are Pushing Back Against Washington</a> appeared first on <a href=\"https://www.techrepublic.com\">TechRepublic</a>.</p>","content":"This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.\nYou can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.","published":"Fri, 24 Jul 2026 14:41:45 +0000","author":"Joseph Ofonagoro","guid":"https://www.techrepublic.com/?p=4366298","created_at":"2026-07-25T17:27:48.578202","last_synchronized":"2026-07-25T17:27:48.578202","sentiment":{"sentiment":"Negative","score":-0.872,"details":{"neg":0.228,"neu":0.729,"pos":0.043,"compound":-0.872}}},{"feed_name":"Gizmodo","feed_url":"https://gizmodo.com/rss","title":"RIP Chuck Russell, ‘The Mask’ and ‘Nightmare on Elm Street 3’ Director","link":"https://gizmodo.com/rip-chuck-russell-the-mask-and-nightmare-on-elm-street-3-director-2000790629","description":"Along with 'The Mask,' Russell is best known for helming 'The Blob,' 'Dreamscape,' and 'The Scorpion King.'","content":"Chuck Russell, a film director known for ’80s and ]90s movies like Nightmare on Elm Street 3: Dream Warriors and The Blob, died earlier this week at 74 years old. Per TMZ, his death was “unexpected,” and a cause has yet to be determined. His family is said to be currently en route to California to learn more.\nBorn May 9, 1952, Russell entered the industry doing indie work as a production manager and assistant director. He wrote screenplays in his off time and eventually met writer (and future collaborator) Frank Darabont. Russell’s first produced screenplay was 1984’s Dreamscape, the second-ever movie to get a PG-13 rating. The aforementioned Dream Warriors was his directorial debut, which was such a hit back then that it revitalized the franchise.\nAfter his 1988 remake of The Blob, Russell’s profile shot up with an adaptation of The Mask comics in 1994. Known for strong visual effects with a limited budget, his approach with Mask was working with ILM to combine Jim Carrey’s live-action performance with then-new digital effects. They were so impressive that the film earned an Oscar nomination for “Best VFX,” which was a bonus on top of the film’s $350 million global box office.\nHe went on to direct The Scorpion King and Eraser, not directing again until an episode of Fringe in 2010. From that point on, his output consisted of a handful of action-thriller movies (Paradise City, Junglee) throughout the 2010s and early 2020s. His final film was a 2024 remake of the 1986 horror movie Witchboard, which he wrote and directed.\nWant more io9 news? Check out when to expect the latest Marvel, Star Wars, and Star Trek releases, what’s next for the DC Universe on film and TV, and everything you need to know about the future of Doctor Who.","published":"Sat, 25 Jul 2026 14:30:59 +0000","author":"Justin Carter","guid":"https://gizmodo.com/?p=2000790629","created_at":"2026-07-25T17:27:49.775658","last_synchronized":"2026-07-25T17:27:49.775658","sentiment":{"sentiment":"Positive","score":0.6369,"details":{"neg":0.0,"neu":0.792,"pos":0.208,"compound":0.6369}}},{"feed_name":"Reddit - r/technology","feed_url":"https://www.reddit.com/r/technology/.rss","title":"‘Really inappropriate’: teachers decry plan for humanoid robot in New York high school","link":"https://www.reddit.com/r/technology/comments/1v69cl5/really_inappropriate_teachers_decry_plan_for/","description":"<table> <tr><td> <a href=\"https://www.reddit.com/r/technology/comments/1v69cl5/really_inappropriate_teachers_decry_plan_for/\"> <img alt=\"‘Really inappropriate’: teachers decry plan for humanoid robot in New York high school\" src=\"https://external-preview.redd.it/x_dkrCUpUzWBv09tsAE79Yp71ChJqcq_MzVfm-8zIfY.jpeg?width=640&amp;crop=smart&amp;auto=webp&amp;s=d248c72b3eb73b43466d564a490e48fc4fab5ed9\" title=\"‘Really inappropriate’: teachers decry plan for humanoid robot in New York high school\" /> </a> </td><td> &#32; submitted by &#32; <a href=\"https://www.reddit.com/user/The_Flaneur_Films\"> /u/The_Flaneur_Films </a> <br /> <span><a href=\"https://www.theguardian.com/us-news/2026/jul/25/new-york-humanoid-robot-teachers-school\">[link]</a></span> &#32; <span><a href=\"https://www.reddit.com/r/technology/comments/1v69cl5/really_inappropriate_teachers_decry_plan_for/\">[comments]</a></span> </td></tr></table>","content":"","published":"2026-07-25T14:06:50+00:00","author":"/u/The_Flaneur_Films","guid":"t3_1v69cl5","created_at":"2026-07-25T17:27:54.522969","last_synchronized":"2026-07-25T17:27:54.522969","sentiment":{"sentiment":"Neutral","score":0.0,"details":{"neg":0.0,"neu":1.0,"pos":0.0,"compound":0.0}}},{"feed_name":"Reddit - r/technology","feed_url":"https://www.reddit.com/r/technology/.rss","title":"Pope's official prayer app commits cardinal sin, leaks 700K+ users' info","link":"https://www.reddit.com/r/technology/comments/1v67g20/popes_official_prayer_app_commits_cardinal_sin/","description":"&#32; submitted by &#32; <a href=\"https://www.reddit.com/user/beIIe-and-sebastian\"> /u/beIIe-and-sebastian </a> <br /> <span><a href=\"https://www.theregister.com/security/2026/07/24/popes-official-prayer-app-commits-cardinal-sin-leaks-700k-users-info/5278603\">[link]</a></span> &#32; <span><a href=\"https://www.reddit.com/r/technology/comments/1v67g20/popes_official_prayer_app_commits_cardinal_sin/\">[comments]</a></span>","content":"","published":"2026-07-25T12:45:06+00:00","author":"/u/beIIe-and-sebastian","guid":"t3_1v67g20","created_at":"2026-07-25T17:27:54.522969","last_synchronized":"2026-07-25T17:27:54.522969","sentiment":{"sentiment":"Neutral","score":0.0,"details":{"neg":0.0,"neu":1.0,"pos":0.0,"compound":0.0}}},{"feed_name":"Reddit - r/technology","feed_url":"https://www.reddit.com/r/technology/.rss","title":"HP admits 30% of PCs still run Windows 10, rejecting Windows 11, and it's why Microsoft caved on support","link":"https://www.reddit.com/r/technology/comments/1v65hxk/hp_admits_30_of_pcs_still_run_windows_10/","description":"<table> <tr><td> <a href=\"https://www.reddit.com/r/technology/comments/1v65hxk/hp_admits_30_of_pcs_still_run_windows_10/\"> <img alt=\"HP admits 30% of PCs still run Windows 10, rejecting Windows 11, and it's why Microsoft caved on support\" src=\"https://external-preview.redd.it/bfZ6v0mOK3d9exeiak9JhN56FIar06S5A8sYV0eDNv8.jpeg?width=640&amp;crop=smart&amp;auto=webp&amp;s=2a5712b653fa87998ddbbf1eb6d595aec962766b\" title=\"HP admits 30% of PCs still run Windows 10, rejecting Windows 11, and it's why Microsoft caved on support\" /> </a> </td><td> &#32; submitted by &#32; <a href=\"https://www.reddit.com/user/kazu-qt\"> /u/kazu-qt </a> <br /> <span><a href=\"https://www.windowslatest.com/2026/07/24/hp-admits-30-of-pcs-still-run-windows-10-rejecting-windows-11-and-its-why-microsoft-caved-on-support/\">[link]</a></span> &#32; <span><a href=\"https://www.reddit.com/r/technology/comments/1v65hxk/hp_admits_30_of_pcs_still_run_windows_10/\">[comments]</a></span> </td></tr></table>","content":"","published":"2026-07-25T11:09:40+00:00","author":"/u/kazu-qt","guid":"t3_1v65hxk","created_at":"2026-07-25T17:27:54.522969","last_synchronized":"2026-07-25T17:27:54.522969","sentiment":{"sentiment":"Positive","score":0.4215,"details":{"neg":0.082,"neu":0.783,"pos":0.135,"compound":0.4215}}},{"feed_name":"Reddit - r/technology","feed_url":"https://www.reddit.com/r/technology/.rss","title":"Be skeptical of OpenAI’s rogue hacker agent story","link":"https://www.reddit.com/r/technology/comments/1v63xpq/be_skeptical_of_openais_rogue_hacker_agent_story/","description":"<table> <tr><td> <a href=\"https://www.reddit.com/r/technology/comments/1v63xpq/be_skeptical_of_openais_rogue_hacker_agent_story/\"> <img alt=\"Be skeptical of OpenAI’s rogue hacker agent story\" src=\"https://external-preview.redd.it/tiVsDuqSU8cPHxIj8vqcNprhte2fqneizjGEJ7vl3MA.jpeg?width=640&amp;crop=smart&amp;auto=webp&amp;s=3c9d79b24ce6d4b92e70549a5436bb05193159df\" title=\"Be skeptical of OpenAI’s rogue hacker agent story\" /> </a> </td><td> &#32; submitted by &#32; <a href=\"https://www.reddit.com/user/ArgentineBeauty\"> /u/ArgentineBeauty </a> <br /> <span><a href=\"https://www.theguardian.com/technology/2026/jul/24/openai-rogue-hacker\">[link]</a></span> &#32; <span><a href=\"https://www.reddit.com/r/technology/comments/1v63xpq/be_skeptical_of_openais_rogue_hacker_agent_story/\">[comments]</a></span> </td></tr></table>","content":"","published":"2026-07-25T09:45:03+00:00","author":"/u/ArgentineBeauty","guid":"t3_1v63xpq","created_at":"2026-07-25T17:27:54.522969","last_synchronized":"2026-07-25T17:27:54.522969","sentiment":{"sentiment":"Negative","score":-0.5574,"details":{"neg":0.106,"neu":0.894,"pos":0.0,"compound":-0.5574}}},{"feed_name":"Reddit - r/technology","feed_url":"https://www.reddit.com/r/technology/.rss","title":"Texas includes minimal data center info in state water plan, citing ‘limited data’","link":"https://www.reddit.com/r/technology/comments/1v65jwz/texas_includes_minimal_data_center_info_in_state/","description":"<table> <tr><td> <a href=\"https://www.reddit.com/r/technology/comments/1v65jwz/texas_includes_minimal_data_center_info_in_state/\"> <img alt=\"Texas includes minimal data center info in state water plan, citing ‘limited data’\" src=\"https://external-preview.redd.it/DR4lSr7v5EyRXL8dvonlzZlXzgdPC6eGpYSGJ-2KvX4.jpeg?width=640&amp;crop=smart&amp;auto=webp&amp;s=cbe775ca1a942b51547404b5f6b7b4480d10a973\" title=\"Texas includes minimal data center info in state water plan, citing ‘limited data’\" /> </a> </td><td> &#32; submitted by &#32; <a href=\"https://www.reddit.com/user/Wagamaga\"> /u/Wagamaga </a> <br /> <span><a href=\"https://communityimpact.com/central-austin/texas-legislature/texas-includes-minimal-data-center-info-in-state-water-plan-citing-limited-data/\">[link]</a></span> &#32; <span><a href=\"https://www.reddit.com/r/technology/comments/1v65jwz/texas_includes_minimal_data_center_info_in_state/\">[comments]</a></span> </td></tr></table>","content":"","published":"2026-07-25T11:12:32+00:00","author":"/u/Wagamaga","guid":"t3_1v65jwz","created_at":"2026-07-25T17:27:54.522969","last_synchronized":"2026-07-25T17:27:54.522969","sentiment":{"sentiment":"Neutral","score":0.0,"details":{"neg":0.0,"neu":1.0,"pos":0.0,"compound":0.0}}},{"feed_name":"Reddit - r/technology","feed_url":"https://www.reddit.com/r/technology/.rss","title":"Pro-Ukraine group claims it helped hack Russian drone air defense system, shooting down Su-57 in friendly fire incident. Moscow confirms fifth-generation fighter jet crashed in technical malfunction","link":"https://www.reddit.com/r/technology/comments/1v687wo/proukraine_group_claims_it_helped_hack_russian/","description":"<table> <tr><td> <a href=\"https://www.reddit.com/r/technology/comments/1v687wo/proukraine_group_claims_it_helped_hack_russian/\"> <img alt=\"Pro-Ukraine group claims it helped hack Russian drone air defense system, shooting down Su-57 in friendly fire incident. Moscow confirms fifth-generation fighter jet crashed in technical malfunction\" src=\"https://external-preview.redd.it/MQRUgfayTe3Ggy-AyWTJCugSJup_i44LvKBcCevQFxU.jpeg?width=640&amp;crop=smart&amp;auto=webp&amp;s=aece7a155b3286d9fe7df95a439ee56facf840af\" title=\"Pro-Ukraine group claims it helped hack Russian drone air defense system, shooting down Su-57 in friendly fire incident. Moscow confirms fifth-generation fighter jet crashed in technical malfunction\" /> </a> </td><td> &#32; submitted by &#32; <a href=\"https://www.reddit.com/user/ArgentineBeauty\"> /u/ArgentineBeauty </a> <br /> <span><a href=\"https://www.tomshardware.com/tech-industry/drones/pro-ukraine-group-claims-it-helped-hack-russian-drone-air-defence-system-shooting-down-su-57-in-friendly-fire-incident-moscow-confirms-fifth-generation-fighter-jet-crashed-in-technical-malfunction\">[link]</a></span> &#32; <span><a href=\"https://www.reddit.com/r/technology/comments/1v687wo/proukraine_group_claims_it_helped_hack_russian/\">[comments]</a></span> </td></tr></table>","content":"","published":"2026-07-25T13:19:14+00:00","author":"/u/ArgentineBeauty","guid":"t3_1v687wo","created_at":"2026-07-25T17:27:54.522969","last_synchronized":"2026-07-25T17:27:54.522969","sentiment":{"sentiment":"Positive","score":0.7003,"details":{"neg":0.054,"neu":0.803,"pos":0.143,"compound":0.7003}}},{"feed_name":"Reddit - r/technology","feed_url":"https://www.reddit.com/r/technology/.rss","title":"Finland’s radical answer to renewable energy’s biggest headache: The world’s largest sand battery","link":"https://www.reddit.com/r/technology/comments/1v653nn/finlands_radical_answer_to_renewable_energys/","description":"<table> <tr><td> <a href=\"https://www.reddit.com/r/technology/comments/1v653nn/finlands_radical_answer_to_renewable_energys/\"> <img alt=\"Finland’s radical answer to renewable energy’s biggest headache: The world’s largest sand battery\" src=\"https://external-preview.redd.it/huvozgrF2GcP02PO5nXjB6H7OcCxU1fKSrtvulwH_sA.jpeg?width=640&amp;crop=smart&amp;auto=webp&amp;s=6ace89adec9c294f0b5c8071e8bcd06ba0c1cf19\" title=\"Finland’s radical answer to renewable energy’s biggest headache: The world’s largest sand battery\" /> </a> </td><td> &#32; submitted by &#32; <a href=\"https://www.reddit.com/user/Logical_Welder3467\"> /u/Logical_Welder3467 </a> <br /> <span><a href=\"https://www.cnbc.com/2026/07/25/finland-sand-battery-renewable-energy-storage.html\">[link]</a></span> &#32; <span><a href=\"https://www.reddit.com/r/technology/comments/1v653nn/finlands_radical_answer_to_renewable_energys/\">[comments]</a></span> </td></tr></table>","content":"","published":"2026-07-25T10:49:10+00:00","author":"/u/Logical_Welder3467","guid":"t3_1v653nn","created_at":"2026-07-25T17:27:54.522969","last_synchronized":"2026-07-25T17:27:54.522969","sentiment":{"sentiment":"Neutral","score":0.0,"details":{"neg":0.0,"neu":1.0,"pos":0.0,"compound":0.0}}},{"feed_name":"Reddit - r/technology","feed_url":"https://www.reddit.com/r/technology/.rss","title":"Paramount and Warner Bros. Pause $110 Billion Merger Deal Until June 2027 or Court Ruling","link":"https://www.reddit.com/r/technology/comments/1v61wqr/paramount_and_warner_bros_pause_110_billion/","description":"<table> <tr><td> <a href=\"https://www.reddit.com/r/technology/comments/1v61wqr/paramount_and_warner_bros_pause_110_billion/\"> <img alt=\"Paramount and Warner Bros. Pause $110 Billion Merger Deal Until June 2027 or Court Ruling\" src=\"https://external-preview.redd.it/z4sP5CN8mGQDdDz8_t_LiI_V10LrkqHVhhY0vox2ZqU.jpeg?width=640&amp;crop=smart&amp;auto=webp&amp;s=2830b154f4577b2175627389d97d082744aa4a6a\" title=\"Paramount and Warner Bros. Pause $110 Billion Merger Deal Until June 2027 or Court Ruling\" /> </a> </td><td> &#32; submitted by &#32; <a href=\"https://www.reddit.com/user/HumbleRestaurant790\"> /u/HumbleRestaurant790 </a> <br /> <span><a href=\"https://oag.ca.gov/news/press-releases/attorney-general-bonta-secures-deal-halting-warner-bros-paramount-merger-until\">[link]</a></span> &#32; <span><a href=\"https://www.reddit.com/r/technology/comments/1v61wqr/paramount_and_warner_bros_pause_110_billion/\">[comments]</a></span> </td></tr></table>","content":"","published":"2026-07-25T07:49:38+00:00","author":"/u/HumbleRestaurant790","guid":"t3_1v61wqr","created_at":"2026-07-25T17:27:54.522969","last_synchronized":"2026-07-25T17:27:54.522969","sentiment":{"sentiment":"Neutral","score":0.0,"details":{"neg":0.0,"neu":1.0,"pos":0.0,"compound":0.0}}},{"feed_name":"Slashdot","feed_url":"http://rss.slashdot.org/Slashdot/slashdotMain","title":"Trump Threatens New Tariffs Against EU Over Google Fine","link":"https://yro.slashdot.org/story/26/07/24/2111219/trump-threatens-new-tariffs-against-eu-over-google-fine?utm_source=rss1.0mainlinkanon&utm_medium=feed","description":"President Trump threatened a \"substantial\" new tariff on the European Union after Brussels fined Google more than $1 billion over alleged illegal trade practices. \"The European Union will pay a very big price for this illegal and highly unethical conduct, which I have consistently warned them about,\" Trump wrote on Truth Social. \"The penalties will be entirely reversed and, we anticipate, a substantial TARIFF to be placed on them at the earliest possible moment.\" Politico reports: The president's threat came just a day after U.S. Trade Representative Jamieson Greer warned that the EU's action against Google -- two fines totaling over $1 billion -- could imperil the bloc's relationship with the White House. At risk: the Turnberry deal, which Trump and European Commission President Ursula von der Leyen signed last fall, that capped U.S. tariffs on EU exports at 15 percent. [...] But the president's social media post could signal a coming breach. \"The United States of America is not a 'PIGGYBANK' for Europe, nor will we allow it to be!\" Trump wrote.<p><div class=\"share_submission\">\n<a class=\"slashpop\" href=\"http://twitter.com/home?status=Trump+Threatens+New+Tariffs+Against+EU+Over+Google+Fine%3A+https%3A%2F%2Fyro.slashdot.org%2Fstory%2F26%2F07%2F24%2F2111219%2F%3Futm_source%3Dtwitter%26utm_medium%3Dtwitter\"><img src=\"https://a.fsdn.com/sd/twitter_icon_large.png\" /></a>\n<a class=\"slashpop\" href=\"http://www.facebook.com/sharer.php?u=https%3A%2F%2Fyro.slashdot.org%2Fstory%2F26%2F07%2F24%2F2111219%2Ftrump-threatens-new-tariffs-against-eu-over-google-fine%3Futm_source%3Dslashdot%26utm_medium%3Dfacebook\"><img src=\"https://a.fsdn.com/sd/facebook_icon_large.png\" /></a>\n\n\n\n</div></p><p><a href=\"https://yro.slashdot.org/story/26/07/24/2111219/trump-threatens-new-tariffs-against-eu-over-google-fine?utm_source=rss1.0moreanon&amp;utm_medium=feed\">Read more of this story</a> at Slashdot.</p>","content":"Trump Threatens New Tariffs Against EU Over Google Fine 13\nPresident Trump threatened a \"substantial\" new tariff on the European Union after Brussels fined Google more than $1 billion over alleged illegal trade practices. \"The European Union will pay a very big price for this illegal and highly unethical conduct, which I have consistently warned them about,\" Trump wrote on Truth Social. \"The penalties will be entirely reversed and, we anticipate, a substantial TARIFF to be placed on them at the earliest possible moment.\" Politico reports: The president's threat came just a day after U.S. Trade Representative Jamieson Greer warned that the EU's action against Google -- two fines totaling over $1 billion -- could imperil the bloc's relationship with the White House. At risk: the Turnberry deal, which Trump and European Commission President Ursula von der Leyen signed last fall, that capped U.S. tariffs on EU exports at 15 percent. [...] But the president's social media post could signal a coming breach. \"The United States of America is not a 'PIGGYBANK' for Europe, nor will we allow it to be!\" Trump wrote.\nIt's just a tax on you (Score:2)\nSo tariffs raise the price of everything you buy. And they don't bring jobs back because even if the factories come back they're going t\nOops (Score:2)\nRe: Oops (Score:3)\nThe EU are just enforcing their laws. Trump doesn't agree with their laws because they are consumer focused, not company focused. Because the EU isn't Republican.\nAnd? (Score:2)\nWhat's new? The next new tariff will be on every nation that breaths because they are consuming Trump's air.\nHow do you make America ugly? You elect Trump.\nWords have meanings. (Score:3)\nWhen the court returns a conviction for a fine, the actions are no longer \"alleged\".\nThere's only one strategy (Score:2)\nTrump doesn't care about the USA getting hurt, he cares about hurting others. You can't threaten him, that just encourages escalation - but you can't let him abuse you, because that encourages escalation.\nThe best strategy - which is still a losing strategy because we're watching a mutually beneficial relationship being torn down unilaterally - is to ignore him where you can, and apply pressure without threats where you can.\nEventually Trump will make the US so unattractive to trade that it will evapourate\nSo Trump works for Google (Score:2)\nMeh, who cares? (Score:2)\nAt this point, it's obvious that Trump is a demented nutcase whose only approach to \"leading\" is flooding the zone with acrimony and vitriol. Best thing to do is ignore the fool. Just rag the puck until his power is constrained by midterms or the next election and let him hurt the USA as much as he wants to.\nThe land of the free (Score:2)\nGoogle's antitrust defeat in Europe reinforces a fundamental principle of the free market: fair competition ensures that superior products win on merit, not monopoly power.\nWhen European goods outperform American rivals on U.S. soil, it is because those companies delivered greater innovation, efficiency, and quality. True economic freedom requires trusting citizens to choose the best product available, not restricting their access through state intervention or protectionist committees. Replacing consumer sov\nPushing the world away from the USA (Score:2)","published":"2026-07-25T15:00:00+00:00","author":"BeauHD","guid":"https://yro.slashdot.org/story/26/07/24/2111219/trump-threatens-new-tariffs-against-eu-over-google-fine?utm_source=rss1.0mainlinkanon&utm_medium=feed","created_at":"2026-07-25T17:27:56.350625","last_synchronized":"2026-07-25T17:27:56.350625","sentiment":{"sentiment":"Negative","score":-0.8382,"details":{"neg":0.097,"neu":0.863,"pos":0.04,"compound":-0.8382}}},{"feed_name":"Hacker News","feed_url":"https://news.ycombinator.com/rss","title":"Stolen Buttons","link":"https://anatolyzenkov.com/stolen-buttons","description":"<a href=\"https://news.ycombinator.com/item?id=48976262\">Comments</a>","content":"Stolen Buttons\nEvery website I visit, I “steal” a button from it. Check out my stash!\nAbout Button Stealer\nLoslegen\nAlle Bewertungen lesen\nConnect with Google\nPrevious slide\nSee all symptoms\nStress\nRegister now\nMessage\nAuktionen\nAUTO-INK DELIVERY\nDer Drucker erkennt, wenn sein Tinten- oder Tonervorrat zur Neige geht. Melde dich kostenlos für Auto-Ink Delivery an, und du bekommst automatisch zum richtigen Zeitpunkt Ersatz, damit dir nie Tinte oder Toner ausgehen. Es gelten die Teilnahmebedingungen.\nRegistrieren Sie sich jetzt\nShop besuchen\nDesign\nShowcase\nSvenska\nAdobe After Effects\nNo\nReject All\nLog In\nYou\nContact and basic info\nFind out more\nOverview\nGo Premium\nContinue with Facebook\nPeople to Hire\nAssets\n→ get in touch\nRefresh\ngrid\nOnline abschließen\nAbmelden\nThemen & Anmeldung\nAnmelden\nBeschreibung\nDer Schulranzen Timeless in klassischer Ranzenform bietet ein enormes Platzangebot und ist gleichzeitig super leicht und ang…\nMehr\nAirpaq\n16\nMehr Informationen\nKonfig.\n12\nShop now\nEntdecken\nTry another way\nLEARN MORE\nVisit Reddit\nMehr erfahren\nBrowse components\n→\nLearn more\nAnmelden\nMein Konto\n1x\nPreise sehen\nSelect\nMLA\nLog in\nStart with Connect\nRegister Now\nWARENKORB\nFÜR DEN SOMMER\nHinzufügen.\nAccept\nJetzt anmelden\nZum Modell\nIn den Warenkorb\nShow top replies\n1 Reply\nAbo auswählen\nACCEPT & CLOSE\nBuy 2 Image Pack\nCreate a post\nCommunities\nEvents\nanatolyzenkov\nSearch\n1 month trial\nHier mehr\nDecline\nYes 👍\nSign Up\nYes 👍\nTechnology\nContinue Reading\nUsers & roles\nReset\nView All\nLog in\nLearn about Heatmaps\nGood, I'm satisfied\nGood\nSave\nSign in\nUpgrade to Plus\n每日分享\nAnalyse\nJoin the team\nAccept invitation\nContact us\nStart free trial\nAppointments\nSee MLA Style Guide\nKundenkonto löschen\nEinloggen/Registrieren","published":"Mon, 20 Jul 2026 09:23:42 +0000","author":"","guid":"https://anatolyzenkov.com/stolen-buttons","created_at":"2026-07-25T17:28:02.551087","last_synchronized":"2026-07-25T17:28:02.551087","sentiment":{"sentiment":"Neutral","score":0.0,"details":{"neg":0.0,"neu":1.0,"pos":0.0,"compound":0.0}}}]}