Pixel

Pixel Updates & Events

Server Updates & Events

Announcements

Fix GitHub Actions setup-go Version Errors

DEV Community

GitHub Actions setup-go unable to find go version, and what is in the quotes

GitHub Actions setup-go unable to find go version is thrown after the action has searched the published Go download index and found nothing that satisfies the spec it was given for this operating system and this architecture. The spec is quoted in the message, and reading what is inside the quotes settles almost every instance of this failure, because the spec is frequently not the string anybody wrote. The basename dispatch and the fall-through are read from parseGoVersionFile in src/installer.ts. The framed line is quoted from ctrl-research/mmo#3, shortened at the ellipses. What this error means A setup-go step fails before anything is installed, with a message naming a version, a platform and an architecture. Look at the quoted spec first and ask whether it is what you expected. Three shapes turn up. A plausible version number means the spec was right and no published build matches it for that platform and architecture. A version you never wrote means it came from a file rather than from the workflow. The contents of an entire file, comments and all, means the action read a file whose name it does not recognise and used the whole thing as a version string. The platform and architecture at the end of the message are not decoration: the search matches a per-file operating system and architecture, so a version that exists can still be unfindable here. ```Actions log, quoted from ctrl-research/mmo#3; the reporter marked the elisions [error]Unable to find Go version '# Single source of truth for language/tool versions (asdf/mise format). Humans and AI agents should use these versions... golang 1.26.5 nodejs 24.16.0 ...' for platform linux and architecture x64.

Common causes

go-version-file points at a file the action does not parse

The most surprising cause and the easiest to confirm, because the error quotes the file back at you. Only go.mod, go.work and .tool-versions are recognised, by exact basename. A tested-go.mod, a copied manifest under a different name or a version file in a format the action never supported all fall through to the branch that treats the whole file as a version string.

A toolchain directive in go.mod pins a patch that cannot be resolved

The toolchain line is read before the go line, so a repository that bumps toolchains for the go command is also, silently, changing what setup-go tries to install. In our experience this is the cause whenever the quoted version is a precise patch that nobody remembers putting in the workflow.

The version exists but has no build for this platform and architecture

The match is per file, not per release, so an arm64 macOS job or a Windows job can fail on a version a Linux job installs happily. The end of the message names the platform and architecture that found nothing, which is the only place this cause announces itself.

The spec is an exact patch that was never published

A typo, a patch number invented by a bump script, or a release that was pulled. This is the cause the message reads as if it always means, and it is real, but it is the one to check after the three above rather than before them.

How to fix it

Read what is inside the quotation marks before changing anything

  1. If the quotes hold many lines or a comment, the action fell through to its unrecognised-basename branch and the fix is the file name, not the version.
  2. If the quotes hold a patch nobody wrote in the workflow, search go.mod for a toolchain line.
  3. If the quotes hold the version you expected, read the platform and architecture at the end of the message and check that a build exists for that pair.

Give go-version-file a name the parser recognises

Point the input at a real go.mod or go.work, or copy the manifest to that basename first. A file staged under any other name is not rejected, so nothing warns you; the whole file simply becomes the spec.

