<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: dsplce.co</title>
    <description>The latest articles on DEV Community by dsplce.co (@dsplce-co).</description>
    <link>https://dev.to/dsplce-co</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3980080%2F1fef55d1-b7ab-4758-b48c-b8538b54f525.png</url>
      <title>DEV Community: dsplce.co</title>
      <link>https://dev.to/dsplce-co</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dsplce-co"/>
    <language>en</language>
    <item>
      <title>Claude Opus 5: everything you need to know</title>
      <dc:creator>dsplce.co</dc:creator>
      <pubDate>Sun, 26 Jul 2026 07:08:55 +0000</pubDate>
      <link>https://dev.to/dsplce-co/claude-opus-5-everything-you-need-to-know-le7</link>
      <guid>https://dev.to/dsplce-co/claude-opus-5-everything-you-need-to-know-le7</guid>
      <description>&lt;p&gt;Claude Opus 5 shipped on the 24th of July, 2026, as &lt;code&gt;claude-opus-5&lt;/code&gt;. It's priced at $5 per million input tokens and $25 per million output tokens — the same as Opus 4.8, and half of what Fable 5 costs.&lt;/p&gt;

&lt;p&gt;The context window is 1M tokens long, the max output is 128K. And there's one breaking change: the model thinks by default now. In other words, on the older Opus you had to ask for thinking explicitly, and it's now set up so it thinks on its own automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually changed from the previous Opus?
&lt;/h2&gt;

&lt;p&gt;Now it's allocating thinking depth per turn itself depending on the set effort level; the higher the effort, the more it thinks, but you can still set it explicitly in the request, and the parameter itself hasn't changed — &lt;code&gt;thinking: {"type": "adaptive"}&lt;/code&gt; is still valid and still means the default. This means that when migrating a project that didn't do any thinking just by renaming it to this new Opus, you need to make sure you have enough room for both the thinking and the visible part of its response, so raise &lt;code&gt;max_tokens&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Also, if you disable thinking — which you can now do only at high effort or below, because pairing it with xhigh or max returns a 400 — it may output a call to a tool as plain text instead of emitting a proper &lt;code&gt;tool_use&lt;/code&gt; block, or leak some of its internal tags into the output. So they recommend keeping it enabled and controlling the cost via the effort level instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much does it cost?
&lt;/h2&gt;

&lt;p&gt;It's priced at $5 per million input tokens and $25 per million output tokens, just like Opus 4.8. The price didn't move, but capability did. Fable 5 is $10 and $50 for the same volumes, so this is exactly half.&lt;/p&gt;

&lt;p&gt;There's also fast mode — a separate pricing line, and a research preview that's been running since February (&lt;code&gt;fast-mode-2026-02-01&lt;/code&gt;), on Opus 4.8 as well. It's available only through the Claude API; it's not on Amazon Bedrock, Google Cloud or Microsoft Foundry. It's priced at $10 per million input tokens and $50 per million output tokens, so 2x the price for up to 2.5x the output tokens per second (for the record this is throughput, not time to first token, so don't expect end-to-end latency to halve).&lt;/p&gt;

&lt;p&gt;The minimum length a prompt needs to be to be cacheable is now 512 tokens instead of 1024, so prompts that were too short to get cached before are being cached automatically now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you use it or Fable 5?
&lt;/h2&gt;

&lt;p&gt;Opus 5 is half the price of Fable 5, and they're very close in terms of the numbers, so for most use cases it's usually about the price. According to their benchmarks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;on CursorBench 3.2 at max effort it comes within half a percent of Fable 5 for half the price&lt;/li&gt;
&lt;li&gt;on OSWorld 2.0, which is computer-use work, it does even better than Fable 5 for a third of its cost&lt;/li&gt;
&lt;li&gt;it's state of the art on Frontier-Bench and GDPval-AA&lt;/li&gt;
&lt;li&gt;it more than doubles Opus 4.8 on Frontier-Bench v0.1&lt;/li&gt;
&lt;li&gt;it scores 3 times higher than the next best model on ARC-AGI 3&lt;/li&gt;
&lt;li&gt;it reaches around 1.5x the next best pass rate on Zapier's AutomationBench at the same cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are Anthropic's figures, so take them with a grain of salt; they're grading &lt;em&gt;themselves&lt;/em&gt;. But generally speaking — mid-tier is no longer a compromise pick, and if you were to use Fable 5 by default, it's worth reconsidering in light of these numbers.&lt;/p&gt;

&lt;p&gt;One place it doesn't win is exploit development. While on OSS-Fuzz it finds vulnerabilities about as well as Mythos 5, writing the exploits is scored separately — and there it's far behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which effort level should you use?
&lt;/h2&gt;

&lt;p&gt;The effort level starts at high by default; it's the default level both on the Claude API and for Claude Code, and this is what Anthropic document as a starting point. There are five levels in total — low, medium, high, xhigh and max — and in terms of this new Opus, it's more important than ever to choose the right one, as this model translates more effort into better output than any previous Opus, so it makes more of a difference which level you pick.&lt;/p&gt;

&lt;p&gt;We'd recommend testing yourself and adjusting up or down from high based on these results — lower when the quality is good enough to save time and money, higher for the hardest tasks. It's also worth noting that especially in this model, the two lowest levels are actually really capable, so the quality there is really good for a fraction of the cost, and the code review is very decent even at the lower levels.&lt;/p&gt;

&lt;p&gt;On the other hand, if you're planning to use the top two levels, make sure to set &lt;code&gt;max_tokens&lt;/code&gt; high enough, 64K is a good place to start; you need it to have space for both thinking and acting across tools and subagents. Stream the responses rather than awaiting one of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does it show a smaller context window than a million?
&lt;/h2&gt;

&lt;p&gt;The context window is 1M tokens long, and that's both its size and the max. It's not 200k long, this figure is about the client app; update it if possible. It's a single size, so you can't have reduced variants.&lt;/p&gt;

&lt;h2&gt;
  
  
  What breaks when you switch?
&lt;/h2&gt;

&lt;p&gt;If you change the model to Opus 5, apart from renaming it, you need to update the surrounding configuration. The biggest potential pitfall is a prompt scaffolding that you might have left behind from older models — the model self-validates out of the box, so any leftover lines like "add a final verification step" or "use a subagent to verify" are prompting it to do additional verification now. Make sure to remove them.&lt;/p&gt;

&lt;p&gt;Two things simply aren't available on this Opus: web fetch and Priority Tier. If you're relying on either, that's a blocker rather than something you configure around.&lt;/p&gt;

&lt;p&gt;If you're an agent-infra maintainer, there are also 2 new features for you (both behind beta headers at the moment):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you can now add/remove tools between conversation turns without losing the cached prompt, so you no longer need to resend a prompt with a fixed list of tools every time (beta header &lt;code&gt;mid-conversation-tool-changes-2026-07-01&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;they have introduced a new fallback mode, which uses their recommended fallback models per refusal category instead of a list you maintain yourself (&lt;code&gt;server-side-fallback-2026-07-01&lt;/code&gt;) — Claude API only, and explicitly not supported on Microsoft Foundry&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What does it do differently with no code change at all?
&lt;/h2&gt;

&lt;p&gt;If you change the model to this new Opus, you may notice a few differences in terms of behaviour even if you haven't changed anything in your prompts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;longer answers and written deliverables&lt;/li&gt;
&lt;li&gt;talks more during agent sessions, narrating its steps&lt;/li&gt;
&lt;li&gt;is more eager to use subagents in multi-agent setups&lt;/li&gt;
&lt;li&gt;does self-verification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are bugs, they're all steerable by prompting. If you feel like the new Opus is slower or noisier than the previous one after the name change, make sure to check if it's not one of these differences before concluding that the model must've regressed; the "thoughtful and proactive" part of their description is the same thing from their side of the fence.&lt;/p&gt;

&lt;p&gt;In terms of its actual capabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sustains long chains of reasoning&lt;/li&gt;
&lt;li&gt;can implement features and do big refactors in agent coding across long tool loops, writing multi-file code without leaving stubs or placeholders behind&lt;/li&gt;
&lt;li&gt;does really good code review and finds a lot of real bugs with a low number of false positives&lt;/li&gt;
&lt;li&gt;is good at reading charts, documents, and diagrams&lt;/li&gt;
&lt;li&gt;builds multi-sheet spreadsheets and slide decks&lt;/li&gt;
&lt;li&gt;runs subagent teams without them stepping on each other's toes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we would do on day one
&lt;/h2&gt;