```.github/workflows/ci.yml (illustrative)
- run: cp tested-go.mod "${RUNNER...

Powered by Readybot.io

Fix GitHub Actions setup-go Version Errors

Host Multiple Apps on One VPS with Docker

DEV Community

Hosting Several Client Apps on One VPS with Docker, Nginx and GitHub Actions

Most small agencies and freelancers end up in the same place: a pile of small client projects. A Next.js marketing site here, a Laravel admin panel there, a Node API for a mobile app, a couple of staging copies. Paying for a separate managed platform for each one adds up fast, and a Kubernetes cluster is far more machinery than a handful of small apps need. This post walks through the setup we use to run several small apps on one VPS: a Docker Compose file per project, Nginx on the host as the reverse proxy, one database container per engine, and GitHub Actions to deploy on every push. Nothing clever. Boring is the point, because boring is easy to debug at 11 pm. The shape of the server Host: Ubuntu LTS with Docker Engine, the Compose plugin, Nginx and Certbot installed directly on the host. Apps: every project lives in /srv/apps/// with its own docker-compose.yml and .env. Ports: each app container binds to 127.0.0.1: only, so nothing is exposed to the internet except Nginx on 80/443. Databases: one MySQL, one PostgreSQL and one MongoDB container on a private Docker network. Each engine holds both the prod and dev databases, with a separate user per database. /srv
├── infra/
│ ├── databases/docker-compose.yml
│ ├── nginx-templates/site.conf
│ └── ports.txt
└── apps/
├── shop/
│ ├── prod/docker-compose.yml
│ └── dev/docker-compose.yml
└── school-crm/
├── prod/docker-compose.yml
└── dev/docker-compose.yml
Why Nginx on the host instead of Traefik or a self-hosted PaaS? Fewer moving parts, less RAM, and a config file you can read top to bottom. On a small VPS that matters. Step 1: a private network and shared databases Create one external network that apps and databases share: docker network create db
Then a single compose file for the database engines: services:
mysql:
image: mysql:8.4
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
volumes:
- mysql-data:/var/lib/mysql
networks: [db]

postgres:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pg-data:/var/lib/postgresql/data
networks: [db]

volumes:
mysql-data:
pg-data:

networks:
db:
external: true
No ports are published, so the databases can't be reached from the internet at all. Apps connect by service name (mysql, postgres), and when you need to open a database from your laptop, an SSH tunnel does the job. Give every project its own user and database per environment, for example shop_prod and shop_dev. A bad migration on dev then can't touch production data. Step 2: one compose file per app Here is a typical Next.js app: services:
web:
image: ghcr.io/your-org/shop-web:${TAG:-latest}
restart: unless-stopped
env_file: .env
ports:
- "127.0.0.1:3101:3000"
networks: [db]

networks:
db:
external: true
Keep a plain ports.txt that maps each project and environment to a port, for example 31xx for prod and 32xx for dev. It sounds trivial, but it is the thing that stops two apps fighting over the same port six months from now. Step 3: an Nginx server block per domain server {
server_name shop.example.com;
client_max_body_size 20m;

location / {
    proxy_pass http://127.0.0.1:3101;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

}
Then issue a certificate: sudo certbot --nginx -d shop.example.com
Dev copies get a subdomain such as dev.shop.example.com pointing to the 32xx port, behind basic auth so search engines and random visitors never see half-finished work. We wrapped this in a tiny new-site.sh script that fills the template, runs nginx -t and reloads. Adding a new project is a few commands instead of a small adventur...

Powered by Readybot.io

Host Multiple Apps on One VPS with Docker

CSP Security Audit: 79 Pages, Zero Policies

DEV Community

Zero of our 79 pages send a Content-Security-Policy, and the strict version would need 892 hashes

Our site sends six security headers on every response. Count them yourself: curl -sI https://pub-trivia.app/faq
You get strict-transport-security, x-content-type-options, x-frame-options, referrer-policy, permissions-policy and x-dns-prefetch-control. You do not get a content-security-policy, because there is no CSP anywhere in the 79 pages in our sitemap. I grepped the response headers of all 79: zero hits. I grepped the repository for the string: zero hits. There is exactly one CSP on the whole origin, and we did not write it: curl -sI 'https://pub-trivia.app/_next/image?url=%2Fdemo-1.webp&w=1200&q=75' | grep -i security
content-security-policy: script-src 'none'; frame-src 'none'; sandbox;
That is Next.js hardcoding a policy on its own image optimiser, on the sensible theory that a route which echoes bytes back at you should not be a script host. The framework protects its own route and leaves the other 79 to us. So I went and priced the policy we have not written. The inventory, over all 79 delivered pages thing a CSP cares about count tags with src 1,496 of those, cross-origin 0 of those, with integrity 0 inline tags 1,557 of those, application/ld+json 200 of those, executable 1,357 on* event handler attributes 0 javascript: URLs 0 tags 0 style attributes 468 0 or 0 0 0 with a data: or absolute URL 0 of 77 Most of that table is good news. With no forms, no frames, no plugins, no and no inline handlers, five directives are free and absolute: object-src 'none'; base-uri 'none'; form-action 'none'; frame-src 'none'; frame-ancestors 'self'
That last one is worth a sentence on its own. We send x-frame-options: SAMEORIGIN, which is the 2009 spelling of frame-ancestors and which every modern browser treats as the weaker, less expressive version of a directive we do not send. The 468 style attributes are the first real cost. They hold only 40 distinct values across the whole site, 25,278 bytes in total, and they are Tailwind utilities that could not be static (animation delays, computed widths). A nonce cannot help: nonces apply to elements, and a style attribute is not an element. The options are style-src 'unsafe-inline', or 'unsafe-hashes' plus 40 hashes, which is the directive whose own name tells you how the spec feels about it. The 200 JSON-LD blocks need no hashes, and I checked rather than assumed Our pages carry a lot of structured data, 200 blocks across the 79 pages, all of it . My first count treated them as inline scripts needing hashes, which would have added 122 distinct hashes to the policy for no reason. A data block is not a script. The HTML spec decides the script's type before it gets anywhere near the CSP check, and a block whose type is not a JavaScript type is never prepared for execution, so there is nothing for script-src to block. I wanted to see that rather than cite it, so I made a page with the tightest possible policy, one executable inline script and one JSON-LD block:

window.__ran = 1; {"@context":"https://schema.org","@type":"FAQPage"}

I registered a securitypolicyviolation listener before the document loaded and read it back afterwards: {
"violations": [ { "d": "script-src-elem", "s": "", "l": 4 } ],
"ran": false,
"scripts": 2,
"ldjsonParsed": "FAQPage"
}
One violation, on line 4, which is the executable script. The data block on line 5 produced nothing, stayed in the DOM, and still parses. So script-src 'none' is perfectly compatible with a page full of schema.org markup, which is a relief for anyone whose rich results depend on it. You can see the blocks on any content page of ours, the FAQ has three, by running this in the console: [...document.querySelectorAll('script[type="application/ld+json"]')]
.map(s => JSON.parse(s.textConte...

Powered by Readybot.io

CSP Security Audit: 79 Pages, Zero Policies

Create a Logo with AI for Free: A Beginner’s Guide

DEV Community

How to Create a Logo with AI for Free

I'm not a designer. The first time I needed a logo, I proved it to myself: I opened a design app, stared at a blank canvas for ten minutes, and closed it again. I didn't want to hire a designer for a first version either, because I hadn't worked out what I actually wanted yet. This is a shorter, adapted version of my full guide. Read the full step-by-step guide on TechAI Guide: How to Create a Logo with AI for Free That's where AI logo tools come in. They won't replace a professional designer for a big brand, but for a first logo — a small business, a YouTube channel, a blog, a student club, a side project — they can take you from a rough idea to something you can actually use in under an hour, without spending anything. Start With a Five-Minute Brief Before you open any tool, write down five things: the exact name, spelled exactly as it should appear; one short line about what you do; a single style — modern and clean, friendly and playful, or serious and professional (pick one, not all three); two colours at most; and where the logo will appear. This takes five minutes and saves an hour of aimless clicking later. Without it, every option the AI makes looks fine but feels wrong, and you won't know why. A useful trick: look at three logos you like from businesses nothing like yours, and note one thing you like about each — a clean font, a simple shape, plenty of empty space. That becomes your brief. Choose One or Two Tools, Not Five There is no single best tool, only a best tool for your job right now. Canva is my default pick because you can edit everything afterwards and keep the same colours and fonts across your other posts. Microsoft Designer is the quickest route if you already live in Word and PowerPoint. Ideogram is the one I reach for when the logo has words in it, because it spells text far more reliably than most image generators — though you should still check every letter. Looka and Shopify Hatchful are great for brainstorming dozens of ideas fast; use them to find a direction, then rebuild that direction in a free editor like Canva so you own the editable file. My rule: try two tools, not five. One logo maker and one AI image tool, a shortlist from each, then stop. Trying every tool on the internet turns a one-hour job into a whole weekend. Write a Prompt the AI Can Actually Use "Make me a nice logo" gives you a generic logo. Use a formula instead: business name + what it does + style + colours + where it will be used. For example: "A clean, modern logo for 'TechAI Guide', a blog that explains AI tools in simple words. Simple lightbulb icon, blue and teal, white background, flat design, no extra text." Or: "A friendly logo for 'Sunny Bakes', a small home bakery in Texas. A simple cupcake icon, warm orange and cream, round shape, easy to read on a phone screen." Three rules make a real difference. Say "flat" and "minimal" — detailed images turn into a mess at profile-picture size. Say "no extra text" — AI tools love adding words you never asked for. And name your colours, or you'll get a rainbow and spend your time fixing colours instead of choosing a logo. Generate at least six versions from the same prompt and don't judge the first one; the first is almost never the best. Pick the Simplest One, Then Simplify It More Choosing is the hardest part, because most of us pick the busiest logo — it looks like the most work went into it. On a phone screen, the busiest logo is usually the hardest to read. Run your shortlist through a quick test: can you still read it at thumbnail size? Does it work in black and white? Is there only one idea in it — one icon, one name, one style? Then remove one thing from the winner: an extra line, a second icon, a shadow, a slogan you don't need yet. Show your top two to one honest friend without saying which you prefer, and ask which one they'd remember tomorrow. Trust their answer more than your own — you've been staring at these for an hour and they haven't. Make It Look Professio...

Powered by Readybot.io

Create a Logo with AI for Free: A Beginner’s Guide

Postgres CamelCase Bug Hid Review Prompts for 57 Days

DEV Community

Our review prompt showed to nobody for 57 days, because the driver camelCases every column a raw query returns

CogniPrep asks a paying customer for a Trustpilot review once, and only once, after they have actually used what they bought. The gate is deliberately small: export const REVIEW_PROMPT_MINIMUMS = {
games: 2,
exercises: 1,
interviews: 1,
} as const;
Any one of those clearing its own bar is enough. It is an OR and not a weighted sum, because somebody who has done one full assessment centre exercise has a formed opinion and making them play a game first would be arbitrary. The counts are measured since the purchase, not since signup. So the gate runs one raw aggregate that counts completed games, exercises and interviews twice each: lifetime, and since a timestamp. That query returned the right numbers. The gate read zero for every customer, every time, from 16 September to 12 November. One client option, two kinds of result Our Postgres client sets a column transform: export const DB_TRANSFORM = {
column: { from: postgres.toCamel, to: postgres.fromCamel },
};
Drizzle's query builder is unaffected by that. It reads results positionally and maps them onto the object shape you asked for, so select({ firstPurchaseAt: usersTable.first_purchase_at }) gives you what it says. db.execute is a different path. It hands back postgres.js's own row objects, and the transform has already rewritten every column name on them. A plain AS games_since arrives as gamesSince. The gate was reading row['games_since']. That is undefined, and Number(undefined ?? 0) is 0. Why nothing went wrong loudly Single-word aliases are not affected by a camelCase transform, which is what made this so quiet. The same query selects paid, games, exercises and interviews, and all four of those arrived under the names the code expected. So: hasPaid was correct. We identified customers fine. The lifetime counts were correct. They are what the prompt's own copy and analytics use. Only the three "since purchase" counts and first_purchase_at broke, and those are exactly the four values the decision is made on. Three counts of 0 against a bar of 2, 1 and 1 is three false comparisons ORed together. The function returned needsReviewPrompt: false and did it in good faith. There is no error to log, no row to find, no exception, no Sentry issue. A zero is what a brand new customer legitimately looks like. We had already made this mistake, where it was loud The nurture cron hit the identical thing earlier, selecting AS user_id and reading row.user_id. There it failed immediately and noisily: the undefined user id meant Drizzle substituted DEFAULT on insert, the NOT NULL column rejected it, and a list of undefineds went into an inArray, which failed every campaign in the same run. That one was found and fixed quickly, and the file now carries a long comment explaining the aliases. The difference between the two is not the bug. It is that one of them had a destination that complained. Worth remembering next time you are deciding whether something needs a test: the bug you should fear is not the complicated one, it is the one whose failure mode is a plausible number. The fix is four characters per alias (SELECT count() FROM game_sessions
WHERE user_id = $1
AND completed_at >= $2) AS "gamesSince"
Quoting the camelCase name outright is stable whether or not the transform is configured, which is the property we actually want. There are 15 raw db.execute call sites in this codebase across five files, and a grep for AS [a-z]+[a-z]+ over all of them now returns nothing. The test asserts the SQL, not just the behaviour Behaviour tests would have passed with the bug in place if the fixtures were written in snake_case, because the fixtures would have been wrong in exactly the same way the code was. So the fixture rows are written the way the driver really hands them over: function activityRow(gamesSince: number) {
return {
games: '6',
gamesSince: String(gamesSince),
exercises: '0',
...
};
}
And one test renders the SQL that was actually built and ...

Powered by Readybot.io*

Postgres CamelCase Bug Hid Review Prompts for 57 Days

Wisp: A Fast, Fun Web Framework for Rust

DEV Community

Wisp - A fast, fun web framework for Rust

Hi, I built Wisp, a web framework for Rust. Here is a 90-second video of it: https://www.youtube.com/watch?v=g0LPPzrQux8 The idea: a folder is a URL, and its +page.wisp file is the page. The top of the file is a short block of Rust that loads data and handles forms, and the rest is markup. The templates compile to Rust, so the compiler checks your markup, and the whole app (styles and static files too) ships as one binary. ---
let name = cx.query_or("name", "world".to_string());

Hello, ${name}!

Things I cared about: Forms work with JavaScript off. #[action] handlers validate on the server, and use:enhance updates the page in place when scripts are on. Reactivity lives in the same file: on:click="count++" and {:count}, no separate component. wisp npm add gets a package for browser code, with no Node install. wisp mcp gives coding agents the docs and a checker, so an agent can read how Wisp works and check its own changes. Hosting: one binary on your own server (wisp host keeps it running), static HTML, or a build for Cloudflare, Deno Deploy, Vercel, Netlify, AWS Lambda, Bun and Node. No auth or database layer, on purpose. Wisp gives tools (tables, signed cookies, hooks, uploads) and leaves those choices to you. On AI-written code: the small test app is 477 tokens in Wisp and 1,043 in Nuxt, 2.2x to 3.9x fewer across the stacks I measured (release 1.0.0-pre.3). Method and apps: https://wispweb.dev/docs/tokens/ Status: this is a pre-release (1.0.0-pre.3), MIT licensed, Rust 1.89 or later. The source is at https://code.wispweb.dev/wispdev/wisp for now. It moves to GitHub and crates.io with 1.0. To try it: cargo install --git https://wisp.ar0.eu/ wisp-web
wisp new my-app
cd my-app
wisp dev
I'd love to hear what breaks, what feels wrong, and what you'd need before you used it for a real project.

Powered by Readybot.io

Wisp: A Fast, Fun Web Framework for Rust

How an AI Agent Verifies Crypto Transactions

DEV Community

My AI Agent Moves Real Money. I Never Take Its Word for Anything.

I run an AI agent that operates a crypto wallet on BNB Chain. It mines tokens, claims free mints, and submits transactions on a schedule, including while I sleep. It is also — in the only sense that matters — untrusted. Not because it has misbehaved. Because "the agent said so" is not evidence. Every action it takes has to survive verification against the blockchain itself before anything gets signed. This is a field report on how that works in practice: the checks, the numbers, and the occasions where verification was the only thing between a good story and a bad transaction. It is also an argument for why a competition like the Agent Games, where an agent's work is judged by proofs instead of claims, is the honest format for machines that act in the world. First: every mining proof gets re-proven Two of the agent's standing jobs are mining protocols on BNB Chain, PEARL and AMNT. The pattern is the same for both. A solver program grinds through candidate numbers (nonces) off-chain until it finds one whose proof digest — a hash fingerprint computed from the wallet address and the nonce — falls at or below the contract's current difficulty target. Finding candidates is the easy half, and a solver's log file is full of them. None of those candidates is trusted. Before any batch is submitted, each one is re-verified against the live contract, three ways: Recompute the digest. The digest is recalculated from the wallet and nonce and must match what the solver claimed. A mismatch means the log is wrong, and the candidate is discarded. Check the live target. Targets move. The digest must still be at or below the target the contract reports now, not the target that existed when the solver found it. Check the used-proof registry. The contract records which proofs have already been spent. A proof that appears there is worthless, however good it looks. Only candidates that pass all three are batched. On 13 November 2026, that pipeline submitted 44 verified PEARL proofs and received 3,258.99 PEARL. AMNT runs on a daily cadence: 20 verified proofs a day, 50,000 AMNT a day. Each batch costs roughly 0.00005 BNB in gas — a small amount, but real money, and it is only ever spent on proofs that already passed verification. Second: the transaction itself gets audited Verifying the inputs is not enough, because a correct proof can still ride inside a wrong transaction. So before signing, the wallet builds a preview: a simulation of the exact call, showing precisely what the transaction would do. (A simulation is a dry run of a transaction against current chain state — it shows the outcome without committing anything.) The preview is validated against strict rules: Check What must be true Proof digest Recomputed digest matches the candidate Target Digest ≤ the live target at submission time Unused Proof absent from the contract's used registry Simulation The exact call succeeds in simulation Balance changes Exactly one: the expected reward, arriving at our wallet Allowances / authority No changes at all Gas Worst case under a hard cap, or we stop The balance-change rule does most of the work. If the preview shows anything other than exactly one inbound movement — the expected reward, to our wallet — there is no signature. No exceptions, no "probably fine," no resubmission of a transaction whose outcome is uncertain. A preview that deviates is a full stop, and the deviation gets investigated before anything else moves. Third: marketing lies, contracts don't Free-mint promotions arrive constantly in this ecosystem, and they are written to be believed quickly. Our rule is to read the contract, not the copy. One campaign promised a token bonus for minting a set of shares. Before spending anything, we read the verified contract source. There was no bonus mechanism in it at all — no function, no accounting, nothing that could ever pay it. (A small amount, 0.2 USDT, did arrive later, paid manually off-contract. Which proved the point rather than ref...

Powered by Readybot.io

How an AI Agent Verifies Crypto Transactions

OLED iPad mini 8: Specs, Design, and Market Impact

DEV Community

Apple’s OLED iPad mini 8: Design, Specs, and Impact

Apple’s latest tablet, the OLED iPad mini 8, arrives as the most substantial refresh the mini line has seen in 14 years. While the device retains the familiar 8‑inch footprint, Apple has shaved off a millimeter of thickness, introduced a brighter OLED panel, and equipped the tablet with the A20 Pro SoC—the same silicon that powers the iPhone 18 Pro. In this hands‑on analysis we break down the engineering choices, explore why they matter to consumers and developers, and assess the broader ripple effects across the tablet market. Design Evolution and Build Quality The iPad mini 8 is a study in incremental refinement. At 5.4 mm thick, it is only 0.2 mm thicker than the folded iPhone Duo, yet it is a full millimeter slimmer than its predecessor, the iPad mini 7. The reduction may sound marginal, but it translates into a noticeably lighter device—255 g versus 293 g—making it the lightest iPad Apple has ever shipped. Key physical changes Thickness: 5.4 mm (down 1 mm from mini 7) Weight: 255 g (≈0.56 lb) Materials: Aluminum enclosure with a sealed acoustic cavity Colors: Midnight and Starlight (no bright finishes) The slimmer chassis also allows for a more ergonomic grip, especially for users who hold the tablet for extended reading or sketching sessions. Apple’s decision to forgo the “fun” color palette in favor of classic finishes signals a focus on professional and portable use cases rather than fashion statements. Display Technology: OLED vs. LCD The most visible upgrade is the shift from an LCD to an OLED panel, making the iPad mini 8 the second iPad to receive OLED after the iPad Pro line. The screen now measures 8.5 inches diagonally, a modest increase over the previous 8.3‑inch panel, but the real gain lies in contrast and color fidelity. Contrast Ratio: Near‑infinite, delivering true blacks Peak Brightness: Higher than the LCD predecessor, ensuring readability in bright environments HDR Support: Enhanced HDR rendering thanks to OLED’s wide color gamut Refresh Rate: Fixed at 60 Hz (no ProMotion 120 Hz) While the lack of a 120 Hz refresh may be noticeable to users accustomed to the iPad Pro’s ProMotion, the OLED panel still offers a premium visual experience. The optional nano‑texture glass on higher‑end models reduces glare without sacrificing clarity, a first for the mini line. Performance Core: A20 Pro Chipset Under the hood, the iPad mini 8 runs Apple’s A20 Pro SoC, the same silicon that powers the iPhone 18 Pro. Apple offers three storage configurations, each paired with a slightly different CPU/GPU binning: 🔹 --------- • CPU Cores: ----------- • GPU Cores: ----------- • Notable Benefit: ----------------- 🔹 128 GB • CPU Cores: 5‑core (binned) • GPU Cores: 7‑core • Notable Benefit: Efficient power envelope 🔹 256 GB • CPU Cores: 5‑core (binned) • GPU Cores: 7‑core • Notable Benefit: Same as 128 GB, more storage 🔹 512 GB • CPU Cores: 6‑core (full) • GPU Cores: 7‑core • Notable Benefit: Highest single‑core performance Even the binned 5‑core variant outperforms the A15 found in the iPad mini 7, delivering smoother multitasking and faster app launches. Developers targeting iPadOS 27.3 will benefit from the extra headroom, especially when leveraging the new machine‑learning APIs introduced in the latest beta. For a deeper look at the iPadOS changes, see Apple’s iOS 27.3 & iPadOS 27.3 beta release notes here. Durability and Audio: IP67 and Acoustic Display Apple has taken durability a step further by granting the iPad mini 8 an IP67 rating—first for any iPad. The tablet can survive immersion in up to 1 meter of water for 30 minutes, making it resilient against accidental splashes, rain, or poolside use. While it isn’t marketed as an underwater camera, the rating provides peace of mind for on‑the‑go professionals. Audio architecture also receives a redesign. Traditional speaker grilles are replaced by a sealed “Acoustic Display” system: tiny tweeters sit beneath the glass, while woofers vibrate the display itself to produc...

Powered by Readybot.io

OLED iPad mini 8: Specs, Design, and Market Impact

ChatGPT vs Gemini in 2026: Which AI Is Better?

DEV Community

ChatGPT vs Gemini: Which Is Better in 2026?

I use ChatGPT and Gemini every single day, and people keep asking me the same question: which one is better? After a year of switching between them for writing, research, coding help, and everyday questions, my honest answer is that neither wins everywhere. They are better at different things, and the right choice mostly depends on what you already use and what you do most. This is a shorter, adapted version of my full comparison. Read the full step-by-step guide on TechAI Guide: ChatGPT vs Gemini: Which Is Better in 2026? The Short Version If your life is in Gmail, Google Docs, and Google Drive, Gemini is the natural pick — it is already inside the tools you use, and its paid plan bundles 5 TB of storage. If you do project-based work, study with guided help, or spend a lot of time in Word, Excel, and PowerPoint, ChatGPT fits better. At the popular $20-a-month level they cost almost exactly the same, so price shouldn't decide it. Your workflow should. What They Cost (November 2026, US Prices) Plan ChatGPT Gemini Free $0 $0 Cheapest paid Go — $8/month Google AI Plus — $4.99/month, 400 GB storage Main paid Plus — $20/month Google AI Pro — $19.99/month, 5 TB storage Heavy use Pro — $100, $200 or $500/month Google AI Ultra — $99.99 or $199.99/month Two things worth knowing before you pay. ChatGPT shows ads on its Free and Go plans, but not on Plus or Pro. And eligible US college students can get Google AI Pro free for a year (redeem by 31 December 2026), while ChatGPT's four-free-months student offer closed on 31 October 2026. If you're a US student, that alone may settle it. Prices and model names change fast, so treat this as a mid-November snapshot and check the official pricing pages on the day you subscribe. The Models, Without the Jargon You don't need to memorise model names, but one thing matters: the free plan and the paid plan don't give you the same brain. ChatGPT's family is GPT-6 — the free plan runs GPT-6 Luna, Plus gets the stronger GPT-6.1 Sol, and Pro adds GPT-6 Astra for heavy reasoning, plus Codex for coding on Plus and above. Gemini's family is Gemini 4 — the free plan runs a light Flash model, while Google AI Pro gives you Gemini 4 Argon, the flagship, with a 1-million-token context window (roughly a whole book in one conversation). Google also just launched Gemini 4 Hydrogen (13 November 2026), a fast model that finishes tasks about 2.5 times faster than the old Gemini 3.8 Flash. The practical takeaway: on the free plans, both are good for everyday questions, but neither gives you the flagship. The real difference shows up when you pay — or when you feed in very long documents, where Gemini's 1-million-token window is genuinely hard to beat. How They Compare on Real Work Everyday writing: ChatGPT is slightly better at sounding natural and matching a tone — "make this friendlier" usually lands on the first try. Gemini is close, and writing inside Google Docs is convenient because you never leave the document. Edge: ChatGPT, unless you live in Docs. Research and long documents: This is Gemini's strongest round. Drop in a 200-page report or a pile of PDFs and ask questions across all of it. ChatGPT's context is much smaller, so long documents have to be split up. Both offer good deep research that browses the web, but for "here is everything, now find the answer", I reach for Gemini. Coding help: Both write code, explain errors, and help you debug. ChatGPT has the edge for most people because Codex is built into Plus and its step-by-step explanations are easier to follow when you're learning. Gemini is strong too, especially since Hydrogen is built for fast, multi-step coding work. For learning to code, start with ChatGPT; for speeding through tasks you already understand, try both and keep the one whose answers need less fixing. Images and video: Both generate images good enough for blog graphics and social posts. Gemini's paid plan adds video generation credits (Veo) and image tools inside Google apps, whi...

Powered by Readybot.io

ChatGPT vs Gemini in 2026: Which AI Is Better?

Latchkey vs Blacksmith: GitHub Actions Runner Costs

DEV Community

Latchkey vs Blacksmith for GitHub Actions runners

Latchkey and Blacksmith both replace ubuntu-latest with a one-line runs-on change and both bill per minute, so the choice comes down to three things: the rate, what you need beyond Linux x64, and whether a failed job costs you a re-run. Latchkey is $0.0025 a minute at 2 vCPU against Blacksmith at $0.004, and Blacksmith ships arm64, Windows and macOS runners plus 3,000 free minutes a month, none of which Latchkey has. One month of 10,000 Linux minutes at 2 vCPU on a private repository, at each vendor's published rate with its free allowance applied. Latchkey's plan fee of $5 to $49 sits on top of its bar and is drawn separately. Rates read 20 September 2026. Blacksmith sells managed GitHub Actions runners on its own hardware in Ashburn, Phoenix, Amsterdam and Frankfurt, adopted by installing a GitHub App and changing runs-on to a label such as blacksmith-4vcpu-ubuntu-2404. It publishes $0.004 a minute at 2 vCPU with 8 GB and an 80 GB disk, 3,000 free minutes a month on every plan, a colocated cache that intercepts actions/cache with 25 GB free per repository per week, and sticky disks for Docker layer reuse. Latchkey sells managed runners the same way and at $0.0025 a minute for the same 2 vCPU and 8 GB, on a 100 GB disk, with a self-healing agent that diagnoses and retries transient failures inside the run rather than handing them back red. It is missing things Blacksmith has shipped for a while: no arm64, no Windows, no macOS, and no free monthly allowance outside the trial and the plan tiers. Every cell below was read from the vendor's own public pricing or documentation page on 20 September 2026. Where a vendor does not publish a number, the cell says "not published" instead of carrying a guess. Comparison Blacksmith Latchkey Price per minute, 2 vCPU Linux x64 $0.004 (2 vCPU, 8 GB RAM, 80 GB disk) $0.0025 (2 vCPU, 8 GB RAM, 100 GB disk) Price per minute, 4 vCPU Linux x64 $0.008 (4 vCPU, 16 GB RAM, 80 GB disk) $0.005 (4 vCPU, 16 GB RAM, 100 GB disk) Free tier 3,000 free minutes a month, printed on every runner card 30-day trial, then 2,000 included minutes a month on Developer, 4,000 on Launch, 6,000 on Scale Billing granularity Not published Per minute Runner sizes and OS 2 to 32 vCPU on Linux x64 and arm64; Windows Server 2025 at 2 to 32 vCPU in public beta; macOS M4 at 6 and 12 vCPU 2, 4, 8 and 16 vCPU, Ubuntu 24.04 on x86_64 only. arm64, Windows and macOS are not offered Cache: type, limit, persistence Colocated cache intercepts actions/cache, 25 GB free per repo per week; sticky disks are ext4 volumes, 5 per job, evicted after 7 days idle, $0.50/GB/month Size not published. latchkey-dev/cache-action@v1 replaces actions/cache with a restore step and a save step, keeping path and key (no restore-keys); entries are isolated per organization, versioned by OS, kept 14 days; a failed restore warns rather than failing the job Docker build acceleration useblacksmith/setup-docker-builder@v2 and useblacksmith/build-push-action@v2; layers sit on a sticky disk per cache key, garbage-collected after 8 days unused latchkey-dev/docker-cache-action@v1, layers held in a Latchkey-managed private registry per organization Concurrency limits None imposed, per the runner docs Developer 20, Launch 40, Scale 80 busy runners per workspace; idle warm runners do not count Where jobs run Blacksmith's own regions: Ashburn, Phoenix, Amsterdam and Frankfurt Latchkey's own AWS estate on m6a-class hosts, one job per runner, no inbound access, destroyed after the job Self-healing or retries Not offered Built in: transient failures are diagnosed and retried inside the run Setup change required Install the GitHub App, then change the runs-on label to blacksmith-4vcpu-ubuntu-2404 A runs-on label change. GitHub organization accounts only Where Blacksmith wins, before anything else Blacksmith covers hardware Latchkey does not sell at all. arm64 is generally available at every size from 2 to 32 vCPU, Windows Server 2025 is in public beta across the same ...

Powered by Readybot.io

Latchkey vs Blacksmith: GitHub Actions Runner Costs

Document REST APIs with AI and DocStar

DEV Community

How to Document a REST API with DocStar: Let Your AI Agent Draft It

Documenting a REST API used to mean days of copying routes, parameters and example bodies into a docs tool. With DocStar, your AI coding agent does that part. Connect Claude Code, Cursor or Codex to DocStar, point it at your code, and it documents every endpoint as a draft: method, URL, auth, parameters, a full body example and sample responses. You review, test and publish an interactive API reference, and the same docs also give you a hosted MCP server and a CLI. This guide walks through the agent-first way to document a REST API with DocStar, what good endpoint docs need, and where your review matters most. What good REST API documentation includes Whether a person or an agent writes it, every endpoint needs the same parts. This is the checklist your agent works to, and the one you review against: Part What readers need Purpose One or two sentences on what the endpoint does and when to use it Method and URL GET, POST, PUT, PATCH or DELETE, plus the full path Parameters Query params and path variables, each with a description Authentication How to authorize the call Headers Required headers such as Content-Type Request body A complete, realistic example and what each field means Responses Real success and error examples Try it A way to send the request and see a live response DocStar has a home for each of these in its endpoint editor, and a TRY button on every published endpoint. Step 1: Connect your coding agent to DocStar DocStar runs an MCP server for documentation work. Add it to your agent as a remote MCP server: https://mcp.docstar.io/mcp
Or connect DocStar from your assistant's connector directory if it's listed there. The first time the agent needs your workspace, you sign in to DocStar in your browser. No API key to copy, and the agent only works with the collections your account can access. See Connect Your AI Assistant. Step 2: Ask it to document your API from the code Open the agent in your API's repository and ask in plain words: Use the DocStar MCP. Read our API routes and document every public endpoint in a new DocStar API collection called "Payments API". For each endpoint, write a clear description, every parameter and header, a full JSON body example with every accepted field, and sample success and error responses. The agent reads your routes and finds the auth, parameters, bodies and responses. It shows you the plan, including the collection slug, and waits for your OK. The slug is permanent and becomes part of your MCP URL and CLI commands, so pick something short like your product name. Already have an OpenAPI 3.x, Swagger 2.0 or Postman file? Ask the agent to import it first and then check each endpoint against your code. Our guide to going from OpenAPI to an MCP server shows that flow. Step 3: Review the drafts Everything the agent creates is a draft, so nothing is public yet. Open the collection in DocStar and review it like a pull request. The agent gets the structure right fast; your job is the judgment calls: Names. Action names readers scan for, like Create order or List invoices. Descriptions. Not "The order ID", but "ID of the order, returned by Create order. Starts with ord_." The same words are what AI assistants read when they call your API through your MCP server, so clear descriptions pay off twice. Body fields. Switch the body from Raw to Body Description to see a description box for every JSON field. Check units and formats (cents or dollars, ISO 8601 dates) and which fields are optional. What stays private. Internal or test-only endpoints can simply stay unpublished. Secrets. Tokens and keys belong in environment variables, never in URLs or examples. Want a change? Ask the agent ("make every description say when to use the endpoint") or edit directly. DocStar's editor is real-time and collaborative, so a writer and an engineer can work on the same page at once. Step 4: Test every endpoint against the real API Every endpoint in DocStar is also a working request, so you can check th...

Powered by Readybot.io

Document REST APIs with AI and DocStar

MCP Server Ecosystem: Data on 61,855 Servers

DEV Community

The State of MCP Servers: I analyzed all 61,855 of them

I run Serverdex, a directory that indexes the official Model Context Protocol (MCP) registry. That means I have a row for every published MCP server sitting in a database — so I pulled the numbers on all 61,855 of them and wrote up the state of the ecosystem. Here's what stood out. 1. The registry 100x'd in about a year In September 2025 the registry held a few hundred servers. It has roughly doubled month over month through 2026: Feb 2026: 845 new servers Jul 2026: 3,465 Sep 2026: 9,427 Oct 2026: 16,876 October 2026 alone added more servers than existed in the entire first six months. If you've felt like MCP went from "interesting protocol" to "everyone is shipping one" overnight — the data agrees. 2. Remote won MCP started as local stdio servers: you install a package and run it yourself. That is no longer the norm. 60% of servers are remote-only — a hosted HTTP endpoint with nothing to install. Only ~32% ship as a local package alone. streamable-http is now the dominant transport, well ahead of local stdio. The ecosystem has quietly shifted from "download and run" to "point your client at a URL." The median server is now something you connect to, not something you install. 3. How they ship Of the ~24,000 servers with an installable package: npm: 16,717 PyPI: 5,367 MCPB: 2,295 Docker (OCI): 1,794 NuGet: 175, Cargo: 129 MCP tooling is a JavaScript-first world, with Python a clear second. 4. A few giants, a long tail There are 1.27 million combined GitHub stars across the index — but attention is brutally concentrated. 92 servers hold 1,000+ stars each, while the vast majority have none. The head is names you'd expect: Browser Use, Scrapling, Metabase, CCXT, the official GitHub MCP server. 5. Open by default MIT is the default license of the MCP ecosystem by a wide margin (6,571 servers), with Apache-2.0 a distant second (778). The space is overwhelmingly open source. Methodology Numbers are from the Serverdex index of the official MCP registry, as of 15 November 2026: 61,855 published servers. Growth is by first-publication date. Languages, stars and licenses cover the servers with a resolvable public GitHub repository. "Remote-only" means a server advertises an HTTP endpoint and no installable package. The full report — with charts and the most-starred servers — is here: The State of MCP Servers. If you maintain an MCP server, it's probably already indexed; you can claim its page for free. And if you want a specific cut of the data (by category, by month, remote vs local over time), ask in the comments — happy to pull it.

Powered by Readybot.io

MCP Server Ecosystem: Data on 61,855 Servers

Fix Docker cgroupv2 Errors in CI Runners

DEV Community

Docker cannot enter cgroupv2, and cgroup mount errors in CI

Docker cannot enter cgroupv2, and the cgroup mount errors beside it, mean the runtime could not place your container into a control group, which on a CI runner nearly always means a daemon running nested inside another container without the v2 controllers delegated to it. Your image and your command are not involved: the container has nowhere to be accounted. A nested daemon can only place containers in cgroups its own environment was given. Delegation is what decides that, and it is not automatic. What this error means A container refuses to start and the message points at /sys/fs/cgroup rather than at anything you wrote, often with the phrase "with domain controllers" and a note that the state is invalid. It appears in Docker-in-Docker jobs, in Kubernetes-in-Docker tools, and in build engines that run their own daemon in a container, and it does not appear on a plain hosted runner doing a plain docker run. We did not reproduce it: it needs a nested daemon in a particular broken delegation state, and building one to photograph it would teach you less than the delegation rules below. The line below is quoted in full from containerd/containerd#6659, ctr prefix and line numbers included, because the clause that names the cause is in the middle of it: applying cgroup configuration for process. The Go file and line numbers move between runc releases, so match on that clause rather than on them. ```The line containerd/containerd#6659 reports (illustrative, not a recorded run) ctr: failed to create shim task: OCI runtime create failed: container_linux.go:380: starting container process caused: process_linux.go:385: applying cgroup configuration for process caused: cannot enter cgroupv2 "/sys/fs/cgroup/default" with domain controllers -- it is in an invalid state: unknown

Common causes

Controllers were never delegated to the nested environment

The dominant cause. A daemon inside a container gets a cgroup subtree, and the controllers it needs have to be enabled in that subtree by whatever created it. When they are not, the runtime cannot create the child cgroup for a new container, and reports that it cannot enter the cgroup it was given.

The cgroup mount is missing or read-only inside the container

A nested daemon needs /sys/fs/cgroup present and writable for its own subtree. A hardened container, a read-only mount, or a sandbox that hides the hierarchy produces mount errors in the same family, with the same effect: nothing can be placed in a cgroup.

The cgroup driver does not match the host

On a v2 host the default driver is systemd. A nested runtime or a tool configured for cgroupfs will fight the systemd hierarchy, and in our experience this is the cause when the error changes shape between runs rather than being stable.

The runtime predates proper cgroup v2 support

Older runc and containerd releases handle the unified hierarchy badly, which is what containerd/containerd#6659 is about. A pinned old runtime in a custom image, or a base image nobody has refreshed, is enough to reproduce a bug that current versions do not have.

How to fix it

Use the daemon the runner already has

  1. Drop the docker:dind service and run Docker commands directly in the job.
  2. If a tool insists on its own engine, check whether it has a mode that reuses the host daemon.
  3. Keep nesting for cases that genuinely need isolation between builds.