&lt;p&gt;In terms of the day 1 plan:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rename the model&lt;/li&gt;
&lt;li&gt;check every &lt;code&gt;max_tokens&lt;/code&gt;, as now it needs to accommodate thinking too; it's the most likely reason your output gets cut off after the switch&lt;/li&gt;
&lt;li&gt;find and remove any verification scaffolding in prompts and agent instructions&lt;/li&gt;
&lt;li&gt;make sure that anywhere you've disabled thinking, the adjacent effort level is high or below, as pairing it with xhigh or max will throw a 400; enforce this before shipping&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And perhaps most importantly: run your own code against it, not their benchmarks, and not ours either — although they could be a good start. We recently published a runnable &lt;a href="https://github.com/dsplce-co/kimi-vs-fable-vs-opus" rel="noopener noreferrer"&gt;benchmark&lt;/a&gt; with Kimi K3, Fable 5 and Opus 4.8 already hooked up. It contains a TypeScript ledger seeded with real bugs, with hidden tests and a prompt to benchmark the models against.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Building with AI is our actual day job.&lt;/strong&gt; We &lt;a href="https://dsplce.co/" rel="noopener noreferrer"&gt;build software with AI&lt;/a&gt; for other companies — this is us reading the release notes on the tools we use every day.&lt;/li&gt;
&lt;li&gt;Curious how our benchmarks went? See &lt;a href="https://dsplce.co/blog/kimi-k3-vs-claude-real-code" rel="noopener noreferrer"&gt;how Kimi K3, Fable 5 and Opus 4.8 handled the same production task&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Building an MCP server in Python (and connecting it to Claude Code)</title>
      <dc:creator>dsplce.co</dc:creator>
      <pubDate>Sat, 25 Jul 2026 15:46:57 +0000</pubDate>
      <link>https://dev.to/dsplce-co/building-an-mcp-server-in-python-and-connecting-it-to-claude-code-4ibk</link>
      <guid>https://dev.to/dsplce-co/building-an-mcp-server-in-python-and-connecting-it-to-claude-code-4ibk</guid>
      <description>&lt;p&gt;An MCP server is a small app that extends an AI model's capabilities by giving it access to custom tools, a particular set of data or workflows. It's based on the Model Context Protocol, which is an open standard for connecting AI apps with these external sources.&lt;/p&gt;

&lt;p&gt;The most straightforward way to create an MCP server is to use the official SDK, implement a single function and mark it as a tool and then expose it through stdio (standard input/output) which you can register in Claude Code; it basically boils down to a single Python file with a single tool and connecting it end-to-end took us around 10 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Generally, the Model Context Protocol defines two sides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The server — it's the app you write that you use to publish tools/data&lt;/li&gt;
&lt;li&gt;The client — for example Claude Code; it finds and calls available tools based on your permission&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As for the main purpose of the Model Context Protocol — before it was introduced, every AI app needed its own custom integration with every tool; the Model Context Protocol replaces this with a single standard connector, so to say it's like USB-C for the AI world — you have a single standardised port instead of having to use a separate cable with every device. In terms of the protocol, a tool is just a function that the model can decide to call.&lt;/p&gt;

&lt;p&gt;So if you want to build an MCP server, you do it when you want your model to have access to some resources you have (like your internal API or database for example) which aren't available through any of the already-published servers.&lt;/p&gt;

&lt;p&gt;Let's have a look at a minimal example of what such server might look like — a single Python file with a single tool that returns the number of words, characters and lines in the input text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaffold the project
&lt;/h2&gt;

&lt;p&gt;To set up the project we used uv (a CLI for managing Python projects) and installed the official SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv init word-count-mcp
&lt;span class="nb"&gt;cd &lt;/span&gt;word-count-mcp
uv add &lt;span class="s2"&gt;"mcp[cli]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;uv init word-count-mcp&lt;/code&gt; — initialises a new project called "word-count-mcp" with an uv project file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;uv add "mcp[cli]"&lt;/code&gt; — adds the mcp package to the project as a dependency with its CLI extras; it also creates a virtual environment and a lockfile&lt;/li&gt;
&lt;li&gt;Note that when you run &lt;code&gt;uv init&lt;/code&gt; for a new project, it creates a sample main.py file which you can remove if you don't need it&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implement a tool
&lt;/h2&gt;

&lt;p&gt;To implement the tool, create server.py:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server.fastmcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastMCP&lt;/span&gt;

&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;word-count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;word_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Count the words, characters, and lines in a block of text.

    Args:
        text: The text to analyze.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;words&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;characters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;characters_no_spaces&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lines&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stdio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These three lines do the entire job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FastMCP("word-count")&lt;/code&gt; — creates an instance of the FastMCP class and names it "word-count"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@mcp.tool()&lt;/code&gt; — annotates a function as a tool; the SDK reads its type hints and the docstring to build the tool's schema so the model knows what it does and what arguments it takes; if you don't provide any type hints, the schema will be ambiguous&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mcp.run(transport="stdio")&lt;/code&gt; — runs the server through stdio, which means that the server waits for requests via stdin and sends responses via stdout&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run it and connect to Claude Code
&lt;/h2&gt;

&lt;p&gt;Now run it to make sure everything works as expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run it, it blocks until it receives some input, which is expected as it's a stdio server and hence it does nothing until a client connects; stop it with Ctrl+C.&lt;/p&gt;

&lt;p&gt;And then, from the project folder, register it in Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add word-count &lt;span class="nt"&gt;--&lt;/span&gt; uv run &lt;span class="nt"&gt;--directory&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The part after &lt;code&gt;--&lt;/code&gt; is the command to run the server. &lt;code&gt;claude mcp add&lt;/code&gt; registers it as a stdio server by default; the &lt;code&gt;--directory "$(pwd)"&lt;/code&gt; bit is a uv flag that points it at the current directory, so no matter from which folder you start Claude Code, it'll work.&lt;/p&gt;

&lt;p&gt;And here's the confirmation that we've added a new stdio server called "word-count" to our local config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Added stdio MCP server word-count with command: uv run --directory /…/word-count-mcp server.py to local config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run &lt;code&gt;claude mcp list&lt;/code&gt; afterwards, you can see that it's Connected, which means that Claude Code started the server, connected through the Model Context Protocol and received a valid list of tools from it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;word-count: uv run --directory /…/word-count-mcp server.py - ✔ Connected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verify the tool call
&lt;/h2&gt;

&lt;p&gt;So let's try using this tool now. Run Claude Code on your machine and ask it to use the word-count tool:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use the word_count tool on this text: "MCP turns Claude into a client for your own tools"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude Code sees the word_count tool (full name mcp_&lt;em&gt;word-count&lt;/em&gt;_word_count), asks for your permission and calls the server which returns its output as is, here are the counts for the sample text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"words"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"characters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;49&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"characters_no_spaces"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So you can see that it's a round-trip from Claude Code through the server to the Python function and back to Claude Code.&lt;/p&gt;

&lt;p&gt;If you want, you can also test it with the MCP Inspector — the SDK's CLI launches it via &lt;code&gt;mcp dev&lt;/code&gt; and it runs at localhost:6274, letting you explore tools and experiment with them by providing arguments and seeing their raw output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run mcp dev server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's very useful for testing servers before integrating them with AI apps like Claude Code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common pitfalls
&lt;/h2&gt;

&lt;p&gt;But there are some common pitfalls worth looking out for when working with the Model Context Protocol:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Never write to stdout in a stdio server — stdout is a channel for strictly-formatted messages to the client, if you write to it in your server, you break the stream and terminate the server; use stderr instead for logs; probably the most common beginner's mistake&lt;/li&gt;
&lt;li&gt;Choose the right transport — use stdio for local servers, HTTP for remote servers that are available via network; generally, tools are local so prefer stdio but don't assume it works by default&lt;/li&gt;
&lt;li&gt;Remember to type your arguments — the tool's schema is based on its type hints and docstring so if you omit the type hints, the schema becomes ambiguous which leads to suboptimal behaviour of the model; always provide type hints and add a one-line docstring for your tools as this is what the model sees&lt;/li&gt;
&lt;li&gt;Know the scope — &lt;code&gt;claude mcp add&lt;/code&gt; takes &lt;code&gt;-s&lt;/code&gt; to decide where the registration is saved: local (the default, which is why you saw “local config” above) = only for you in the current project, user = you across all projects, project = written to .mcp.json so you can commit it and share it with your team; use local to experiment, project for sharing&lt;/li&gt;
&lt;li&gt;Restart after tool changes — when you connect a client (like Claude Code) with an MCP server, it reads the list of tools once and then doesn't ask again until you restart the connection&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Is it worth building your own?
&lt;/h2&gt;

&lt;p&gt;So… When should you create your own MCP server? Anytime you need the model to access some resources that only you have (like your internal API or a database). If it's a system that's popular enough (like GitHub), there's usually a standard server for it but if you want to give your model access to some custom thing, you need to build this bridge.&lt;/p&gt;

&lt;p&gt;The barrier of entry is really low — it's just about defining a function and annotating it with a single decorator; if you can write Python, you can write an MCP tool. But the more advanced challenges appear once your tool actually starts using some systems — authentication, error handling, rate limits, reliability etc. This word-count tool is pretty benign as it's deterministic and inert but when your tool needs to connect with a database or spend money, the actual engineering work starts.&lt;/p&gt;