```.github/workflows/ci.yml
- run: docker info --format '{{.CgroupVersion}} {{.CgroupDriver}}'
- run: docker build -t app .
Give the nested daemon the cgroup environment it needs When you do nest, run the inner daemon with the host cgroup namespace and the privileges to manage its subtree, and mount the hierarchy so it is writable. This is a deliberate reduction in isolation, so scope it to the job that needs it rather than making it the default. docker run --privileged --cgroupns=host \
- v /sys/fs/cgroup:/sys/fs/cgroup:rw \
docker:29-dind
Align the cgroup drive...

Powered by Readybot.io

Fix Docker cgroupv2 Errors in CI Runners

Monetize Technical Content With No Upfront Costs

DEV Community

How to Monetize Technical Content Without Any Up‑Front Investment

Creating technical content—tutorials, code snippets, deep‑dive articles, or video walkthroughs—can be a rewarding way to share knowledge and build a personal brand. The challenge many creators face is turning that effort into a sustainable income stream without spending money on hosting, marketing tools, or paid advertising. Below is a practical roadmap that shows how you can start earning from your expertise today, using only free resources and a strategic approach. 1. Define a Precise Niche and Understand Your Audience A clear focus helps you stand out and attract a dedicated audience. Identify pain points: Look at forums, Q&A sites, and social media groups to see what questions keep recurring. Create audience personas: Sketch brief profiles—e.g., “mid‑level DevOps engineer looking for quick deployment tricks” or “student learning data‑science pipelines.” A well‑defined niche also makes it easier for platforms and sponsors to match you with relevant opportunities later on. 2. Use Free Platforms to Publish and Distribute Zero‑cost channels can host your content, drive traffic, and lay the groundwork for monetization. Platform Ideal Content Type Key Benefits GitHub Pages / GitLab Pages Written tutorials, documentation, static sites Free custom domain support, version control, SEO‑friendly YouTube Video tutorials, live coding, webinars Massive audience, built‑in ad revenue once thresholds are met Medium (Partner Program) Long‑form articles, case studies Access to a paying subscriber base Substack Newsletter‑style posts, curated resources Direct email list building, optional paid subscriptions Dev.to Blog posts, code snippets, community discussions Tech‑focused community, easy cross‑posting from GitHub Publish consistently on at least two of these platforms. Repurpose the same core material—e.g., turning a written guide into a short video—to maximize reach without extra creation effort. 3. Build Revenue Streams That Require No Up‑Front Costs Once you have a modest but engaged audience, you can layer on monetization methods that rely on existing traffic. a. Advertising and Platform Revenue Shares Platforms such as YouTube and Medium offer ad revenue sharing, typically requiring engagement metrics. The revenue is generated automatically from the views you already have. b. Affiliate Links for Relevant Tools Developer‑focused services (cloud providers, IDE plugins, API marketplaces) often run affiliate programs that pay per sign‑up or per sale. Insert contextual links within tutorials—e.g., a “Deploy this app with DigitalOcean” button—so readers can act immediately. c. Sponsored Content and Partnerships Brands looking to reach a technical audience sometimes pay for sponsored posts, product reviews, or webinars. Approach companies whose tools you already use and can speak about authentically. Since you’re not paying for ad placements, the cost is purely your time and the value you provide. d. Paid Memberships or Patreon‑Style Support If you consistently deliver deep, actionable content, a portion of your audience may be willing to support you directly. Platforms like Patreon, Ko‑fi, or a simple “Buy Me a Coffee” button let fans contribute monthly or per‑article. Offer exclusive perks—early access to drafts, private Q&A sessions, or a members‑only Discord channel—to make the tier attractive. e. Digital Products with Zero Production Cost Downloadable cheat sheets or reference PDFs: Compile the most common commands or patterns from your tutorials into a concise PDF. Host it on a free file‑sharing service and gate the download behind a small one‑time payment via PayPal or Stripe. Live workshops or webinars: Use free video‑conferencing tools to host paid live sessions. Since the content is delivered in real time, you don’t need to create a separate product beforehand. 4. Scale, Optimize, and Reinforce the Loop Monetization is an iterative process. As revenue streams start to flow, focus on scaling without re‑introducing upfront costs....

Powered by Readybot.io

Monetize Technical Content With No Upfront Costs

5 Best YouTube Transcript Tools Tested in 2026

DEV Community

5 Best YouTube Transcript Extractor Tools Tested in 2026: From CLI to Zero-Extension Markdown Notes

Watching a 90-minute technical talk, university lecture, or podcast interview at 2x speed is still slow when all you really need is to skim the structure, verify an exact quote at its timestamp, or pull a clean .srt or Markdown excerpt into your notes. If you have tried automating or streamlining YouTube transcripts recently, you have probably hit at least one of these three walls: Fragmented, overlapping auto-captions: YouTube's auto-generated tracks routinely chop a single spoken sentence into four or five 2-second cues with overlapping millisecond timestamps (for example, cue 1 runs 00:00:08,960 --> 00:00:13,599 while cue 2 already starts at 00:00:11,519). Unnecessary Whisper paywalls on captioned videos: Plenty of "AI transcript" wrappers force you to create an account and spend paid audio-transcription minutes even when a clean human or official CC track is already sitting on YouTube. Over-permissioned browser extensions: Installing a persistent Chrome extension that requests Read and change all your data on all websites just to copy a transcript—only for it to break the next time YouTube updates its page layout—gets old fast. To see which workflow holds up best in 2026, I tested 5 distinct YouTube transcript approaches side by side: YouTube's native panel, the gold-standard CLI extractor, a classic subtitle downloader, a minimal single-click web generator, and a zero-extension transcript reading workbench. TL;DR: Which One Should You Pick? Quick in-player check with no file export: Use YouTube's Built-in Transcript Panel. Zero setup, right next to the video. Batch CLI archiving of playlists and raw .vtt/.srt tracks: Use yt-dlp. Free, open-source, scriptable, and unbeatable in the terminal. One-off full-file SRT or VTT download across 50+ streaming sites: Use DownSub. Fast whole-file downloads without installing software. Occasional full-text copy and quick AI summary (under 25 videos): Use YouTube-Transcript.io. Clean UI with 25 free no-signup tokens. No-login, no-extension browser reading (youtube.com swapped to vidleaf.app), exact quoted phrase search, multi-line selection exported to SRT/TXT/Markdown with timestamps, and confirmation-gated audio transcription when captions are missing: Use Vidleaf. Side-by-Side Comparison Matrix Tool Setup & Access Export Formats Fallback When Video Has No CC Standout Workflow Strength Free Tier & Pricing Model YouTube Built-in Transcript Native web UI · No login Manual highlight & copy-paste only (no file export) None (button hidden if no captions exist) Syncs directly with playback inside YouTube 100% Free yt-dlp Local CLI · Terminal setup Raw .vtt, .srt, .ttml, .json3 None natively (must pipe audio into local Whisper) Regex language filtering & full-playlist batching Free & Open Source (Unlicense) DownSub Web app / optional extension · No login Full-track SRT, VTT, TXT Basic downloader stops; separate Video to Text Pro tool bills 5 credits/min Supports YouTube, Viki, Viu, Dailymotion & 50+ sites Free for existing subtitles (ad-supported); paid credits for AI transcription YouTube-Transcript.io Web app · No signup for starter tier One-click full-text copy or plain-text download Relies on available public video transcripts Minimal UI with one-click summary & token-based bulk tool 25 free extractions (tokens); paid token packs afterward Vidleaf Browser workbench · No extension, no-login guest reads Full track or selected lines in SRT, TXT, and timestamped Markdown Yes (checks free existing captions first; asks before using audio minutes + speaker labels) Swap youtube.com to vidleaf.app in URL bar; quoted phrase search; bilingual view; also supports Bilibili Free existing-caption reads & exports (unlimited with free account); 30 free audio mins/week + rollover signup bonus; one-time packs, no subscriptions Hands-On Breakdown of All 5 Options 1. YouTube's Built-in Transcript Panel: Zero-Friction Spot Checks Expanding a YouTube video's description and clicking Show transcript opens a...

Powered by Readybot.io

5 Best YouTube Transcript Tools Tested in 2026

Synaphi: Accessible AI Medical Communication

DEV Community

Beyond Words: Building Synaphi, an AI Medical Assistant for Everyone

This is a submission for the MLH × DEV Writing Challenge, featuring Synaphi, built at EmoryHacks 2026. What I Built Imagine understanding every word your doctor says, but being unable to answer a single question. "Where does it hurt?" For someone recovering from a stroke, living with severe paralysis, or temporarily unable to speak, that simple question can become an enormous communication barrier. And the problem isn't necessarily understanding. It's having a reliable way to respond. At EmoryHacks 2026, we built Synaphi — an AI-assisted patient–clinician communication platform designed around a simple principle: everyone deserves to be heard. Synaphi brings clinicians and patients into a synchronized communication experience. The clinician asks questions, and the patient responds through an accessible interface designed to support different physical and communication abilities. The patient can use: Large visual and pictogram-based choices Keyboard and single-switch scanning Dwell-based selection Touch controls Browser-supported speech input Experimental webcam-based blink and head gestures The platform also supports five languages and can help patients communicate pain location, intensity, and quality. But we made one architectural decision that defines the entire project: AI can help decide which choices to offer. It cannot decide what the patient means. The patient selects the information. The patient confirms the final message. The clinician remains responsible for clinical decisions. Why not just build another medical chatbot? Because a chatbot that guesses what a person wants to say can introduce a serious problem: it might express something the person never intended. For example, if someone indicates pain, AI should not invent additional symptoms or make statements about consent. Our solution separates AI-powered interpretation from deterministic message construction. The intelligence assists communication. It does not replace the communicator. How Synaphi works The system follows a six-stage interaction: A clinician asks a question, such as "Where does it hurt?" Google Gemini interprets the question into structured intent, response type, and search terms. Typesense retrieves relevant choices from a curated communication library. Synaphi displays large, accessible response cards. The patient selects and reviews a response using their preferred controls. Only after confirmation is the message spoken aloud and sent to the clinician. Our frontend uses React and TypeScript. A Python FastAPI backend handles communication and AI orchestration, with WebSockets synchronizing clinician and patient views. We designed the experience to support two devices or a split-screen demonstration. The hardest accessibility problem: one button A patient with very limited mobility may be able to activate only one switch. That means ordinary buttons, dropdowns, and text entry become difficult to use. We implemented two-level switch scanning. Instead of cycling through every possible button individually, the interface highlights groups of actions first, then individual choices within the selected group. The patient can navigate and respond using the Space key as a switch. We tested a complete question-answering flow using only Space-key input. Our emergency Help action remains available independently of Gemini and Typesense. Even when the AI services fail, the patient can still trigger the prototype's local help notification. Experimental facial controls Synaphi also implements facial-landmark detection using MediaPipe inside the browser. For intentional-blink selection, the detection logic includes a calibrated threshold, a deliberate hold period, hysteresis, cooldown, and a requirement to reopen the eyes before another selection. These safeguards are intended to reduce accidental activations. The detection logic has unit tests, and the MediaPipe model has been loaded in automated browser testing. However, reliable blink/head operation on ...

Powered by Readybot.io

Synaphi: Accessible AI Medical Communication

Serverless Invoicing: Secure, Offline-First PDFs

DEV Community

Stop Renting Your Business Data: The Case for Serverless Invoicing

Every month, thousands of freelancers pay a 'convenience tax' to SaaS providers to host simple PDF documents on a remote server. From an engineering perspective, this is absurd. Why are we offloading sensitive financial PII to a third-party cloud simply to generate a document that is fundamentally local in nature? When we started Vanza Invoices, we weren't just building a tool; we were challenging the assumption that 'connected' means 'useful.' By moving to an offline-first architecture using React and Capacitor, we eliminated the need for a backend entirely. Here is why the serverless paradigm is the ultimate security and performance upgrade for utility-based software. The Liability of the Centralized Database When you build a SaaS invoicing platform, you become a custodian of your users' most sensitive data: tax IDs, bank details, and client lists. Every database entry is a potential liability. If you store this data, you are responsible for it. You need SOC2 compliance, you need to worry about SQL injection, and you need to manage the inevitable data leaks that plague cloud-based providers. By moving the data to the client-side, we shifted the paradigm. With Vanza Invoices, the 'backend' is the user's filesystem. We use IndexedDB for persistent storage, keeping the data isolated within the app's sandboxed environment. If there is no server, there is no server to hack. The security perimeter is defined by the user’s device security, not our API authentication logic. Performance Through Elimination Network latency is the enemy of productivity. In a traditional SaaS model, clicking 'Generate PDF' triggers a round-trip to an API, server-side template rendering, a database lookup, and a storage upload. This is a massive chain of failure points. If the internet drops or the server is under load, the contractor is stuck waiting on a spinner while their client is standing right in front of them. Our approach is to move the computation to the edge—literally. By running the PDF generation engine directly within the Capacitor runtime, the latency is effectively zero. The data is already there. The PDF is rendered in the local webview. The signature is captured on the canvas and serialized into the local blob. It’s an instantaneous loop that feels native because it is native. The Economics of Zero-Subscriptions There is a hidden cost to SaaS that goes beyond the monthly subscription fee: the developer's operational overhead. Managing AWS Lambda costs, database instance sizes, and load balancing for a tool that is used sporadically is inefficient engineering. We realized that by stripping away the backend, we stripped away the recurring costs. This allowed us to build a sustainable model where we don't have to charge monthly rent for a static document. We are currently detailing the shift in the industry and why many developers are following suit in our full bilingual guide with interactive checklists. Engineering for Ownership Moving to an offline-first architecture requires a shift in how you handle state synchronization and local conflict resolution. However, for utility apps—invoicing, estimates, contracts—the benefits are undeniable: Total Data Sovereignty: Users own their data in a raw format they can export at any time. Zero Server Costs: We don't have a recurring monthly burn rate to keep the app functional. Privacy-by-Architecture: We don't track user activity because we don't have a server to send logs to. Reliability: The app works in a basement, on an airplane, or at a remote job site with no bars. If you are a developer looking to build tools that respect user autonomy, I encourage you to look at the Vanza Invoices stack. Stop building systems that rely on a persistent connection and start building tools that empower the user to own their workflow. You can see how this works in practice by downloading the app here: https://play.google.com/store/apps/details?id=app.vanza.docs

Powered by Readybot.io

Serverless Invoicing: Secure, Offline-First PDFs

Graceful Shutdown in Node.js: Prevent Data Loss

DEV Community

Mastering Graceful Shutdown in Node.js: Prevent Data Loss in Production

What Is a Graceful Shutdown? Graceful shutdown in Node.js is the process of stopping an application server cleanly without interrupting ongoing user requests or damaging active database operations. Instead of instantly killing the application process when a stop command is issued, the server stops accepting new incoming traffic, finishes processing current HTTP requests, closes database connections properly, and then exits safely. This process guarantees that active users do not experience sudden network errors and that pending write operations complete successfully. A Real-World Analogy: Closing a Restaurant Imagine a busy restaurant reaching its closing hour at 10:00 PM. The management does not turn off the dining room lights and kick paying customers out in the middle of their meals. Instead, the host locks the front door to prevent new diners from walking in, while the kitchen continues cooking orders for guests who are already seated. Once every remaining customer finishes eating, pays their bill, and leaves, the staff cleans up, turns off the stoves, and locks the building. In Node.js, shutting down gracefully works the exact same way: locking the front door represents refusing new HTTP connections, and letting seated guests finish their meals represents completing active requests. Why It Matters in Modern Tech Infrastructure In cloud platforms like Kubernetes, Docker, or AWS, application servers are frequently restarted, autoscaled, or redeployed during software updates. When the host system needs to terminate your application instance, it sends an operating system signal such as SIGTERM to announce the upcoming shutdown. Without a graceful shutdown handler, your server immediately dies mid-operation. If a user is currently submitting a credit card payment or saving a profile update to a MySQL database when that process dies, their HTTP request drops instantly and database records can be left in an incomplete or corrupted state. Engineers implement graceful shutdowns so that deployments and server restarts happen with zero downtime and zero data loss. Code Example: Graceful Shutdown in Express.js Here is how you can set up a clean termination workflow in an Express application connected to a MySQL database pool using native Node.js process events: const express = require('express');
const mysql = require('mysql2/promise');

const app = express();
const PORT = process.env.PORT || 3000;

// Create a MySQL database connection pool
const dbPool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'secretpassword',
database: 'production_db',
connectionLimit: 10
});

app.get('/checkout', async (req, res) => {
try {
// Simulate a database operation
await dbPool.query('INSERT INTO orders (status) VALUES (?)', ['completed']);
res.status(200).send('Order processed successfully!');
} catch (error) {
res.status(500).send('Database transaction failed');
}
});

const server = app.listen(PORT, () => {
console.log(Express app listening on port ${PORT});
});

// Handle process termination signals
function shutdownGracefully(signal) {
console.log(Received ${signal}. Starting graceful shutdown...);

// Stop accepting new network connections
server.close(async () => {
console.log('HTTP server closed. No longer accepting new requests.');

try {
  // Close database connection pool cleanly
  await dbPool.end();
  console.log('MySQL database connection pool drained and closed.');
  process.exit(0);
} catch (error) {
  console.error('Error shutting down database pool:', error);
  process.exit(1);
}

});
}

// Register listeners for common termination signals
process.on('SIGTERM', () => shutdownGracefully('SIGTERM'));
process.on('SIGINT', () => shutdownGracefully('SIGINT'));
Key Takeaway Graceful shutdown is a fundamental backend reliability pattern that converts abrupt, risky server kills into predictable, controlled exits. By taking a few extra...

Powered by Readybot.io

Graceful Shutdown in Node.js: Prevent Data Loss

Fix GitHub Actions Cache Path Validation Errors

DEV Community

GitHub Actions Path Validation Error caching, on a green job

A GitHub Actions Path Validation Error caching line means the glob in your path input matched nothing when the post step ran, so no archive was built and nothing was uploaded. The job stays green, because the action writes that line with core.info rather than as a warning annotation. The throw sits above the try block, so it is caught by the action rather than by the toolkit, and logWarning routes it to core.info. What this error means Every run installs from scratch and every run is successful. There is no red step, no annotation in the run summary, and the restore step near the top of the job reports a miss in the ordinary way that a first run would. The only evidence is one line inside the collapsed post step at the very bottom, which says that the paths specified for caching do not exist and that no cache is being saved. Because it is not an annotation, it does not appear in the run summary, it does not show in the job list, and searching the repository for failed runs will never surface it. ```Reconstructed: the message is thrown by @actions/cache saveCache and the [warning] prefix is added by logWarning in actions/cache v6.1.0. Not captured from a run [warning]Path Validation Error: Path(s) specified in the action for caching do(es) not exist, hence no cache is being saved.

Common causes

The directory is created by a step that did not run

A cache step wraps a build that was skipped by an if:, or that lives in another job, or that failed earlier while the workflow carried on. The post step then runs against a workspace where the directory was never created, matches nothing, and says so quietly.

The tool writes somewhere other than where you cached

Package manager store locations move between versions and between operating systems. Caching a hard-coded home-relative path on a Windows runner, or a Linux default on macOS, gives a path that is correct in general and absent on this runner. Asking the tool for its own path is the durable fix.

A later step deleted or moved the directory

A cleanup step, a container that mounted over the location, or a build that relocates its output between phases can all leave the path empty by the time the post step runs. The post step is the last thing in the job, so it sees the end state and not the state the cache step was written for.

The path input resolved to an empty string

An expression that produced nothing, or a multi-line input where the only line was a comment, leaves the action with no patterns at all. This is the second message rather than the first, and it is worth reading which one you have before changing anything.

How to fix it

Find the line before you change the path

  1. Open a run and expand the last step, named Post followed by your cache step name.
  2. Look for a line starting [warning]Path Validation Error; it is plain output, not an annotation.
  3. If the line names paths not existing, the directory was absent at the end of the job.
  4. If it says at least one path is required, the path input itself was empty and the expression that builds it is the bug.

Ask the tool where its cache is