&lt;p&gt;The thing is that there's a difference between "works" and "production-grade". If you want to build an MCP server that works, it takes 10 minutes; if you want to have a production-grade MCP server, it might take 10 months. This is the kind of work DSPLCE does — we help AI companies take their AI-built software the last mile. Have a look at the &lt;a href="https://github.com/dsplce-co/word-count-mcp" rel="noopener noreferrer"&gt;example on our GitHub&lt;/a&gt;, clone and connect it in a couple of minutes.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>llm</category>
      <category>mcp</category>
      <category>python</category>
    </item>
    <item>
      <title>Kimi K3 vs Claude Fable 5 and Opus 4.8: a benchmark you can run yourself</title>
      <dc:creator>dsplce.co</dc:creator>
      <pubDate>Tue, 21 Jul 2026 22:37:37 +0000</pubDate>
      <link>https://dev.to/dsplce-co/kimi-k3-vs-claude-fable-5-and-opus-48-a-benchmark-you-can-run-yourself-4b15</link>
      <guid>https://dev.to/dsplce-co/kimi-k3-vs-claude-fable-5-and-opus-48-a-benchmark-you-can-run-yourself-4b15</guid>
      <description>&lt;p&gt;Kimi K3 was released this week, and like every model release it's being judged on leaderboard scores and screenshots. But a score is a bit like a football result, in that it tells you who won, not how the game was &lt;em&gt;played&lt;/em&gt;. You wouldn't sign a player off a scoreline alone — you'd want to watch the tape. With code models, the tape is the code itself: how it's structured, whether you'd actually want to maintain it. That's what leaderboards can't show you.&lt;/p&gt;

&lt;p&gt;So we built a benchmark that compares &lt;strong&gt;Kimi K3&lt;/strong&gt;, &lt;strong&gt;Claude Fable 5&lt;/strong&gt; and &lt;strong&gt;Claude Opus 4.8&lt;/strong&gt; on exactly that — and it's open source, so you can run it yourself at &lt;a href="https://github.com/dsplce-co/kimi-vs-fable-vs-opus" rel="noopener noreferrer"&gt;github.com/dsplce-co/kimi-vs-fable-vs-opus&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This note summarises our findings after running it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The task
&lt;/h2&gt;

&lt;p&gt;Given a double-entry ledger module with an existing test suite, make it production-ready and add two features: &lt;strong&gt;transaction reversal&lt;/strong&gt; and &lt;strong&gt;statement generation&lt;/strong&gt;. There's no hint that there are any bugs to find.&lt;/p&gt;

&lt;p&gt;But the existing tests aren't actually good — they don't test for real money-safety bugs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A transfer that can lose money if its second half doesn't complete (the transfer isn't atomic).&lt;/li&gt;
&lt;li&gt;Amounts drifting by fractions of a cent due to floating-point precision.&lt;/li&gt;
&lt;li&gt;An accessor that exposes internals and lets callers corrupt the ledger.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So passing these tests proves only &lt;em&gt;demo&lt;/em&gt; quality — that the core functionality works, nothing more. To grade the results we used a second, &lt;strong&gt;hidden&lt;/strong&gt; test suite that checks the actual money safety — one the models never see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same harness, on purpose
&lt;/h2&gt;

&lt;p&gt;All three models were run in the same harness:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agentically inside Claude Code (opening and editing files, running tests, reacting to failures).&lt;/li&gt;
&lt;li&gt;In a high-effort setting.&lt;/li&gt;
&lt;li&gt;Three runs each, to see some run-to-run variance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We made sure to run Kimi K3 inside Claude Code too, just like the Claudes, so the harness itself isn't a variable here. The results reflect how well each model does &lt;em&gt;as an agent inside Claude Code&lt;/em&gt; — how it would work with a dev team using it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;We're framing the analysis around &lt;em&gt;how each model wrote the code&lt;/em&gt;, rather than the pass/fail binary.&lt;/p&gt;

&lt;h3&gt;
  
  
  What every model got right
&lt;/h3&gt;

&lt;p&gt;All three, across all nine runs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Properly fixed the atomicity bug — first validate all the transaction's lines (this pass can throw), then commit in a second pass that cannot throw, forbidding partial transactions:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// validate everything first — this part is allowed to throw&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`account not open: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// then commit — from here nothing throws, so it's all-or-nothing&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;makeEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Implemented the &lt;code&gt;reverse&lt;/code&gt; feature as a compensating transaction routed through the existing &lt;code&gt;post&lt;/code&gt; function, inheriting its atomicity and lifecycle checks.&lt;/li&gt;
&lt;li&gt;Fixed the accessor leak that would let outside callers mutate the ledger's history.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So fixing the known bug and adding the features is a solved problem for all of them. They're interchangeable on that basic bar — the differentiation only starts to appear with the harder questions about the code's long-term behaviour.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Fable 5
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Most consistent&lt;/strong&gt; across all three runs; the same load-bearing choices each time.&lt;/li&gt;
&lt;li&gt;Wrote a self-test in each run that verifies encapsulation &lt;em&gt;as an invariant&lt;/em&gt; — it tries to corrupt the ledger through the returned object and asserts that it fails, so if a future dev removes the defensive copy, the test breaks immediately instead of a silent leak shipping to production — which is like the difference between doing the safe thing and making the safe thing hard to undo.&lt;/li&gt;
&lt;li&gt;The only differences between runs were in degree of caution (one run reserved a namespace, guarded against overflow, threw on unknown accounts instead of returning zero). No run shipped a bug.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Its limitation: it stores money as a bare &lt;code&gt;number&lt;/code&gt; type — nothing enforces dollars vs cents at the type level; it's all runtime checks. And its reversal linkage uses a string-prefix convention, which is fragile if the naming convention isn't followed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Opus 4.8
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Wrote the most &lt;strong&gt;self-documenting&lt;/strong&gt; code — comments exactly at the points where invariants hold, entries frozen at write time to guard against mutation, and detailed reports including root-cause writeups and a &lt;em&gt;"deliberately not changed"&lt;/em&gt; section outlining conscious choices.&lt;/li&gt;
&lt;li&gt;Produced the most instructive failure of the exercise, in one run. It tightened the &lt;code&gt;post&lt;/code&gt; function to reject non-integer amounts, but left the dollars-to-cents conversion unrounded — a floating-point issue, like &lt;code&gt;0.07 * 100 = 7.000000000000001&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tightened check inside post: reject non-integer minor units&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amount must be whole minor units&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// but the conversion, in another file, was left unrounded&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toMinor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;major&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;major&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// 0.07 * 100 -&amp;gt; 7.000000000000001&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;0.07&lt;/code&gt; transfer doesn't work now, because the stricter integer check collides with the floating-point imprecision from the unrounded dollars-to-cents conversion. Worse yet, the run's own tests stayed green, because it never created a test that transfers a fractional amount; it was like its safety net had the very hole its own change fell through — a landmine that looks green. The other two Opus runs rounded the conversion and tested it, so this is not a constant weakness of the model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kimi K3
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Produced &lt;strong&gt;structurally consistent&lt;/strong&gt; code across all three runs — atomic &lt;code&gt;post&lt;/code&gt;, idempotent reversal, copy-at-the-boundary pattern.&lt;/li&gt;
&lt;li&gt;Some runs added rejection of a reused transaction id with conflicting content, catching a double-spend class of bug that the reference solution doesn't.&lt;/li&gt;
&lt;li&gt;The boldest of the three: two of its runs changed the money type to &lt;code&gt;bigint&lt;/code&gt;, which is technically the right way to represent currency in JavaScript. But Kimi's &lt;code&gt;bigint&lt;/code&gt; conversion was inconsistent at the edges — the transfer function still accepted floating-point numbers, and the balance reader converted back via floating point (&lt;code&gt;Number(minor) / 100&lt;/code&gt;), reintroducing the very error &lt;code&gt;bigint&lt;/code&gt; was adopted to remove.&lt;/li&gt;
&lt;li&gt;In one run, Kimi froze the array returned by &lt;code&gt;getEntries&lt;/code&gt; to protect the ledger's internal state, but that broke callers who legitimately append to their own local copy of that array; the internal state stays safe, but the public contract quietly changed underneath everyone using it — it is the mirror image of a leak (a leak lets a caller corrupt your state, this protects your state by breaking the caller).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The remaining risk is at the edges
&lt;/h3&gt;

&lt;p&gt;What these findings tell us is that all three models have mostly solved the core problem — they fixed the bug and added in the features. The remaining risk is at the edges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Untyped money values.&lt;/li&gt;
&lt;li&gt;Safety mechanisms located separately from the code they protect.&lt;/li&gt;
&lt;li&gt;Fragile, naming-based reversal linkage.&lt;/li&gt;
&lt;li&gt;Comments that don't match the actual behaviour.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's also worth noting that even for the &lt;em&gt;same model with the same prompt&lt;/em&gt;, the public-contract behaviour differed across runs — whether a duplicate transaction throws or silently no-ops, whether a reversal is allowed after an account is closed, whether querying a statement for a nonexistent account throws or returns an empty list. This variance was present in all three models.&lt;/p&gt;