Hard-coded store paths are the most common cause of a path that exists on one runner and not another. Resolve it at run time and feed it into the cache step, which keeps working across matrix operating systems and across tool upgrades.

.github/workflows/ci.yml (illustrative) - id: pip run: echo "dir=$(pip cache dir)" >> "$GITHUB_OUTPUT" - uses: actions/cache@v6 with: path: ${{ steps.pip.outputs.dir }} key: pip-${{ runner.os }}-${{ hashFiles('**/requirements*.txt') }} Make the absence loud The action will not fail for you, so add the check yourself while you are settling this. One line at the end of the job turns a silent miss into a red step that names the directory, and it costs nothing once the path is right..github/workflows/ci.yml (illustrative) name: Assert the cache path exists if...

Powered by Readybot.io

Fix GitHub Actions Cache Path Validation Errors

Search Greenhouse, Lever and Ashby Jobs in One Place

DEV Community

How to search Greenhouse, Lever and Ashby jobs in one place

A company's own careers page is the source of its job postings, and that page usually runs on a hiring platform such as Greenhouse, Lever, Ashby or Workday. Job boards copy the posting from there, and a copy can stay up after the original has closed. So the freshest place to look for a job is the company career pages themselves. The trouble is that there are thousands of them, and no single search box covers them all. This post shows two ways to search Greenhouse, Lever and Ashby jobs in one place: With Google and the site: operator, which is free and takes some work. With JobBloom, an AI job search engine I built to do the same thing in one sentence. Method 1: Google with the site: operator Each hiring platform hosts its customers' job pages on one domain. If you limit a Google search to that domain, you search every company on the platform at once. Platform Domain to search Greenhouse boards.greenhouse.io, job-boards.greenhouse.io Lever jobs.lever.co Ashby jobs.ashbyhq.com Workable apply.workable.com SmartRecruiters jobs.smartrecruiters.com A search for remote backend jobs on the first three looks like this: (site:boards.greenhouse.io OR site:job-boards.greenhouse.io OR site:jobs.lever.co OR site:jobs.ashbyhq.com) "backend engineer" (TypeScript OR "Node.js") remote
This works, and it's worth knowing. It also has four problems: Many results are closed. Google keeps a page in its index after the job is taken down. You only find out when you click. "Remote" is just a word. A posting that says "this role is not remote" matches the search as well as a remote job does. There is no ranking by fit. You get pages that contain your words, in Google's order, and you read each one to see if it suits you. The query gets long. Every platform you add is another site: clause, and Google limits a search to 32 words. Method 2: describe the job in one sentence JobBloom runs the same kind of search for you. You describe the job in plain English, and it searches company career pages on 33 hiring platforms. It's free and needs no sign-up. I typed "Remote backend engineer jobs, TypeScript or Node.js" on 15 November 2026. This is what came back: The search returned six open jobs. The top three were a Senior Backend Engineer (Node.js) role at Nuitée with a 95% match, a Senior Backend Engineer role at Ora So with 92%, and a TypeScript role at Databento with 88%. Each result has a short summary, skill tags, the location, and the hiring platform the posting came from. "View role" opens the original posting on the company's own page. You can also choose which platforms to search. All 33 are on by default: How the search works The pipeline has five steps. The loading screen groups them into three stages and updates as they run: 1. Understand the sentence An LLM reads the prompt and pulls out the hard requirements: the role, the location, whether the job is remote, hybrid or on-site, the job type, and the must-have skills. It then writes a short search query. You can see that query on the results page, next to "Searched for". 2. Search career pages only The query goes to a web search API, limited to the domains of the supported hiring platforms. This is the site: trick from method 1, applied to 33 platforms at once. Each platform is a small config entry: export const approvedJobHostFamilies = [
{
provider: 'greenhouse',
queryHost: 'boards.greenhouse.io',
hosts: [
'boards.greenhouse.io',
'job-boards.greenhouse.io',
'job-boards.eu.greenhouse.io',
'boards.eu.greenhouse.io',
],
},
{
provider: 'lever',
queryHost: 'jobs.lever.co',
hosts: ['jobs.lever.co', 'jobs.eu.lever.co'],
},
// ...31 more
]
Because the search is limited to these domains, job boards and reposts never enter the results. 3. Rank cheaply before any AI reads a page LLM calls are the expensive part of a search, so results first get a ranking from free signals: title match, location and age. Only the most promising ones go on to t...