&lt;p&gt;Which means that &lt;strong&gt;if a particular edge-case behaviour is crucial, it's best to pin it with an explicit test&lt;/strong&gt; rather than assuming it's consistent across model runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden test results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Clean runs&lt;/th&gt;
&lt;th&gt;The one failure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Fable 5&lt;/td&gt;
&lt;td&gt;3 / 3&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Opus 4.8&lt;/td&gt;
&lt;td&gt;2 / 3&lt;/td&gt;
&lt;td&gt;a fractional transfer throws incorrectly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kimi K3&lt;/td&gt;
&lt;td&gt;2 / 3&lt;/td&gt;
&lt;td&gt;a frozen returned array breaks a caller&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both Opus's and Kimi's failures were about judgment and edge cases, not fundamental capability. This aligns with the earlier finding that all three have largely solved the core problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The restraint trap
&lt;/h2&gt;

&lt;p&gt;We also included a &lt;em&gt;restraint trap&lt;/em&gt;: an existing function rounds money to whole units, which is lossy — but it's well-documented, tested behaviour that some other code depends on. The senior move is to leave the rounding as-is, add a warning comment, and provide a new, exact accessor next to it. The junior move is to silently "fix" the rounding, breaking the existing callers that depend on it.&lt;/p&gt;

&lt;p&gt;Fable and Opus made the senior move in every run — they preserved the existing rounding behaviour, added a warning comment, and added an exact alternative accessor. Kimi was the least disciplined here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Silently changed the rounding behaviour.&lt;/li&gt;
&lt;li&gt;Produced a different rounding result in each run.&lt;/li&gt;
&lt;li&gt;Left the comment claiming &lt;em&gt;"whole major units"&lt;/em&gt; despite the change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're sharing this because it's revealing — how a model handles a contract it didn't write, when nobody forces it to, says a lot about what it'll do to the contracts already in your codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  The verdict
&lt;/h2&gt;

&lt;p&gt;The verdict is two-sided. On one hand, &lt;strong&gt;Fable wins on consistency&lt;/strong&gt; — no shipped bugs, and the most reliable. On the other, this is a single, small task, and the other models' failures weren't substantial enough to rule out the results shifting on a different problem.&lt;/p&gt;

&lt;p&gt;So what we'd recommend is to treat this benchmark as a &lt;strong&gt;template&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define which properties of correctness matter for your use case.&lt;/li&gt;
&lt;li&gt;Run many more than three attempts per model.&lt;/li&gt;
&lt;li&gt;Test across diverse types of task, not just a ledger.&lt;/li&gt;
&lt;li&gt;Include long-horizon tasks.&lt;/li&gt;
&lt;li&gt;Measure consistency separately, rather than folding it into pass/fail.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;

&lt;p&gt;You can run the benchmark on your own codebase, with your own hidden tests, using the code (including the exact prompt) published at &lt;a href="https://github.com/dsplce-co/kimi-vs-fable-vs-opus" rel="noopener noreferrer"&gt;github.com/dsplce-co/kimi-vs-fable-vs-opus&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Building with AI is our actual day job.&lt;/strong&gt; We &lt;a href="https://dsplce.co/" rel="noopener noreferrer"&gt;build software with AI&lt;/a&gt; for other companies — this benchmark was just us kicking the tyres on the tools we use every day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New to Kimi K3?&lt;/strong&gt; Here's &lt;a href="https://dsplce.co/blog/kimi-k3-claude-code" rel="noopener noreferrer"&gt;how to run it inside Claude Code&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>github</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to use Kimi K3 with Claude Code (subscription, not API)</title>
      <dc:creator>dsplce.co</dc:creator>
      <pubDate>Fri, 17 Jul 2026 21:47:10 +0000</pubDate>
      <link>https://dev.to/dsplce-co/how-to-use-kimi-k3-with-claude-code-subscription-not-api-kol</link>
      <guid>https://dev.to/dsplce-co/how-to-use-kimi-k3-with-claude-code-subscription-not-api-kol</guid>
      <description>&lt;p&gt;Moonshot AI shipped &lt;strong&gt;Kimi K3&lt;/strong&gt; today — a 2.8-trillion-parameter open-weight model that, on the benchmarks, is trading blows with the frontier: &lt;a href="https://venturebeat.com/technology/chinas-moonshot-ai-releases-kimi-k3-the-largest-open-source-model-ever-rivaling-top-u-s-systems" rel="noopener noreferrer"&gt;Claude Fable 5 and GPT-5.6 Sol&lt;/a&gt;. On one frontend-coding arena it actually &lt;a href="https://www.tomshardware.com/tech-industry/artificial-intelligence/moonshot-releases-2-8-trillion-parameter-kimi-k3" rel="noopener noreferrer"&gt;took the top spot&lt;/a&gt;, a notch above Fable 5.&lt;/p&gt;

&lt;p&gt;Benchmarks are one thing. Whether a model is good &lt;em&gt;for you&lt;/em&gt; is another — and the only honest way to settle that is to point it at your own code and watch it work for an afternoon. Claude Code is where a lot of us already do that work, so the fastest way to give K3 a go is to run it &lt;em&gt;inside Claude Code&lt;/em&gt;: same cockpit you already know, different engine under the hood.&lt;/p&gt;

&lt;p&gt;Here's the whole setup. It takes about two minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this works at all
&lt;/h2&gt;

&lt;p&gt;Claude Code is a client. A very good one — the agent loop, the tool use, the diffs, the terminal UX — but underneath, it just speaks the Anthropic API protocol to some backend. Point it at Anthropic's servers and you get Claude. Point it somewhere else that speaks the same protocol and you get whatever's there.&lt;/p&gt;

&lt;p&gt;Kimi's coding endpoint speaks that protocol. So you keep the cockpit you already like and swap the engine — no new tool to learn, no workflow to rebuild. And because you're bringing your own backend, &lt;strong&gt;you don't need an Anthropic subscription or a Claude API key at all.&lt;/strong&gt; The only account in play is on the Kimi side.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll need
&lt;/h2&gt;

&lt;p&gt;One thing: a paid &lt;strong&gt;&lt;a href="https://www.kimi.com/code/docs/en/" rel="noopener noreferrer"&gt;Kimi Code&lt;/a&gt;&lt;/strong&gt; plan that includes K3 — the subscription, not pay-as-you-go API credits, which of course are more expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — generate a Kimi API key
&lt;/h2&gt;

&lt;p&gt;Log in to the Kimi coding console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://www.kimi.com/code/console
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the &lt;strong&gt;API Keys&lt;/strong&gt; page, create a new key, and copy it — it's shown to you exactly once, so paste it somewhere safe. It'll look like &lt;code&gt;sk-kimi-…&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;(Yes, it's called an "API key" even though you're on a subscription. That's just how you authenticate; your usage still counts against your plan, not a metered bill. More on that further down.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — set the environment variables
&lt;/h2&gt;

&lt;p&gt;Claude Code reads its backend from environment variables. Open your shell's startup file — &lt;code&gt;~/.zshrc&lt;/code&gt;, &lt;code&gt;~/.bashrc&lt;/code&gt;, or whatever your shell uses — and add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api.kimi.com/coding/
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_AUTH_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-kimi-…            &lt;span class="c"&gt;# the key from step 1&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_DEFAULT_HAIKU_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kimi-k3
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_DEFAULT_OPUS_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kimi-k3
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_DEFAULT_SONNET_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kimi-k3
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kimi-k3
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLAUDE_CODE_SUBAGENT_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kimi-k3
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ENABLE_TOOL_SEARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What each one is doing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ANTHROPIC_BASE_URL&lt;/code&gt;&lt;/strong&gt; — where Claude Code sends its requests. This is the actual switch: Kimi's coding endpoint instead of Anthropic's.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ANTHROPIC_AUTH_TOKEN&lt;/code&gt;&lt;/strong&gt; — your Kimi key. If you've ever logged into Claude Code the normal way, this stands in for that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ANTHROPIC_MODEL&lt;/code&gt;&lt;/strong&gt; — the main model for the session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ANTHROPIC_DEFAULT_{HAIKU,SONNET,OPUS}_MODEL&lt;/code&gt;&lt;/strong&gt; — Claude Code reaches for different Claude tiers for different jobs: a small fast one for background chores, the mid one for the main work, the big one for heavy reasoning. Kimi's endpoint only serves &lt;code&gt;kimi-k3&lt;/code&gt;, so you map all three onto it — otherwise Claude Code tries to call a Claude model that isn't there, and that request fails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;CLAUDE_CODE_SUBAGENT_MODEL&lt;/code&gt;&lt;/strong&gt; — the subagents Claude Code spawns for sub-tasks run on &lt;code&gt;kimi-k3&lt;/code&gt; too, instead of defaulting back to a Claude tier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ENABLE_TOOL_SEARCH=false&lt;/code&gt;&lt;/strong&gt; — Tool Search is a newer Claude Code feature that loads tool definitions on demand. It leans on Anthropic-specific behaviour, so it's the first thing to misbehave against a third-party backend. Turn it off for a clean run.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3 — reload and launch
&lt;/h2&gt;

&lt;p&gt;Pick up the new variables — either reload the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc      &lt;span class="c"&gt;# or ~/.bashrc, or your shell's equivalent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…or just open a fresh terminal. Then start Claude Code as usual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. You're driving Kimi K3 through Claude Code.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check which model you're actually running
&lt;/h2&gt;

&lt;p&gt;Two reliable ways, and one that lies to you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The welcome screen.&lt;/strong&gt; When Claude Code starts, it prints the active model bottom-left. On a working setup it reads &lt;code&gt;kimi-k3&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ghvlz6gnajthmpxep8e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ghvlz6gnajthmpxep8e.png" alt="Claude Code's welcome screen showing kimi-k3 as the active model" width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll notice it says &lt;strong&gt;API Usage Billing&lt;/strong&gt; right next to it. Don't let that spook you — it doesn't mean you're being metered. Claude Code labels any token-based auth that way (as opposed to a subscription &lt;em&gt;login&lt;/em&gt;); on Kimi's side your usage still draws from your flat plan. Essentially it's a label about how you &lt;em&gt;authenticated&lt;/em&gt;, not how you &lt;em&gt;pay&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;/status&lt;/code&gt;.&lt;/strong&gt; Inside a session, type &lt;code&gt;/status&lt;/code&gt;. If the Base URL reads &lt;code&gt;https://api.kimi.com/coding/&lt;/code&gt;, the switch took.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asking the agent — don't.&lt;/strong&gt; You'd think you could just ask it "which model are you?" — but this is the one check that misleads you. Claude Code injects a system prompt telling the model it &lt;em&gt;is&lt;/em&gt; Claude, so K3, being an obedient junior, will often cheerfully answer "I'm Claude." It isn't lying to you on purpose; it's doing exactly what its instructions told it to. Which is the whole thing in miniature: a model's output is a product of the conditions you put it in, not some fixed truth you can interrogate out of it. Trust the UI, not the confession.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it worth it?
&lt;/h2&gt;

&lt;p&gt;For the price of finding out — two minutes and a subscription you can cancel — yes, run the experiment. But run the &lt;em&gt;right&lt;/em&gt; one. A leaderboard tells you a model can be good in the aggregate; it can't tell you whether it's good at &lt;em&gt;your&lt;/em&gt; stack, &lt;em&gt;your&lt;/em&gt; patterns, the specific mess in &lt;em&gt;your&lt;/em&gt; repo. That's not something you read off a benchmark — it's something you feel after an afternoon of real work.&lt;/p&gt;

&lt;p&gt;So point it at a real task, not a toy. Watch where it's sharp and where it flails. That's the only comparison that decides anything, and now it costs you almost nothing to run.&lt;/p&gt;

&lt;p&gt;The bigger point sits one level up: the model is the interchangeable part. Being able to swap the engine under your tools — Claude this week, Kimi the next, whatever's ahead of the pack after that — is worth more than any single model being "the best," because "best" now has a shelf life measured in weeks. The skill that lasts isn't picking the winner; it's knowing how to put whichever model you're holding to work.&lt;/p&gt;

&lt;p&gt;That's the same instinct behind everything we build: the model is the fast, tireless junior — the judgement about what actually ships is yours. If you've got an AI-built app that dazzles in a demo and then buckles the moment real users touch it, taking it &lt;a href="https://dsplce.co/demo-was-the-easy-part" rel="noopener noreferrer"&gt;the last mile to production&lt;/a&gt; is exactly what we do.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need a Claude or Anthropic subscription to use Kimi K3 in Claude Code?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. Claude Code is just a client that speaks the Anthropic API protocol; pointing it at Kimi's coding endpoint means you authenticate entirely with a Kimi key. All you need is a paid Kimi Code plan that includes K3 — no Anthropic account, no Claude API key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is a Kimi subscription cheaper than API usage for Claude Code?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For agent-style coding, yes. A single Claude Code session can burn millions of tokens, and metered API pricing scales with every one of them. A flat monthly Kimi Code membership fixes the cost no matter how hard you drive it — the same reason heavy Claude Code users pick a Claude subscription over the raw API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Kimi K3 as good as Claude for coding?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On its launch benchmarks it's frontier-level — Kimi K3 topped one frontend-coding arena ahead of Claude Fable 5, and it ships a 1M-token context window. But a leaderboard isn't your codebase. The only comparison that settles it is running it on your real projects for a few sessions and assessing the outcomes yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does Claude Code show "API Usage Billing" if I'm on a Kimi subscription?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because that label reflects how you authenticated (with a token), not how you're billed. Claude Code shows "API Usage Billing" for any token-based backend and "subscription" only for its own login flow. On Kimi's side, your usage still draws from your flat plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I trust the agent when I ask which model it is?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No — that's the one check that misleads you. Claude Code's system prompt tells the model it is Claude, so Kimi K3 will often answer "I'm Claude." Verify with the welcome screen or &lt;code&gt;/status&lt;/code&gt; (Base URL should read &lt;code&gt;https://api.kimi.com/coding/&lt;/code&gt;) instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I switch back to Claude?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Comment out or unset the environment variables in your shell profile — &lt;code&gt;ANTHROPIC_BASE_URL&lt;/code&gt; and &lt;code&gt;ANTHROPIC_AUTH_TOKEN&lt;/code&gt; are the two that matter — then reload it or open a new terminal, and Claude Code talks to Anthropic again. If you flip back and forth a lot, keep the Kimi block behind a shell alias or a separate profile you can toggle instead of editing the file each time.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>HTML/CSS Animation to Video (MP4): the Headless, Deterministic Way (incl. Claude)</title>
      <dc:creator>dsplce.co</dc:creator>
      <pubDate>Fri, 12 Jun 2026 06:30:03 +0000</pubDate>
      <link>https://dev.to/dsplce-co/htmlcss-animation-to-video-mp4-the-headless-deterministic-way-incl-claude-4a2</link>
      <guid>https://dev.to/dsplce-co/htmlcss-animation-to-video-mp4-the-headless-deterministic-way-incl-claude-4a2</guid>
      <description>&lt;p&gt;To convert an HTML/CSS animation — including one Claude generated — to MP4, render it &lt;strong&gt;headlessly&lt;/strong&gt; instead of screen-recording it: a headless Chrome instance drives the page's clock, captures each frame at an exact timestamp, and &lt;a href="https://ffmpeg.org/documentation.html" rel="noopener noreferrer"&gt;FFmpeg&lt;/a&gt; stitches the frames into video. That makes the output frame-perfect and identical on every run. The one-command path is &lt;a href="https://crates.io/crates/htmlrec" rel="noopener noreferrer"&gt;htmlrec&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;dsplce-co/tap/htmlrec ffmpeg
hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Defaults:&lt;/strong&gt; 1280×720, 30fps, 5s, MP4. &lt;strong&gt;Formats:&lt;/strong&gt; MP4, WebM (VP9, optional alpha), and MOV (ProRes 4444, alpha). Override resolution and timing with &lt;code&gt;--width&lt;/code&gt;, &lt;code&gt;--height&lt;/code&gt;, &lt;code&gt;--fps&lt;/code&gt;, and &lt;code&gt;--duration&lt;/code&gt;, and add &lt;code&gt;--transparent&lt;/code&gt; for an alpha channel.&lt;/p&gt;




&lt;p&gt;So you asked Claude to animate something. Maybe a logo, a loading screen, a data viz. It spat out a neat HTML file with CSS keyframes, everything looks crisp in the browser — and now you need it as an MP4.&lt;/p&gt;

&lt;p&gt;The obvious approach is screen recording. Open QuickTime or OBS, hit record, play the animation, stop, trim. Works, kind of. Except it's not frame-perfect. If your machine lags for half a second, that lag is baked into the video. The animation runs at whatever speed your CPU felt like that afternoon. Completely non-deterministic. And the moment you tweak something — wrong colour, timing off by 200ms — you're setting the whole thing up again, which is just tiring. Not to mention that every time you hit record you start at a slightly different frame, so swapping the asset in your video editor becomes a pain because nothing lines up the same way twice.&lt;/p&gt;

&lt;p&gt;There's a better way.&lt;/p&gt;

&lt;p&gt;You can use &lt;a href="https://crates.io/crates/htmlrec" rel="noopener noreferrer"&gt;htmlrec&lt;/a&gt; — a CLI tool that renders HTML animations to video frame by frame, without touching your screen. It controls the browser clock directly, so every frame is captured at exactly the right moment regardless of your machine's load. Pixel-perfect, every single time.&lt;/p&gt;

&lt;p&gt;Install it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;dsplce-co/tap/htmlrec ffmpeg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How do I convert an HTML/CSS animation to MP4?
&lt;/h2&gt;

&lt;p&gt;The reliable way to convert an HTML animation to video is to render it headlessly, frame by frame, instead of screen-recording it. Point a tool at your HTML file, let it drive the browser clock, and capture each frame at an exact timestamp:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works for any self-contained HTML/CSS animation — a logo reveal, a loading screen, a chart, or anything an LLM like Claude generated for you. The full step-by-step is below.&lt;/p&gt;




&lt;h2&gt;
  
  
  The workflow
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Get your animation from Claude&lt;/strong&gt; &lt;em&gt;(skip if you already have an HTML animation)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ask Claude for whatever you need. Something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Create an HTML/CSS animation of a logo appearing with a fade and slight upward motion, black background, 3 seconds"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You'll get back a self-contained HTML file. Save it — let's call it &lt;code&gt;animation.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Render it&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. By default you get a 1280×720, 30fps, 5-second MP4.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Adjust if needed&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Custom resolution and duration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.mp4 &lt;span class="nt"&gt;--width&lt;/span&gt; 1920 &lt;span class="nt"&gt;--height&lt;/span&gt; 1080 &lt;span class="nt"&gt;--duration&lt;/span&gt; 3 &lt;span class="nt"&gt;--fps&lt;/span&gt; 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Need transparency (for overlaying on other footage)?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.webm &lt;span class="nt"&gt;--transparent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WebM with VP9 preserves the alpha channel. Works with &lt;code&gt;.mov&lt;/code&gt; (ProRes 4444) too if you're in a video editing pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;Here's a minimal animation Claude might generate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;place-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;.logo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;64px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;appear&lt;/span&gt; &lt;span class="m"&gt;1s&lt;/span&gt; &lt;span class="n"&gt;ease&lt;/span&gt; &lt;span class="n"&gt;forwards&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;appear&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"logo"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;dsplce&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Render it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; logo.mp4 &lt;span class="nt"&gt;--duration&lt;/span&gt; 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean MP4, no screen recording, no lag, no manual trimming.&lt;/p&gt;




&lt;p&gt;The whole point of htmlrec is to remove the friction between "Claude gave me an animation" and "I have a video file I can actually use." One command, done.&lt;/p&gt;

&lt;p&gt;If you're generating HTML assets regularly — ad creatives, onboarding animations, motion graphics for social — this becomes part of your pipeline rather than a manual step every time. Turning something an AI generated into a deterministic, repeatable artifact you can actually ship is the same instinct we bring to whole products — it's the idea behind taking an &lt;a href="https://dsplce.co/demo-was-the-easy-part" rel="noopener noreferrer"&gt;AI prototype to production&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Source and docs: &lt;a href="https://github.com/dsplce-co/htmlrec" rel="noopener noreferrer"&gt;github.com/dsplce-co/htmlrec&lt;/a&gt;. More about &lt;a href="https://dsplce.co/our-work/htmlrec" rel="noopener noreferrer"&gt;htmlrec on our site&lt;/a&gt;, alongside the rest of our open-source work.&lt;/p&gt;




&lt;h2&gt;
  
  
  How do I convert a CSS animation to MP4?
&lt;/h2&gt;

&lt;p&gt;CSS keyframe animations render to MP4 exactly like everything else — there's nothing special to configure. As long as the motion is driven by &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations" rel="noopener noreferrer"&gt;CSS animations&lt;/a&gt; (&lt;code&gt;@keyframes&lt;/code&gt;, &lt;code&gt;transition&lt;/code&gt;, &lt;code&gt;animation&lt;/code&gt;), a headless renderer captures it deterministically. Match the duration to your animation and you're done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.mp4 &lt;span class="nt"&gt;--duration&lt;/span&gt; 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default is 30fps; pass &lt;code&gt;--fps 60&lt;/code&gt; for extra-smooth fast motion when your animation needs it. Either way the frames come from the browser's own clock, so a 3-second animation is exactly 3 seconds of video — no drift, no dropped frames.&lt;/p&gt;




&lt;h2&gt;
  
  
  Is the output deterministic (including in CI)?
&lt;/h2&gt;

&lt;p&gt;"Deterministic" is the whole point: the same HTML always produces the same video, regardless of what your machine was doing while it rendered. A screen recorder can't promise that — it captures in real time, so any hiccup is baked into the file. A headless renderer steps the page forward one frame at a time and grabs each frame at a precise timestamp, then hands the sequence to FFmpeg:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That determinism is what makes this practical in a real pipeline. Tweak a colour or a timing value, re-render, and only the parts you changed change — the rest of the frames are identical to last time. Swapping the clip into a video editor lines up perfectly every time because nothing is recorded "live."&lt;/p&gt;

&lt;p&gt;The same property makes it CI-friendly: it runs headless with no display, so the identical &lt;code&gt;hrec render&lt;/code&gt; command produces the identical video on a build server as it does on your laptop — install Chromium and FFmpeg on the runner and wire it into your pipeline.&lt;/p&gt;

&lt;p&gt;That same instinct — do the rigorous version, not the flaky one — is what separates an AI-built &lt;em&gt;demo&lt;/em&gt; from an AI-built &lt;em&gt;product&lt;/em&gt;. A demo comes together astonishingly fast; making it hold up under real users and constant change is the actual work. If you're staring at an AI-built app that won the demo and now fights every change, &lt;a href="https://dsplce.co/demo-was-the-easy-part" rel="noopener noreferrer"&gt;taking it the last mile to production&lt;/a&gt; is exactly what we do.&lt;/p&gt;




&lt;h2&gt;
  
  
  Do you need a framework like HyperFrames?
&lt;/h2&gt;

&lt;p&gt;Short answer: it depends on how much you're building.&lt;/p&gt;

&lt;p&gt;There's a growing category of open-source tools that turn HTML into video. HeyGen recently open-sourced &lt;strong&gt;HyperFrames&lt;/strong&gt;, a renderer that converts HTML to video and is positioned as "built for agents" — meant to be driven programmatically inside LLM workflows. Under the hood it's the same core idea as the approach here: a headless browser renders the page and FFmpeg encodes the frames, deterministically. It runs locally (Node 22+ and FFmpeg) — there's no hosted service or signup.&lt;/p&gt;

&lt;p&gt;So the real question isn't "platform vs. local tool" — both run on your machine. It's &lt;strong&gt;scope&lt;/strong&gt;. HyperFrames is a larger Node framework: it ships adapters for GSAP, Lottie, and Three.js plus a timeline model for composing more complex, programmatic animations. If that's the kind of thing you're building, a framework that size earns its keep.&lt;/p&gt;

&lt;p&gt;But for the common case — you already have a self-contained HTML/CSS animation and you just want a clean MP4 — you don't need a framework at all. The job is two well-understood pieces:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://developer.chrome.com/docs/chromium/new-headless" rel="noopener noreferrer"&gt;Headless Chrome&lt;/a&gt;&lt;/strong&gt; renders the page and steps the animation clock forward frame by frame.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FFmpeg&lt;/strong&gt; stitches those frames into a video.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's exactly what &lt;a href="https://crates.io/crates/htmlrec" rel="noopener noreferrer"&gt;htmlrec&lt;/a&gt; is: a single-purpose Rust CLI wrapping those two. Point it at an HTML file, get a deterministic MP4 — one command, nothing to compose, frame-perfect.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to convert a Claude animation to MP4
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MP4 is the safest choice for most use cases — universally supported, small file size, plays everywhere. No extra flags needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  How do I convert a Claude animation to WebM?
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.webm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WebM is a good fit if you're embedding the animation on the web. Smaller than MP4 at comparable quality, and supported natively in Chrome, Firefox, and Edge (Safari's VP9 support is patchier, so test there).&lt;/p&gt;




&lt;h2&gt;
  
  
  How do I get a transparent (alpha) video from an HTML animation?
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.mov &lt;span class="nt"&gt;--transparent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MOV with ProRes 4444 is the most reliable format for transparency — virtually every video editor (Premiere, After Effects, Final Cut) handles it without issues.&lt;/p&gt;

&lt;p&gt;WebM also supports an alpha channel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hrec render animation.html &lt;span class="nt"&gt;-o&lt;/span&gt; out.webm &lt;span class="nt"&gt;--transparent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But in practice, a lot of editing software either doesn't support WebM at all, or silently drops the alpha channel when importing it. If you're taking the file into a video editor, stick with MOV.&lt;/p&gt;

&lt;p&gt;Still, if you're embedding the animation directly on a web page rather than taking it into an editor, transparent WebM is a strong option — Chrome and Firefox handle it natively and the file size is a fraction of MOV. Safari's support for alpha-channel WebM is unreliable, though, so don't count on it there.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How do I get a transparent (alpha) video from an HTML animation?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;--transparent&lt;/code&gt; and pick an alpha-capable container: &lt;code&gt;hrec render animation.html -o out.mov --transparent&lt;/code&gt; gives MOV (ProRes 4444), the most reliable in video editors, and &lt;code&gt;-o out.webm --transparent&lt;/code&gt; gives VP9 with alpha for the web. Using &lt;code&gt;--transparent&lt;/code&gt; with an &lt;code&gt;.mp4&lt;/code&gt; is rejected, because H.264 has no alpha channel. Many editors silently drop WebM's alpha on import, so prefer MOV for Premiere, After Effects, or Final Cut.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I set a custom resolution and frame rate?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. &lt;code&gt;--width&lt;/code&gt;, &lt;code&gt;--height&lt;/code&gt;, and &lt;code&gt;--fps&lt;/code&gt; override the defaults of 1280×720 at 30fps — e.g. &lt;code&gt;hrec render animation.html -o out.mp4 --width 1920 --height 1080 --fps 60&lt;/code&gt;. Match &lt;code&gt;--duration&lt;/code&gt; to your animation's length so nothing is cut off or padded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What about long animations?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;--duration&lt;/code&gt; to the real length in seconds; the default is 5. Frames are captured by stepping a virtual clock rather than playing back in real time, so a longer animation simply means more frames — not a longer wait tied to playback speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it work headless in CI?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. It drives headless Chromium and shells out to FFmpeg with no display, so the same &lt;code&gt;hrec render&lt;/code&gt; command runs in a CI container once Chromium and FFmpeg are installed on the runner. Because capture is clock-driven rather than real-time, the video is identical on every run regardless of CI load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What about fonts and external assets?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Make the animation self-contained — inline the CSS, embed fonts, and base64 or co-locate images — so the headless browser renders exactly what you saw. A web font that hasn't finished loading when the first frame is captured falls back to a system face, so embed or preload anything the animation depends on.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>claude</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to toggle realtime on a table in Supabase migrations</title>
      <dc:creator>dsplce.co</dc:creator>
      <pubDate>Thu, 11 Jun 2026 21:28:54 +0000</pubDate>
      <link>https://dev.to/dsplce-co/how-to-toggle-realtime-on-a-table-in-supabase-migrations-388m</link>
      <guid>https://dev.to/dsplce-co/how-to-toggle-realtime-on-a-table-in-supabase-migrations-388m</guid>
      <description>&lt;p&gt;You set up a table locally, opened it in Supabase Studio, flipped the Realtime toggle on, and everything worked — your frontend was getting live updates, subscriptions firing perfectly. Then you ran &lt;code&gt;supabase db diff&lt;/code&gt;, generated your migration, pushed it all to production… and Realtime is dead. No events, no subscriptions, nothing.&lt;/p&gt;

&lt;p&gt;You check the table — it's there. Columns, RLS policies, indexes, all present. But the Realtime toggle? Off. As if you never touched it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;supabase db diff&lt;/code&gt; command compares database schemas. Realtime configuration is not a schema change — it's a publication membership. Under the hood, flipping that toggle in Studio just adds your table to a Postgres publication called &lt;code&gt;supabase_realtime&lt;/code&gt;. That's it. And because it's neither a schema change nor a data change, &lt;code&gt;db diff&lt;/code&gt; is completely blind to it. It won't show up in the diff output, and it won't end up in your migration file.&lt;/p&gt;

&lt;p&gt;If you want Realtime to travel with your migrations, you just simply need to write the SQL yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what's the SQL?
&lt;/h2&gt;

&lt;p&gt;It's just one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;PUBLICATION&lt;/span&gt; &lt;span class="n"&gt;supabase_realtime&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That adds your table to the publication. Any client subscribed to it will start receiving changes.&lt;/p&gt;

&lt;p&gt;Multiple tables at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;PUBLICATION&lt;/span&gt; &lt;span class="n"&gt;supabase_realtime&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Removing a table from Realtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;PUBLICATION&lt;/span&gt; &lt;span class="n"&gt;supabase_realtime&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Turning it into a migration
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;supabase migration new enable_realtime_on_messages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste the &lt;code&gt;ALTER PUBLICATION&lt;/code&gt; statement into the generated file and you're done. Next time you deploy, the table will be part of the &lt;code&gt;supabase_realtime&lt;/code&gt; publication in the target environment.&lt;/p&gt;

&lt;p&gt;One thing to watch out for — if someone already enabled Realtime on that table manually in production, the migration will fail with a duplicate table error.&lt;/p&gt;

&lt;h2&gt;
  
  
  The faster way — supabase-plus
&lt;/h2&gt;

&lt;p&gt;Keeping track of which tables have Realtime enabled, remembering the publication name, checking the current state before writing the migration — it adds up. &lt;a href="https://github.com/dsplce-co/supabase-plus" rel="noopener noreferrer"&gt;supabase-plus&lt;/a&gt; handles all of that with a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sbp manage realtime
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdsplce.co%2Fmanage-rt-demo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdsplce.co%2Fmanage-rt-demo.gif" alt="sbp manage realtime demo" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It connects to your local database, lists every table in the schema, and shows you which ones are currently subscribed to &lt;code&gt;supabase_realtime&lt;/code&gt;. From there it's an interactive multi-select — toggle tables on or off, and the tool generates the &lt;code&gt;ALTER PUBLICATION&lt;/code&gt; statements for you, drops them into a timestamped migration file in &lt;code&gt;supabase/migrations/&lt;/code&gt;, and optionally applies the migration to your local database right away.&lt;/p&gt;

&lt;p&gt;You can also scope it to a specific schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sbp manage realtime &lt;span class="nt"&gt;--schema&lt;/span&gt; private
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No looking up syntax, no guessing what's already enabled — the tool shows you the state and lets you change it in one step.&lt;/p&gt;

&lt;p&gt;One toggle in Studio, zero lines in your migration history. Now you know how to fix that.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Related:&lt;/strong&gt; working closer to the database? See &lt;a href="https://dsplce.co/blog/postgres-running-queries" rel="noopener noreferrer"&gt;how to see running queries in Postgres and kill them&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>database</category>
      <category>postgres</category>
      <category>sql</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to see running queries in Postgres and kill them</title>
      <dc:creator>dsplce.co</dc:creator>
      <pubDate>Thu, 11 Jun 2026 21:28:08 +0000</pubDate>
      <link>https://dev.to/dsplce-co/how-to-see-running-queries-in-postgres-and-kill-them-j5i</link>
      <guid>https://dev.to/dsplce-co/how-to-see-running-queries-in-postgres-and-kill-them-j5i</guid>
      <description>&lt;p&gt;Something is slow. Maybe a page takes forever to load, maybe a migration is hanging, maybe your Supabase dashboard just spins. You suspect a query is stuck somewhere in your database, but you can't see what's happening — Postgres doesn't exactly surface this on its own.&lt;/p&gt;

&lt;p&gt;Turns out it does. You just need to ask.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeing what's running
&lt;/h2&gt;

&lt;p&gt;Postgres keeps track of every active connection and what it's doing in a system view called &lt;code&gt;pg_stat_activity&lt;/code&gt;. You can query it like any table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clock_timestamp&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;query_start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_stat_activity&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'idle'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you every non-idle process — its process ID, current state, the SQL it's running, and how long it's been at it. If something has been running for minutes when it should take milliseconds, you've found your problem.&lt;/p&gt;

&lt;p&gt;A few things worth knowing about the columns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pid&lt;/code&gt; — the process ID, which you'll need if you want to kill it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;state&lt;/code&gt; — usually &lt;code&gt;active&lt;/code&gt; (running right now), &lt;code&gt;idle in transaction&lt;/code&gt; (sitting inside an open transaction doing nothing), or &lt;code&gt;idle&lt;/code&gt; (waiting for work)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;query&lt;/code&gt; — the actual SQL text&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;query_start&lt;/code&gt; — when the current query began&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to include the user and database to narrow things down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;usename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clock_timestamp&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;query_start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_stat_activity&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'idle'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The dangerous one — idle in transaction
&lt;/h2&gt;

&lt;p&gt;An &lt;code&gt;active&lt;/code&gt; query that's been running for a while is usually just slow. An &lt;code&gt;idle in transaction&lt;/code&gt; connection is a different kind of problem — it means someone (or some code) opened a transaction and never committed or rolled it back. The connection is doing nothing, but it's still holding locks, which can block other queries from running.&lt;/p&gt;

&lt;p&gt;These are the ones that tend to cause cascading slowdowns. If you see one that's been sitting there for longer than expected, it's almost certainly a bug in application code — a missing &lt;code&gt;COMMIT&lt;/code&gt;, an unhandled exception that skipped the cleanup, or a connection pool that didn't reclaim the session properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Killing a process
&lt;/h2&gt;

&lt;p&gt;Once you've identified the offending &lt;code&gt;pid&lt;/code&gt;, you have two options.&lt;/p&gt;

&lt;p&gt;The gentle approach — ask the query to cancel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pg_cancel_backend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sends a cancel signal to the running query. If the process is &lt;code&gt;active&lt;/code&gt;, the query stops and the connection goes back to idle. It's the equivalent of hitting Ctrl+C — the session stays alive, no harm done.&lt;/p&gt;

&lt;p&gt;The forceful approach — terminate the connection entirely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pg_terminate_backend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This kills the entire backend process. The connection is dropped, any open transaction is rolled back, and the client gets disconnected. Use this when &lt;code&gt;pg_cancel_backend&lt;/code&gt; doesn't work — which tends to happen with &lt;code&gt;idle in transaction&lt;/code&gt; sessions, since there's no active query to cancel.&lt;/p&gt;

&lt;p&gt;Replace &lt;code&gt;12345&lt;/code&gt; with the actual &lt;code&gt;pid&lt;/code&gt; from your &lt;code&gt;pg_stat_activity&lt;/code&gt; query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Killing in bulk
&lt;/h2&gt;

&lt;p&gt;If you've got several stuck connections and want to clear them all at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pg_terminate_backend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_stat_activity&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'idle in transaction'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;query_start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'5 minutes'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That terminates every connection that's been idle in a transaction for more than five minutes. Adjust the interval to taste.&lt;/p&gt;

&lt;h2&gt;
  
  
  On Supabase specifically
&lt;/h2&gt;

&lt;p&gt;If you're on Supabase, you can run all of this through the SQL Editor in the dashboard. The same &lt;code&gt;pg_stat_activity&lt;/code&gt; view is available, and &lt;code&gt;pg_cancel_backend&lt;/code&gt; / &lt;code&gt;pg_terminate_backend&lt;/code&gt; both work. No extra permissions needed — the default &lt;code&gt;postgres&lt;/code&gt; role has access.&lt;/p&gt;

&lt;p&gt;One thing to keep in mind: Supabase runs background processes for Realtime, Auth, and PostgREST. You'll see these in &lt;code&gt;pg_stat_activity&lt;/code&gt; too. Don't kill them — they'll usually show up with usernames like &lt;code&gt;supabase_admin&lt;/code&gt; or &lt;code&gt;authenticator&lt;/code&gt;. Stick to terminating connections from your own application's role.&lt;/p&gt;




&lt;p&gt;Working in Supabase a lot? We built &lt;a href="https://dsplce.co/our-work/supabase-plus" rel="noopener noreferrer"&gt;supabase-plus&lt;/a&gt; to smooth over exactly this kind of CLI friction — and wrote up &lt;a href="https://dsplce.co/blog/supabase-realtime-migration" rel="noopener noreferrer"&gt;how to toggle realtime on a table in Supabase migrations&lt;/a&gt;, another thing the dashboard makes harder than it should be.&lt;/p&gt;

</description>
      <category>database</category>
      <category>performance</category>
      <category>postgres</category>
      <category>sql</category>
    </item>
    <item>
      <title>Kubernetes kills your pod? Here's why</title>
      <dc:creator>dsplce.co</dc:creator>
      <pubDate>Thu, 11 Jun 2026 21:27:31 +0000</pubDate>
      <link>https://dev.to/dsplce-co/kubernetes-kills-your-pod-heres-why-25a7</link>
      <guid>https://dev.to/dsplce-co/kubernetes-kills-your-pod-heres-why-25a7</guid>
      <description>&lt;p&gt;Your pods keep getting killed. Not crashing — killed. One moment they're running fine, the next they're gone and Kubernetes is spinning up replacements. You check the logs and there's nothing useful. The pod just… disappeared.&lt;/p&gt;

&lt;p&gt;Turns out Kubernetes killed it on purpose. And if you don't tell it how much memory your app actually needs, it'll keep doing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Kubernetes evicts pods
&lt;/h2&gt;

&lt;p&gt;Kubernetes runs on nodes — physical or virtual machines that host your containers. Each node has a finite amount of CPU and memory. When a node runs low on resources, Kubernetes has to make a choice: which pods stay, and which ones get evicted to free up space.&lt;/p&gt;

&lt;p&gt;The decision comes down to QoS classes — Quality of Service tiers that Kubernetes assigns to every pod based on how you've configured resource requests and limits.&lt;/p&gt;

&lt;p&gt;There are three classes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BestEffort&lt;/strong&gt; — no resource requests or limits defined. Kubernetes has no idea how much CPU or memory the pod needs. These get killed first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Burstable&lt;/strong&gt; — requests and limits are defined, but they're different (e.g., &lt;code&gt;requests: 256Mi&lt;/code&gt;, &lt;code&gt;limits: 512Mi&lt;/code&gt;). The pod is guaranteed the request amount, but can burst up to the limit. Killed second.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guaranteed&lt;/strong&gt; — requests and limits are set to the same value. Kubernetes reserves exactly that amount of resources for the pod. Killed last.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your pods don't have resource configuration at all, they're running as BestEffort. And when the node hits memory pressure, BestEffort pods are the first to go — no questions asked.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Guaranteed class
&lt;/h2&gt;

&lt;p&gt;Setting your pod to the Guaranteed class is one line in your deployment config. Define &lt;code&gt;requests&lt;/code&gt; and &lt;code&gt;limits&lt;/code&gt; for both CPU and memory, and make them identical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;512Mi"&lt;/span&gt;
    &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
  &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;512Mi"&lt;/span&gt;
    &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Kubernetes now knows this pod needs exactly 512 MiB of RAM and half a CPU core, and it reserves that capacity when scheduling the pod onto a node. If a node doesn't have 512 MiB available, the pod won't be placed there. And if the node runs into memory pressure later, this pod gets evicted last — only after all BestEffort and Burstable pods are gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The side effect — better autoscaling
&lt;/h2&gt;

&lt;p&gt;On managed Kubernetes platforms like &lt;a href="https://aws.amazon.com/eks/" rel="noopener noreferrer"&gt;EKS&lt;/a&gt;, this has a second benefit: the cluster autoscaler pays attention to resource requests when deciding whether to add new nodes.&lt;/p&gt;

&lt;p&gt;If your pods are BestEffort (no resource config), the autoscaler sees them as requiring zero resources. Ten pods running on a single node looks fine to it, even if that node is at 90% memory usage. It won't spin up a new node because, from its perspective, there's no unmet resource demand.&lt;/p&gt;

&lt;p&gt;But if those same pods are Guaranteed with &lt;code&gt;requests: 512Mi&lt;/code&gt;, and the current node doesn't have 512 MiB free, the autoscaler sees a pod that can't be scheduled and adds a new node to accommodate it. Your pods start spreading across multiple nodes instead of piling up on one.&lt;/p&gt;

&lt;p&gt;This is particularly rigid on EKS — other Kubernetes providers are a bit more lenient, but EKS strictly follows the scheduler's resource calculations. If you don't define requests, autoscaling won't trigger, and you'll end up with all your pods crammed onto a single node until it runs out of memory and starts evicting things.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-off
&lt;/h2&gt;

&lt;p&gt;The downside of Guaranteed is that you're committing to a specific memory limit. If your app grows and starts using more than what you've configured, the pod gets OOMKilled (out-of-memory killed) instead of being allowed to burst beyond the limit.&lt;/p&gt;

&lt;p&gt;With Burstable, you could set &lt;code&gt;requests: 256Mi&lt;/code&gt; and &lt;code&gt;limits: 1Gi&lt;/code&gt;, giving the app room to spike without getting killed. But you lose the scheduling guarantees — Kubernetes only reserves the 256 MiB request amount, so the pod might end up on a node that doesn't have the full gigabyte available.&lt;/p&gt;

&lt;p&gt;Guaranteed means you need to monitor memory usage and bump the limit when your app legitimately needs more. It's a bit more maintenance, but in exchange you get predictable scheduling, protection from eviction, and autoscaling that actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to set it
&lt;/h2&gt;

&lt;p&gt;In your Kubernetes deployment manifest, add the &lt;code&gt;resources&lt;/code&gt; block under &lt;code&gt;containers&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-image:latest&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;512Mi"&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
          &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;512Mi"&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deployment.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the QoS class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pod &amp;lt;pod-name&amp;gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.status.qosClass}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it says &lt;code&gt;Guaranteed&lt;/code&gt;, you're set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking the right values
&lt;/h2&gt;

&lt;p&gt;Start by looking at what your pods are actually using. Get current memory consumption:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl top pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take the highest value you see, add 20-30% headroom, and use that as your request and limit. If a pod is sitting at 400 MiB, set it to 512 MiB. If it's consistently hitting 800 MiB, go with 1 GiB.&lt;/p&gt;

&lt;p&gt;For CPU, half a core (&lt;code&gt;500m&lt;/code&gt;) is a reasonable starting point for most apps. Bump it if you see CPU throttling in your metrics.&lt;/p&gt;

&lt;p&gt;And then monitor. If you see OOMKills in the pod events, the limit is too low — increase it. If memory usage grows over time as you ship new features, update the config to match.&lt;/p&gt;

&lt;p&gt;Kubernetes won't kill your pods arbitrarily once they're Guaranteed. But you have to tell it what "guaranteed" actually means.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>infrastructure</category>
      <category>kubernetes</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