Powered by Readybot.io

Search Greenhouse, Lever and Ashby Jobs in One Place

AI Agent Access Reviews: 3 Essential Security Checks

DEV Community

Add AI agents to your access review in three checks (owner, scope, stop and evidence)

Add AI agents to your access review in three checks Treat each AI agent like an entitlement in your next access review, and ask three questions of it. Who owns it? What can it reach? How do you stop it, and what evidence shows that you can? A thread on r/grc asked the question that started this. u/TheAccountableAgent asked whether access reviews include AI agent permissions. They noted that coding agents now store "always allow" rules locally, and that Apiiro had reported 1,379 secrets in public copies of one tool's settings file. Their approach: treat each saved agent permission like an IAM entitlement with an owner, a business reason, the data it reaches and a review date. They asked how others evidence this for auditors. My answer builds on an argument I made in Agents don't go rogue. Scopes do. Scope is what an agent can technically do whatever its instructions say: the identities it holds, the tools it can call, the hosts it can reach and the actions it can take without a human. The people who deploy an agent set its scope. "Out of scope and nothing stopped it" is a finding, and a finding can be owned, fixed and audited. Check 1: Who owns the agent? The AI Security Wiki's Agentic AI Security and Tool Governance standard says no agent should be approved until its owner, identity, tools, APIs, data boundary, approval workflow, logging, kill switch and SOC integration are defined. The r/grc replies add the practical detail: Split the owner in two. LogicManager suggested separating the technical maintainer from the business owner. The maintainer explains how the permission is configured. The business owner explains why the agent needs it. The person who configured the permission shouldn't become its business owner by default. Expect orphans. Ok_Statistician_997 said the owner field trips people up. A dev sets the permission, changes teams and nobody updates it. Tying the review date to the service account's own access cycle helped them stop it drifting. Make a move trigger a review. LogicManager again: a developer changing teams should trigger a review, not an assumption that the permission is still appropriate. Decide your source of truth first. AlwaysAnotherShot pointed out that many of these permissions live in a settings file on a developer's laptop. If endpoint tooling can collect the config files, sample from that like any entitlement list. If not, you rely on developers self-declaring, and the control description should say so plainly. Check 2: What can it reach? This is the scope inventory. One row per agent. Here is a sketch built from the fields the thread and the wiki name. The field names are mine. agent: support-triage-bot
purpose: "Summarise and label inbound tickets"
business_owner:
technical_maintainer:
identity: own workload identity # not a person's login, not a shared key
tools: [ticket.read, ticket.label]
hosts_reachable: [tickets.internal.example]
data_scope: "Ticket text only"
secrets_in_agent_environment: none
can_modify_credentials: false
actions_without_human: [label]
approval_required_for: [delete, send, change-permissions]
last_reattested: 2026-11-01
next_review:
Fields to include, all from the sources: Owner, purpose, data scope, connected tools, secrets, approval boundaries, and whether the agent can create or modify other credentials (macromind on r/grc). Effective permissions and recent use at each review. Flag dormant grants and broad wildcard access (macromind). Wildcard "always allow" rules on shell commands, or anything that can read .env files: treat as a finding by default, with a written exception and an expiry date (AlwaysAnotherShot). Then check the inventory against how the agent is actually wired. The r/AskNetsec thread on beginner mistakes securing agent credentials gives a quick list of what to look for: A shared admin key "for testing", tokens kept overnight, and tool calls logged without the human, task and scope behind each request. That is the original poster's own lis...

Powered by Readybot.io

AI Agent Access Reviews: 3 Essential Security Checks

Migrate Safely to the Dynatrace OTel Collector

DEV Community

Dynatrace OTel Collector: migrating from Contrib safely

Your Collector runs the Contrib image, and someone has asked you to move to the Dynatrace OTel Collector. Before you touch a line of configuration, one question matters: what actually changes? This article skips the tour of Contrib, which our Contrib components guide already covers. It looks only at the gap between the two builds and at the migration path. What I read: the introduction and deployment pages of the Dynatrace documentation, then the README of the GitHub repository, on 9 November 2026. Version numbers are those shown on those pages that day. I did not run the binary, so anything about runtime behaviour is flagged as unverified. Dynatrace OTel Collector: a curated subset, not a fork The documentation is explicit. The distribution keeps the standard configuration model and component semantics, with no proprietary abstractions and no hidden defaults. In practice, a YAML file written for Contrib stays readable as it is. The difference lies in what is compiled into the binary. Contrib ships a very large number of third-party receivers, processors and exporters. Dynatrace advises against it in production and keeps it for testing, because it uses more storage and memory than a targeted build. The Dynatrace distribution contains only components the vendor says it has verified for common use cases. The full list lives in the GitHub repository, not in the documentation. What the distribution removes: read the list before you switch This is where migrations go wrong. A component missing from the binary stops the Collector from starting, because it rejects a configuration that names an unknown component. Here is what I found in the README’s Components section. Source : github.com Receivers: otlp, filelog, hostmetrics, prometheus, statsd, syslog, fluentforward, jaeger, zipkin, kafka, kafka_metrics and four Kubernetes receivers (k8sobjects, k8scluster, kubelet_stats, k8sevents). Journald and netflow are alpha. Processors: batch, memory_limiter, attributes, resource, resource_detection, transform, k8sattributes, cumulativetodelta, metricstarttime, probabilistic_sampler, tail_sampling and groupbytrace, plus filter, redaction and genainormalizer in alpha. Exporters: otlp, otlphttp, debug and kafka, plus load_balancing in alpha. Extensions and connectors: bearertokenauth, oidcauth, healthcheck, zpages, k8sleaderelector and filestorage; forward and, in alpha, spanmetrics. I could not find a Prometheus Remote Write exporter, a file exporter, a SQL receiver or a Docker receiver in that list. If your pipelines rely on one of them, you need another route. That absence comes from my reading of the README. I did not compile the binary to confirm it. What you get in return The distribution adds no hidden feature. It adds guarantees around the binary. Bounded vendor support: x86-64 and ARM64 builds are covered by Dynatrace Support. Each minor version is supported for three months, with fixes shipped as a patch on the latest supported minor or in the next minor. Independent security patches: Dynatrace says it receives them without waiting for upstream Collector releases. Signed images: they are signed with cosign from version 0.49.0 and published on GHCR, Amazon ECR Public and Docker Hub. Limited support for everything else: for Contrib, Dynatrace only covers the components and versions present in its own distribution. GitHub issues are handled on a best-effort basis, with no contract or SLA. So the trade is fewer components for a supported perimeter. For an SRE team, it also means less surface to patch, and therefore less toil with every CVE. Migrating from Contrib in five steps This is the route I would follow. It starts from your real configuration, not from the documentation. List the components you use. With yq (version 4), print what your pipelines name, then the extensions: yq '.service.pipelines[] | .receivers[], .processors[], .exporters[]' config.yaml \
| sed 's#/.##' | sort -u
yq '.service.extensions[]' config.yaml | sed 's...

Powered by Readybot.io*

Migrate Safely to the Dynatrace OTel Collector

Claude Code Telemetry: Remove Email, Sum Usage

DEV Community

Claude Code OpenTelemetry: Drop user.email, Sum the Deltas

If you want to record your own or your team's Claude Code telemetry (token, cost and session usage), its OpenTelemetry export (sent over OTLP, the open standard protocol for logs and metrics) will do it, and it writes your signed-in account email onto log records and metric points whenever the email is available. I set up a local OpenTelemetry Collector (a small service that receives and forwards that data) that deletes that field and writes plain JSON lines. Summing the points in those files gives Claude Code token usage and cost totals that add up, as long as no points go missing. When you finish, you have a collector listening only on localhost that keeps session, token and cost records on local disk without the email. One counting rule matters: each metric point (one numeric reading per export) is a delta (only the change since the previous export), so the total is the sum of all points. Reading the last point gives you the wrong number. The OpenTelemetry export carries user.email Turning the export on takes a handful of environment variables. What comes with them is the problem. Claude Code's monitoring docs mark user.email as "Always included when available." They list switches for some other attributes, such as the session ID, account UUID and version. None of the switches covers the email. The fix is a drop processor (the attributes processor with action: delete): one collector rule that deletes a named field before anything is written. In an early trial without the rule, the token usage points carried user.email among 11 attribute keys. With the rule in place, a pre-install trial of two short headless Claude Code sessions against a scratch collector showed the email on 0 of 150 log records and 0 of 14 metric points. The account UUID and organization ID stayed. They are opaque IDs rather than an address, though each is still stable per account, and they are what lets you split usage per account. OpenTelemetry Collector config for Claude Code Claude Code gets the environment block below. The endpoint is the loopback HTTP receiver. The host name and namespace are placeholders. {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1",
"OTEL_METRICS_EXPORTER": "otlp",
"OTEL_LOGS_EXPORTER": "otlp",
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://127.0.0.1:14318",
"OTEL_RESOURCE_ATTRIBUTES": "host.name=,service.namespace="
}
I left OTEL_LOG_USER_PROMPTS and OTEL_LOG_TOOL_DETAILS unset. The docs say prompt content is logged only when the first one is set. Setting or removing any of these keys affects new sessions only. The collector side, with both pipelines: receivers:
otlp:
protocols:
http:
endpoint: 127.0.0.1:14318
processors:
attributes/drop-email:
actions:
- key: user.email
action: delete
exporters:
file/metrics:
path: /data/metrics.jsonl
format: json
flush_interval: 5s
rotation: {max_megabytes: 100, max_days: 0, max_backups: 0}
file/logs:
path: /data/logs.jsonl
format: json
flush_interval: 5s
rotation: {max_megabytes: 100, max_days: 0, max_backups: 0}
service:
pipelines:
metrics:
receivers: [otlp]
processors: [attributes/drop-email] # memory_limiter and batch omitted here
exporters: [file/metrics]
logs:
receivers: [otlp]
processors: [attributes/drop-email]
exporters: [file/logs]
The real config also has a gRPC receiver, a health check, memory_limiter and batch, and a second exporter on each pipeline that feeds a local SigNoz backend. All listeners bind 127.0.0.1. Processors run before exporters, so the email is gone before any exporter sees the record. max_days: 0 turns off age-based removal, and max_backups: 0 keeps every rotated file. There is no compression key, so the output is plain JSON lines. Why not the obvious approach? Three moves look reasonable at first. I rejected all three: one after testing it, one after it had already ...

Powered by Readybot.io

Claude Code Telemetry: Remove Email, Sum Usage

** Latest from PC Gamer **

Chronicles: Medieval developer declared bankruptcy on the day it announced it was in trouble, but development will continue after a $5 million reprieve from Coffee Stain

A new studio overseen by "key members" of the Chronicles team will finish the historical strategy.

Powered by Readybot.io

** Latest from PC Gamer ** ## Chronicles: Medieval developer

Fix GitHub Actions Composite Action Input Errors

DEV Community

GitHub Actions composite action unexpected input

GitHub Actions composite action unexpected input is the runner warning Unexpected input(s) 'x', valid inputs are [...]: a key in your with: is not declared under inputs in the action's action.yml, so the value is dropped and the step carries on without it. Its fatal neighbor, Required property is missing: shell, means a run step inside the composite action has no shell, and the fix is to add one, such as shell: bash, to every run step. Two separate checks on two separate files. The metadata is validated first and fails the job; the input names are compared second and only warn. What this error means Two different failures wear this label and they are worth separating on sight. The first is a warning: the step runs, the job is green, and one option you thought you set had no effect, with a line in the log naming the key you passed and listing the ones the action accepts. The second is fatal and arrives before anything runs: the action fails to load, and the message names a file, a line and a column inside the action rather than inside your workflow. Both are decided by reading the action metadata. ```Actions log, quoted from grafana/install-alloy#2 Error: grafana/install-alloy/main/action.yml (Line: 13, Col: 7): Required property is missing: shell Error: GitHub.DistributedTask.ObjectTemplating.TemplateValidationException: The template is not valid. grafana/install-alloy/main/action.yml (Line: 13, Col: 7): Required property is missing: shell [three stack frames omitted] Error: Failed to load grafana/install-alloy/main/action.yml

Common causes

The input is not declared in the action metadata

The ordinary case, and usually a spelling or a separator: an underscore where the action uses a hyphen, a plural where the action is singular. The identifier rules allow both spellings, so nothing about the name is invalid, it is just not the one the action published.

A composite run step has no shell

The step key that is optional everywhere else and required here. It is easy to lose when a run step is copied out of a workflow into an action, because in a workflow the job default supplies it. The action then fails to load for every caller at once, which is why this often arrives as a report from a consumer.

The action is read from a different ref than you think

The valid list is built from the metadata at the ref you pinned. A floating major tag that moved, or a local action on a branch you did not check out, gives a list that does not match the documentation you were reading. Pinning to a SHA removes the question.

The value is read through the toolkit rather than the context

Inside a composite action the input environment variables are not set, so a nested script action asking the toolkit for an input gets an empty string. Nothing warns, because from the runner's point of view the input was accepted and passed on as declared.

How to fix it

Read the file position in the message first

  1. If the path in the error is an action.yml, the metadata is the problem and the job will not start.
  2. If the message is a warning naming a key and a valid list, the metadata loaded and only the name is wrong.
  3. If neither appears and the value is empty inside the action, it is declared but read the wrong way.

Put a shell on every composite run step

There is no job default inside an action, so each run step carries its own. Anything on the documented shell list works; bash is the portable default on the hosted Linux and macOS images and is available on the Windows ones.

.github/actions/setup/action.yml (illustrative) runs: using: composite steps: - run: ./install.sh shell: bash Declare the input, then read it through the inputs context Declaring it makes the key expected; reading it through the context is what makes the value arrive. Give optional inputs a default so the action behaves predictably when the caller says nothing, and remember that every default is a string..github/acti...

Powered by Readybot.io

Fix GitHub Actions Composite Action Input Errors