<?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: Athreya aka Maneshwar</title>
    <description>The latest articles on DEV Community by Athreya aka Maneshwar (@lovestaco).</description>
    <link>https://dev.to/lovestaco</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%2F1002302%2F9dbe5057-f6da-4c08-9b5d-37fe9d281476.png</url>
      <title>DEV Community: Athreya aka Maneshwar</title>
      <link>https://dev.to/lovestaco</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lovestaco"/>
    <language>en</language>
    <item>
      <title>A VPN Is a Lie You Tell Your Kernel</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Mon, 27 Jul 2026 12:24:24 +0000</pubDate>
      <link>https://dev.to/lovestaco/a-vpn-is-a-lie-you-tell-your-kernel-41dj</link>
      <guid>https://dev.to/lovestaco/a-vpn-is-a-lie-you-tell-your-kernel-41dj</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A VPN is mostly a polite lie you tell your kernel. Here is how EdgeVPN tells it without a single central server, explained by actually reading the Go&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have used VPNs for years without really knowing what one &lt;em&gt;is&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;I typed &lt;code&gt;wg-quick up&lt;/code&gt;, saw a new interface show up in &lt;code&gt;ip a&lt;/code&gt;, and moved on with my life. &lt;/p&gt;

&lt;p&gt;Classic dev move: it works, ship it, never look inside.&lt;/p&gt;

&lt;p&gt;Then I found &lt;a href="https://github.com/mudler/edgevpn" rel="noopener noreferrer"&gt;EdgeVPN&lt;/a&gt;, a VPN written in Go that has &lt;strong&gt;no central server at all&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;You generate a token, paste it on two machines, and they find each other across the internet and start routing packets. &lt;/p&gt;

&lt;p&gt;No control plane, no &lt;code&gt;10.0.0.1&lt;/code&gt; server you have to keep alive, no cloud bill.&lt;/p&gt;

&lt;p&gt;That sounded suspiciously like magic, so I did the only reasonable thing and read the whole codebase. &lt;/p&gt;

&lt;p&gt;This post is me walking you through what I found, from "what even is a network interface" all the way down to how a single IP packet crosses the planet without anyone in charge.&lt;/p&gt;

&lt;p&gt;Grab a coffee. This one has depth.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, the uncomfortable truth about VPNs
&lt;/h2&gt;

&lt;p&gt;A VPN is not a tunnel. &lt;/p&gt;

&lt;p&gt;A VPN is a &lt;strong&gt;lie you tell your operating system&lt;/strong&gt;, and the OS is very willing to believe you.&lt;/p&gt;

&lt;p&gt;Here is the trick. &lt;/p&gt;

&lt;p&gt;Linux (and macOS, and Windows) lets you create a &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/TUN/TAP" rel="noopener noreferrer"&gt;TUN device&lt;/a&gt;&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;It looks like a normal network interface, it shows up in &lt;code&gt;ifconfig&lt;/code&gt;, it has an IP, the kernel happily routes traffic to it. &lt;/p&gt;

&lt;p&gt;But it is not connected to any hardware. &lt;/p&gt;

&lt;p&gt;It is connected to a file descriptor that your program holds.&lt;/p&gt;

&lt;p&gt;That is the whole thing. Really:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your program &lt;strong&gt;reads&lt;/strong&gt; from that file descriptor, and it gets IP packets the kernel wanted to send out.&lt;/li&gt;
&lt;li&gt;Your program &lt;strong&gt;writes&lt;/strong&gt; to that file descriptor, and the kernel believes those packets just arrived from the network.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything else a VPN does (encryption, peer discovery, routing) is just deciding what happens in the gap between that read and that write. &lt;/p&gt;

&lt;p&gt;If you can move bytes from machine A's file descriptor to machine B's file descriptor, congratulations, you have written a VPN.&lt;/p&gt;

&lt;p&gt;Everything after this is engineering.&lt;/p&gt;

&lt;p&gt;Here is EdgeVPN's read loop, and it is honestly almost anticlimactic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ifce&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ethernet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="n"&gt;ethernet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Frame&lt;/span&gt;
    &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MTU&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ifce&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"could not read from interface"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That is it. That is the interface. &lt;code&gt;ifce&lt;/code&gt; is the TUN device, read gives you a raw packet, and now it is your problem.&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%2Fi1d8j8owm7ws6z20uqf7.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%2Fi1d8j8owm7ws6z20uqf7.png" alt=" " width="360" height="270"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The hard part is not tunneling, it is the phone book
&lt;/h2&gt;

&lt;p&gt;Now the real question. &lt;/p&gt;

&lt;p&gt;Machine A wants to send a packet to &lt;code&gt;10.1.0.13&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Machine A reads that packet off the TUN device. Cool. &lt;strong&gt;Where does it send it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;10.1.0.13&lt;/code&gt; is a made up address. &lt;/p&gt;

&lt;p&gt;It exists only inside your virtual network. &lt;/p&gt;

&lt;p&gt;Somewhere out there is a real machine, behind a real &lt;a href="https://en.wikipedia.org/wiki/Network_address_translation" rel="noopener noreferrer"&gt;NAT&lt;/a&gt;, with a real (and probably changing) public IP, that has claimed that made up address. &lt;/p&gt;

&lt;p&gt;Something has to map one to the other.&lt;/p&gt;

&lt;p&gt;WireGuard solves this by making you do it by hand. &lt;/p&gt;

&lt;p&gt;You write &lt;code&gt;AllowedIPs&lt;/code&gt; and an &lt;code&gt;Endpoint&lt;/code&gt; in a config file for every peer. &lt;/p&gt;

&lt;p&gt;It is simple, fast, and it does not scale to "I have twelve laptops behind twelve different routers and one of them is my friend's Raspberry Pi."&lt;/p&gt;

&lt;p&gt;Traditional VPNs solve it with a &lt;strong&gt;server&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;The server knows everyone, everyone talks to the server, the server is the phone book. Also the server is the single point of failure, the single point of trust, and the single thing you have to pay for.&lt;/p&gt;

&lt;p&gt;EdgeVPN solves it with a &lt;strong&gt;gossiped ledger&lt;/strong&gt;, which is a fancy way of saying: every node keeps shouting what it knows, and everyone eventually agrees. Let me build up to it.&lt;/p&gt;
&lt;h2&gt;
  
  
  The token is the entire network
&lt;/h2&gt;

&lt;p&gt;Everything starts with one command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;edgevpn &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; config.yaml
&lt;span class="c"&gt;# or, as a portable token&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;EDGEVPNTOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;edgevpn &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The token is just that YAML, base64'd. Nothing more. Decode one and you get roughly this:&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;otp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dht&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9000&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;7Kx...43-random-chars...&lt;/span&gt;
    &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;43&lt;/span&gt;
  &lt;span class="na"&gt;crypto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9000&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Qz9...43-random-chars...&lt;/span&gt;
    &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;43&lt;/span&gt;
&lt;span class="na"&gt;room&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bT2...43-random-chars...&lt;/span&gt;
&lt;span class="na"&gt;rendezvous&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mP4...43-random-chars...&lt;/span&gt;
&lt;span class="na"&gt;mdns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xL8...43-random-chars...&lt;/span&gt;
&lt;span class="na"&gt;max_message_size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20971520&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Five random strings and two intervals. &lt;/p&gt;

&lt;p&gt;No IPs, no hostnames, no server address, no certificates. &lt;/p&gt;

&lt;p&gt;This file &lt;strong&gt;is&lt;/strong&gt; your network. &lt;/p&gt;

&lt;p&gt;Which brings us to the most important sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt; Exposing this file or passing-it by is equivalent to give full control to the network.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hold that thought, we are coming back to it with receipts.&lt;/p&gt;

&lt;p&gt;The clever bit is those &lt;code&gt;otp&lt;/code&gt; blocks. &lt;/p&gt;

&lt;p&gt;Those keys are not used directly. &lt;/p&gt;

&lt;p&gt;They are fed into &lt;strong&gt;TOTP&lt;/strong&gt;, the same algorithm behind the 6 digit codes in your authenticator app, except tuned to spit out a long base64 string instead:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TOTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;otp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Hash&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c"&gt;// sha256 here&lt;/span&gt;
        &lt;span class="n"&gt;Digits&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// 43&lt;/span&gt;
        &lt;span class="n"&gt;TimeStep&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;otp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TimeWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c"&gt;// 9000 seconds&lt;/span&gt;
        &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Format&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hash&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nb&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StdEncoding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EncodeToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;nb&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TOTP&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So every 9000 seconds (2.5 hours), every node in the network independently derives &lt;strong&gt;the same new secret&lt;/strong&gt;, without talking to each other. &lt;/p&gt;

&lt;p&gt;Time is the only coordination they need. This gets used for two different things, and both are neat.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: finding each other at a rotating rendezvous
&lt;/h2&gt;

&lt;p&gt;To meet a stranger on the internet you need a place to meet. &lt;/p&gt;

&lt;p&gt;EdgeVPN uses the &lt;strong&gt;&lt;a href="https://github.com/libp2p/go-libp2p-kad-dht" rel="noopener noreferrer"&gt;IPFS Kademlia DHT&lt;/a&gt;&lt;/strong&gt; as that meeting point, announcing itself under a key that is just &lt;code&gt;TOTP(dht.key)&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;So your rendezvous on a &lt;strong&gt;public&lt;/strong&gt; hash table moves every couple of hours. &lt;/p&gt;

&lt;p&gt;A watcher sees peers cluster around a random looking key, then that key goes cold forever.&lt;/p&gt;

&lt;p&gt;One subtle trap, nicely handled: if everyone rotates at exactly time T, a node with a skewed clock rotates early and for a moment nobody finds anybody. &lt;/p&gt;

&lt;p&gt;So the DHT announces on the last two rendezvous points at once:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;rendezvousHistory&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Ring&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Overlapping windows. Unglamorous, and exactly the difference between "works in the demo" and "works at 3am". &lt;/p&gt;

&lt;p&gt;On a LAN, mDNS does the same job with the &lt;code&gt;mdns&lt;/code&gt; string as the service tag.&lt;/p&gt;

&lt;p&gt;Here is the full bootstrap:&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%2Fbmturqdr7c5q4212x7jd.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%2Fbmturqdr7c5q4212x7jd.png" alt=" " width="800" height="895"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice the ordering. Peers connect first, and &lt;strong&gt;then&lt;/strong&gt; they exchange the routing table. &lt;/p&gt;

&lt;p&gt;The routing table travels over the p2p network it describes. Bootstrapping is fun.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: the gossip room, sealed twice
&lt;/h2&gt;

&lt;p&gt;Connected peers join a &lt;a href="https://docs.libp2p.io/concepts/pubsub/overview/" rel="noopener noreferrer"&gt;GossipSub&lt;/a&gt; topic named from the other OTP. &lt;/p&gt;

&lt;p&gt;libp2p already encrypts every connection, and EdgeVPN still wraps &lt;strong&gt;another&lt;/strong&gt; AES layer around each message, keyed the same rotating way:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;sealkey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;internalCrypto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MD5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internalCrypto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TOTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SealKeyLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SealKeyInterval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExchangeKey&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Why double up? The rendezvous is public, so anyone can find the topic and subscribe. &lt;/p&gt;

&lt;p&gt;Transport encryption protects the hop, not the payload from someone who legitimately joined the room. &lt;/p&gt;

&lt;p&gt;The seal means a lurker hears noise, and since the key retires every 2.5 hours, brute forcing captured traffic chases a key nobody uses anymore.&lt;/p&gt;

&lt;p&gt;Layers of secrets, rotating on a timer, all from one shared string. Most elegant idea in the codebase.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3: the ledger, or "eventual consistency by shouting"
&lt;/h2&gt;

&lt;p&gt;Now the phone book. EdgeVPN keeps a tiny blockchain. Before you close the tab: no proof of work, no mining, no coin. &lt;/p&gt;

&lt;p&gt;It is a chain of blocks purely for cheap ordering, and each block carries a &lt;code&gt;map[bucket]map[key]value&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The buckets are hardcoded constants that read like a table of contents for the whole product:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;FilesLedgerKey&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"files"&lt;/span&gt;
    &lt;span class="n"&gt;MachinesLedgerKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"machines"&lt;/span&gt;
    &lt;span class="n"&gt;ServicesLedgerKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"services"&lt;/span&gt;
    &lt;span class="n"&gt;UsersLedgerKey&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"users"&lt;/span&gt;
    &lt;span class="n"&gt;HealthCheckKey&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"healthcheck"&lt;/span&gt;
    &lt;span class="n"&gt;DNSKey&lt;/span&gt;            &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"dns"&lt;/span&gt;
    &lt;span class="n"&gt;EgressService&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"egress"&lt;/span&gt;
    &lt;span class="n"&gt;TrustZoneKey&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"trustzone"&lt;/span&gt;
    &lt;span class="n"&gt;TrustZoneAuthKey&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"trustzoneAuth"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The routing table is just the &lt;code&gt;machines&lt;/code&gt; bucket keyed by virtual IP. &lt;/p&gt;

&lt;p&gt;Every feature here is "write to a bucket, read other people's writes". &lt;/p&gt;

&lt;p&gt;Once that clicked the codebase went from clever to obvious, which is the best thing you can say about an architecture.&lt;/p&gt;

&lt;p&gt;Conflict resolution is beautifully blunt:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blockchain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Last&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;block&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hash&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blockchain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Highest block wins, ties broken by higher hash so every node breaks them identically. Whole block replace, no vector clocks, no Raft.  &lt;/p&gt;

&lt;p&gt;"But that loses writes," you say. Yes! And it does not matter, because every node reannounces its own state on a ticker until it sees itself reflected in the chain. &lt;/p&gt;

&lt;p&gt;Read it, check if the world agrees with you, and if not, say it again. Louder. Forever. &lt;/p&gt;

&lt;p&gt;The system converges not because the merge is smart but because everyone is relentlessly repetitive. &lt;/p&gt;

&lt;p&gt;Wipe the entire chain and the nodes refill it in seconds.&lt;/p&gt;

&lt;p&gt;The tradeoff is real and the docs own it: every block goes to every peer, so this is chatty by design. &lt;/p&gt;

&lt;p&gt;Fine for a routing table, which is why the README warns you off latency sensitive workloads.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 4: an actual packet, end to end
&lt;/h2&gt;

&lt;p&gt;Here is the life of one ping, with the real function names.&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%2F2jg3t8p6w6ue1dztc1an.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%2F2jg3t8p6w6ue1dztc1an.png" alt=" " width="744" height="1986"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The lookup is where every piece meets:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MachinesLedgerKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;notFoundErr&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;machine&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Machine&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Decode the Peer&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PeerID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Virtual IP goes in, libp2p peer ID comes out. &lt;/p&gt;

&lt;p&gt;That one lookup replaces the entire VPN server, and libp2p handles the rest: NAT traversal, hole punching, relays, encryption, multiplexing.&lt;/p&gt;

&lt;p&gt;The receiving end checks the sender against that same ledger before copying a byte, and resets the stream otherwise. &lt;/p&gt;

&lt;p&gt;If you are not in my routing table, I will not read your packets. &lt;/p&gt;

&lt;p&gt;Two jobs, one map.&lt;/p&gt;
&lt;h2&gt;
  
  
  The bit I did not expect to enjoy: leaderless leader election
&lt;/h2&gt;

&lt;p&gt;Every node drops a timestamp into a &lt;code&gt;healthcheck&lt;/code&gt; bucket, and anyone older than &lt;code&gt;maxtime&lt;/code&gt; counts as gone.&lt;/p&gt;

&lt;p&gt;But some jobs need exactly one node to run them (scrubbing stale entries, handing out IPs). &lt;/p&gt;

&lt;p&gt;With no server, who decides? This:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Leader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actives&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;leaderboard&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;uint32&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;leader&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;actives&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;actives&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;leaderboard&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// fnv32a&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;leaderboard&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;leader&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;leaderboard&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;leader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&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="n"&gt;leader&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Hash every live peer ID, highest wins. No votes, no terms, no Raft. &lt;/p&gt;

&lt;p&gt;Every node computes it locally and agrees, because the ledger already synced the input. &lt;/p&gt;

&lt;p&gt;When the leader dies it falls out of the healthcheck bucket, everyone recomputes, and a new leader appears with zero messages exchanged.&lt;/p&gt;

&lt;p&gt;Is it Paxos? No. Does deciding who deletes stale map entries need Paxos? Also no. Right sized engineering is underrated.&lt;/p&gt;

&lt;p&gt;Same trick powers the built in DHCP: nodes without an IP wait, the leader among them takes the lowest free one, next node repeats.&lt;/p&gt;

&lt;p&gt;Distributed DHCP with no DHCP server, built from a hash function and patience.&lt;/p&gt;
&lt;h2&gt;
  
  
  Now, about that token
&lt;/h2&gt;

&lt;p&gt;Remember that warning. In the default config, holding the token lets you write anything into any bucket. Including this:&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="err"&gt;machines/&lt;/span&gt;&lt;span class="mf"&gt;10.1&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&amp;gt;&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;span class="err"&gt;PeerID:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-peer-id-actually"&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;Congratulations, you just hijacked someone's virtual IP and every node will happily route their traffic to you. &lt;/p&gt;

&lt;p&gt;The routing table has no concept of who owns which key.&lt;/p&gt;

&lt;p&gt;That is the sort of thing you only catch by reading source, and to their credit the project keeps layering in defenses. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PeerGuardian&lt;/strong&gt; (&lt;code&gt;pkg/trustzone&lt;/code&gt;) adds ECDSA challenge and response, so the token alone does not get you in. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relay ACL&lt;/strong&gt; (&lt;code&gt;pkg/config/relay_acl.go&lt;/code&gt;) gates who can use you as a circuit relay, with an open bootstrap window because a NAT'd peer often needs a relay &lt;em&gt;before&lt;/em&gt; it can prove membership. Chicken and egg, at the network layer.&lt;/p&gt;

&lt;p&gt;The good one is &lt;strong&gt;ledger ownership&lt;/strong&gt; (&lt;code&gt;pkg/blockchain/sign.go&lt;/code&gt;): sign every entry with the node's libp2p key and enforce per key ownership on merge, in three modes, &lt;code&gt;off&lt;/code&gt;, &lt;code&gt;observe&lt;/code&gt;, &lt;code&gt;enforce&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;That middle mode is the detail I liked most. &lt;/p&gt;

&lt;p&gt;Turn it on in a live network, watch the violation logs for a week, then flip to enforce. &lt;/p&gt;

&lt;p&gt;That is how you ship a breaking security change to something you cannot restart all at once.&lt;/p&gt;

&lt;p&gt;The README also says it plainly: &lt;em&gt;"I'm not a security expert, and this software didn't went through a full security audit"&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Respect for that instead of stamping "military grade encryption" on a landing page.&lt;/p&gt;
&lt;h2&gt;
  
  
  The layer cake
&lt;/h2&gt;

&lt;p&gt;Zooming out, here is how the whole thing stacks. &lt;/p&gt;

&lt;p&gt;Everything above the node is optional and plugs into the same two extension points:&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%2F2rp6mmvvx9v0ra6nqxhz.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%2F2rp6mmvvx9v0ra6nqxhz.png" alt=" " width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The dotted lines matter. Control traffic (who owns what) goes through the gossip ledger, slowly and to everyone. &lt;/p&gt;

&lt;p&gt;Data traffic (your actual packets) goes point to point over a dedicated libp2p stream protocol, directly to the one peer that needs it. &lt;/p&gt;

&lt;p&gt;Mixing those up is how you build something that falls over at four nodes.&lt;/p&gt;

&lt;p&gt;The extension seam is two types, and once you see them you can read any feature in the repo:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// something long running that gets the node and the ledger&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;NetworkService&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;blockchain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ledger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;

&lt;span class="c"&gt;// something that handles a raw stream for a given protocol ID&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;StreamHandler&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;blockchain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ledger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The VPN is a &lt;code&gt;NetworkService&lt;/code&gt;. So is DNS. So is the file transfer. So is the healthcheck. &lt;/p&gt;

&lt;p&gt;Adding a feature means picking a bucket name, announcing into it, and optionally claiming a protocol ID for the data path. &lt;/p&gt;

&lt;p&gt;That is the whole framework.&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%2F89n1otobo4j1azhqsfcv.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%2F89n1otobo4j1azhqsfcv.png" alt=" " width="360" height="312"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What I actually took away
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Coordination is the actual product.&lt;/strong&gt; WireGuard says "you coordinate, I will be fast". Commercial VPNs say "a server coordinates, pay us". EdgeVPN says "we gossip until we agree". The question was never encryption.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repeating yourself beats being clever.&lt;/strong&gt; Every node reannounces its own truth forever, so the merge logic gets to be ten dumb lines and still heals from total data loss. Idempotent shouting as a consistency model. I am stealing this.&lt;/p&gt;

&lt;p&gt;Go read some source code. The scariest part of most systems is the part you have not looked at yet, and it is usually about 300 lines.&lt;/p&gt;

&lt;p&gt;Repo is &lt;a href="https://github.com/mudler/edgevpn" rel="noopener noreferrer"&gt;github.com/mudler/edgevpn&lt;/a&gt;, docs are &lt;a href="https://mudler.github.io/edgevpn" rel="noopener noreferrer"&gt;here&lt;/a&gt;, and &lt;a href="https://docs.libp2p.io/" rel="noopener noreferrer"&gt;libp2p's docs&lt;/a&gt; are where the real dark magic lives.&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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>programming</category>
      <category>linux</category>
      <category>networking</category>
    </item>
    <item>
      <title>How terminal-sharing tools put your shell in a browser</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Sun, 26 Jul 2026 16:43:17 +0000</pubDate>
      <link>https://dev.to/lovestaco/how-terminal-sharing-tools-put-your-shell-in-a-browser-328</link>
      <guid>https://dev.to/lovestaco/how-terminal-sharing-tools-put-your-shell-in-a-browser-328</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You have probably done this dance. &lt;/p&gt;

&lt;p&gt;You are debugging something on a box, your teammate says "can you just show me", and you end up screen sharing a 4K monitor over a video call so they can squint at 11px monospace text that has been compressed into oatmeal.&lt;/p&gt;

&lt;p&gt;There is a better way, and it has existed for years. &lt;/p&gt;

&lt;p&gt;Tools like &lt;a href="https://github.com/tsl0922/ttyd" rel="noopener noreferrer"&gt;ttyd&lt;/a&gt;, &lt;a href="https://github.com/cs01/termpair" rel="noopener noreferrer"&gt;TermPair&lt;/a&gt;, and &lt;a href="https://github.com/ekzhang/sshx" rel="noopener noreferrer"&gt;sshx&lt;/a&gt; put your actual terminal in someone else's browser. Real text. Real selection. &lt;/p&gt;

&lt;p&gt;Real copy paste.&lt;/p&gt;

&lt;p&gt;I got curious about how they pull this off, so I cloned all three and read them. &lt;/p&gt;

&lt;p&gt;Turns out they are solving the same problem in three very different ways, and the differences are genuinely interesting. &lt;/p&gt;

&lt;p&gt;Let's get shell shocked together.&lt;/p&gt;

&lt;h2&gt;
  
  
  what even is a terminal
&lt;/h2&gt;

&lt;p&gt;Before any of the web stuff makes sense, you need one concept: the &lt;strong&gt;pseudoterminal&lt;/strong&gt;, or PTY.&lt;/p&gt;

&lt;p&gt;When you run &lt;code&gt;bash&lt;/code&gt; in your terminal app, bash is not talking to your keyboard. &lt;/p&gt;

&lt;p&gt;It is talking to a file. &lt;/p&gt;

&lt;p&gt;Specifically, a pair of linked file descriptors that the kernel sets up to impersonate an actual physical teletype from 1965.&lt;/p&gt;

&lt;p&gt;One end is the &lt;strong&gt;master&lt;/strong&gt;, one end is the &lt;strong&gt;slave&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Your terminal emulator holds the master. Bash holds the slave, and bash has no idea it is being catfished.&lt;/p&gt;

&lt;p&gt;Anything you write to the master shows up as keyboard input to bash. Anything bash prints comes back out of the master. &lt;/p&gt;

&lt;p&gt;That is the entire trick. &lt;/p&gt;

&lt;p&gt;Terminal sharing tools are just programs that hold the master end and forward those bytes somewhere more interesting than a window on your laptop.&lt;/p&gt;

&lt;p&gt;Here is sshx doing exactly that, in &lt;code&gt;crates/sshx/src/terminal/unix.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;nix&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;pty&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Winsize&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;nix&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;unistd&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;execvp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ForkResult&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;use&lt;/span&gt; &lt;span class="nn"&gt;nix&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;login_tty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TIOCGWINSZ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TIOCSWINSZ&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Terminal&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;child&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="n"&gt;master_read&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;master_write&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Fork, call &lt;code&gt;login_tty&lt;/code&gt; in the child so the slave fd becomes its controlling terminal, &lt;code&gt;execvp&lt;/code&gt; the shell, and keep the master in the parent. &lt;/p&gt;

&lt;p&gt;ttyd does the same thing in C. &lt;/p&gt;

&lt;p&gt;TermPair does it in Rust. &lt;/p&gt;

&lt;p&gt;Three codebases, one ancient POSIX handshake. &lt;/p&gt;

&lt;p&gt;Everyone takes the same fork in the road.&lt;/p&gt;

&lt;p&gt;Oh, and &lt;code&gt;TIOCSWINSZ&lt;/code&gt; is how the terminal size gets set. &lt;/p&gt;

&lt;p&gt;When you resize your browser window, that ioctl is what eventually fires so that &lt;code&gt;vim&lt;/code&gt; redraws correctly instead of smearing itself across the screen. &lt;/p&gt;

&lt;p&gt;Resize handling is not a nice-to-have here, it is load bearing.&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%2Fdi8etifapyhedgmi8pzf.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%2Fdi8etifapyhedgmi8pzf.png" alt=" " width="360" height="258"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The simplest possible version: ttyd
&lt;/h2&gt;

&lt;p&gt;ttyd is the "no, seriously, that's it" implementation. &lt;/p&gt;

&lt;p&gt;One C process, built on libwebsockets and libuv. &lt;/p&gt;

&lt;p&gt;It &lt;strong&gt;is&lt;/strong&gt; the web server and it &lt;strong&gt;is&lt;/strong&gt; the terminal host. &lt;/p&gt;

&lt;p&gt;There is no relay, no separate client, no key exchange.&lt;/p&gt;

&lt;p&gt;The protocol is beautifully dumb. From &lt;code&gt;src/server.h&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// client -&amp;gt; server&lt;/span&gt;
&lt;span class="cp"&gt;#define INPUT           '0'
#define RESIZE_TERMINAL '1'
#define PAUSE           '2'
#define RESUME          '3'
#define JSON_DATA       '{'
&lt;/span&gt;
&lt;span class="c1"&gt;// server -&amp;gt; client&lt;/span&gt;
&lt;span class="cp"&gt;#define OUTPUT            '0'
#define SET_WINDOW_TITLE  '1'
#define SET_PREFERENCES   '2'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Single byte opcode, then payload. That is the whole wire format. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;'0'&lt;/code&gt; plus your keystrokes goes up, &lt;code&gt;'0'&lt;/code&gt; plus terminal output comes down. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;PAUSE&lt;/code&gt; and &lt;code&gt;RESUME&lt;/code&gt; exist because a fast-scrolling &lt;code&gt;cat&lt;/code&gt; of a huge file can outrun the browser, so the client can apply backpressure instead of dying.&lt;/p&gt;

&lt;p&gt;Here is the full round trip:&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%2Fzl5b3y57xplscd3yl8vz.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%2Fzl5b3y57xplscd3yl8vz.png" alt=" " width="800" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two details worth stealing from ttyd:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The frontend ships inside the binary.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;The Preact and xterm.js app in &lt;code&gt;html/&lt;/code&gt; gets bundled, gzipped, and inlined into &lt;code&gt;src/html.h&lt;/code&gt; as a C byte array. &lt;/p&gt;

&lt;p&gt;You get a single executable with no static file directory to misplace. &lt;/p&gt;

&lt;p&gt;The tradeoff is that touching a &lt;code&gt;.tsx&lt;/code&gt; file means running &lt;code&gt;yarn run inline&lt;/code&gt; and then rebuilding the C binary, which surprises people exactly once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It is read only by default.&lt;/strong&gt; You have to pass &lt;code&gt;-W&lt;/code&gt; to let viewers actually type.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ttyd &lt;span class="nt"&gt;-p&lt;/span&gt; 7681 &lt;span class="nt"&gt;-c&lt;/span&gt; user:pass &lt;span class="nt"&gt;-O&lt;/span&gt; &lt;span class="nt"&gt;-W&lt;/span&gt; bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That said, ttyd's threat model is "you trust the server, because the server is your machine." &lt;/p&gt;

&lt;p&gt;The bytes are plaintext over the wire unless you turn on TLS. &lt;/p&gt;

&lt;p&gt;Which brings us to the interesting question.&lt;/p&gt;
&lt;h2&gt;
  
  
  What if you don't trust the server?
&lt;/h2&gt;

&lt;p&gt;The moment you want to share a terminal with someone across the internet, you need a middleman with a public IP. And now that middleman can read everything you type.&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%2Fcmdgep1xg6kys57dq3g2.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%2Fcmdgep1xg6kys57dq3g2.png" alt=" " width="360" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;TermPair and sshx both took that guy's advice. &lt;/p&gt;

&lt;p&gt;They make the server a &lt;strong&gt;blind relay&lt;/strong&gt;: it routes ciphertext between the terminal host and the browsers, and it structurally cannot decrypt any of it.&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%2Fldkushytdmid38vhrz2n.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%2Fldkushytdmid38vhrz2n.png" alt=" " width="800" height="621"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Green means "can read your terminal."&lt;/p&gt;

&lt;p&gt;The whole game is shrinking the green.&lt;/p&gt;
&lt;h2&gt;
  
  
  The key delivery trick
&lt;/h2&gt;

&lt;p&gt;So the client encrypts and the browser decrypts. Both need the key. &lt;/p&gt;

&lt;p&gt;The server must not have it. But the browser gets its entire existence from the server. &lt;/p&gt;

&lt;p&gt;How do you hand a secret to a page that the server itself sent you?&lt;/p&gt;

&lt;p&gt;The answer is a URL fragment.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://sharemyclau.de/s/abc123#key-goes-right-here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Everything after &lt;code&gt;#&lt;/code&gt; is &lt;strong&gt;never sent in the HTTP request&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;It is a client side construct. &lt;/p&gt;

&lt;p&gt;Browsers use it for anchor scrolling and hash routing, and it stays in the address bar and in JavaScript's &lt;code&gt;location.hash&lt;/code&gt; while the server sees only &lt;code&gt;/s/abc123&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;So you share one link, the recipient's browser reads the key out of its own address bar, and the relay in the middle sees an opaque session ID and a stream of noise.&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%2F4c4spju4tjytrb8dz4hd.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%2F4c4spju4tjytrb8dz4hd.png" alt=" " width="360" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the flow end to end:&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%2Fuxsn896cax51ugjrly2r.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%2Fuxsn896cax51ugjrly2r.png" alt=" " width="800" height="821"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The security of this rests entirely on the link. Anyone who gets the full URL gets the session. &lt;/p&gt;

&lt;p&gt;Don't paste it in a public Slack channel and then act surprised.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two flavors of crypto
&lt;/h2&gt;

&lt;p&gt;TermPair and sshx both do AES, but they made different calls, and the differences are instructive.&lt;/p&gt;
&lt;h3&gt;
  
  
  TermPair: AES-128-GCM with counter IVs
&lt;/h3&gt;

&lt;p&gt;From &lt;code&gt;crates/termpair-common/src/encryption.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;iv_from_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;IV_LENGTH&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;IV_LENGTH&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="nf"&gt;.to_le_bytes&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;&lt;span class="nf"&gt;.copy_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;iv&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;GCM gives you authentication for free, so a tampered message fails to decrypt rather than quietly turning into garbage that your terminal then interprets as escape sequences. &lt;/p&gt;

&lt;p&gt;The IV is a plain message counter, which is fine and correct &lt;strong&gt;as long as you never reuse one with the same key&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Nonce reuse in GCM is not a "slightly weaker" situation, it is a "here is your key, thanks for playing" situation.&lt;/p&gt;

&lt;p&gt;Which is why &lt;code&gt;constants.rs&lt;/code&gt; has this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ROTATION_THRESHOLD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MAX_MESSAGES_PER_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ROTATION_THRESHOLD&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After about a million messages, the key rotates. &lt;/p&gt;

&lt;p&gt;There is a whole &lt;code&gt;aes_key_rotation&lt;/code&gt; event in the protocol for it. &lt;/p&gt;

&lt;p&gt;Separate keys for terminal output and browser input, plus a bootstrap key for the handshake, so the counter spaces cannot collide. &lt;/p&gt;

&lt;p&gt;It is careful work, and the kind of thing that is invisible when it is done right.&lt;/p&gt;
&lt;h3&gt;
  
  
  sshx: Argon2 stretched key with AES-CTR
&lt;/h3&gt;

&lt;p&gt;sshx made a different bet. From &lt;code&gt;crates/sshx/src/encrypt.rs&lt;/code&gt;, with a comment I really enjoyed:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SALT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="s"&gt;"This is a non-random salt for sshx.io, since we want to stretch the security of 83-bit keys!"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;hasher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Argon2&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nn"&gt;Algorithm&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Argon2id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nn"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;V0x13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nn"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The key in your URL is only 83 bits of entropy, because sshx wants the shareable link to stay short enough to actually paste in chat. &lt;/p&gt;

&lt;p&gt;83 bits is not a lot by modern standards, so it runs Argon2id over it with real memory cost parameters. Brute forcing now costs 19MB and real CPU time per guess instead of a nanosecond. &lt;/p&gt;

&lt;p&gt;It is a deliberate trade of raw entropy for ergonomics, paid back with a KDF.&lt;/p&gt;

&lt;p&gt;Then it uses CTR mode with a clever addressing scheme:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream_num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;assert_ne!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream_num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"stream number must be nonzero"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// security check&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nf"&gt;.copy_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;stream_num&lt;/span&gt;&lt;span class="nf"&gt;.to_be_bytes&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Aes128Ctr64BE&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.aes_key&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="nf"&gt;.seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="nf"&gt;.apply_keystream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Because CTR mode is seekable, sshx can encrypt "the bytes at offset N of stream M" without touching anything before them. &lt;/p&gt;

&lt;p&gt;That is not a crypto flex, it is an architecture decision: it means a browser that reconnects can say "I have everything up to byte 4096, send me the rest" and the server can serve that from its buffer. &lt;/p&gt;

&lt;p&gt;Encryption and resumability designed together.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;assert_ne!(stream_num, 0)&lt;/code&gt; guard is there because stream 0 is used for the encrypted zeros block that proves you have the right key. &lt;/p&gt;

&lt;p&gt;Reusing it would mean reusing a keystream. &lt;/p&gt;

&lt;p&gt;Nice to see the security check written down instead of assumed.&lt;/p&gt;
&lt;h2&gt;
  
  
  The part that makes sshx feel fast
&lt;/h2&gt;

&lt;p&gt;Here is my favorite detail in any of these codebases.&lt;/p&gt;

&lt;p&gt;There is a fundamental problem with remote terminals: your keystroke has to go to the server, into the PTY, back out, and back to you before you see the character. &lt;/p&gt;

&lt;p&gt;On a 200ms link, typing feels like moving through syrup.&lt;/p&gt;

&lt;p&gt;sshx solves this the way &lt;a href="https://mosh.org/" rel="noopener noreferrer"&gt;Mosh&lt;/a&gt; does, with &lt;strong&gt;predictive echo&lt;/strong&gt;. From &lt;code&gt;src/lib/typeahead.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A terminal "local echo" or typeahead addon for xterm.js.&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// This is forked from VSCode's typeahead implementation at&lt;/span&gt;
&lt;span class="c1"&gt;// https://github.com/microsoft/vscode/blob/1.80.1/...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The client guesses. You press &lt;code&gt;k&lt;/code&gt;, it draws a &lt;code&gt;k&lt;/code&gt; immediately (slightly dimmed) while the real round trip happens in the background. &lt;/p&gt;

&lt;p&gt;When the server's actual output arrives, it reconciles. &lt;/p&gt;

&lt;p&gt;If the guess was right, nothing visibly changes. &lt;/p&gt;

&lt;p&gt;If it was wrong, because you were in &lt;code&gt;vim&lt;/code&gt; or hitting a password prompt, it rolls back.&lt;/p&gt;

&lt;p&gt;The tricky part is knowing when &lt;strong&gt;not&lt;/strong&gt; to predict. &lt;/p&gt;

&lt;p&gt;Predicting into a password prompt would echo your password onto the screen, which is a memorable way to lose a friend. &lt;/p&gt;

&lt;p&gt;So the addon tracks terminal modes and bails out when it is not confident.&lt;/p&gt;

&lt;p&gt;Getting a-head of the output, but politely.&lt;/p&gt;
&lt;h2&gt;
  
  
  Sessions, state, and the boring stuff that matters
&lt;/h2&gt;

&lt;p&gt;The last layer is lifecycle. Sessions are long lived, connections are not. Wifi drops. Laptops sleep.&lt;/p&gt;

&lt;p&gt;sshx handles this with sequence numbers in the protocol (&lt;code&gt;crates/sshx-core/proto/sshx.proto&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;TerminalData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;uint32&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;bytes&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;uint64&lt;/span&gt; &lt;span class="na"&gt;seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Sequence number of the first byte&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;SequenceNumbers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;uint32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="na"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Active shells and their sequence numbers&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Every shell is a stream with a byte offset. On reconnect, the client sends where it left off and the server replays the difference. &lt;/p&gt;

&lt;p&gt;Combined with seekable CTR encryption, resume is nearly free.&lt;/p&gt;

&lt;p&gt;It also has to decide when to give up on a session:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="cd"&gt;/// Timeout for a disconnected session to be evicted and closed.&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;DISCONNECTED_SESSION_EXPIRY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Five minutes without a backend client and the session is closed and dropped from the &lt;code&gt;DashMap&lt;/code&gt;. Otherwise every crashed laptop leaks memory forever.&lt;/p&gt;

&lt;p&gt;And because sshx runs a globally distributed mesh, session state also goes into Redis via &lt;code&gt;crates/sshx-server/src/state/mesh.rs&lt;/code&gt;, so you can connect to the nearest server rather than always crossing an ocean. &lt;/p&gt;

&lt;p&gt;TermPair, meanwhile, caps things with hard limits (&lt;code&gt;MAX_TERMINALS: 200&lt;/code&gt;, &lt;code&gt;MAX_BROWSERS_PER_TERMINAL: 50&lt;/code&gt;, &lt;code&gt;MAX_WS_MSGS_PER_SEC: 500&lt;/code&gt;) which is the sensible answer when you are running one modest box instead of a mesh.&lt;/p&gt;
&lt;h2&gt;
  
  
  So which one should you use
&lt;/h2&gt;

&lt;p&gt;Genuinely depends on what you are doing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ttyd&lt;/strong&gt; if the network is already trusted. Homelab, LAN, behind a VPN, embedded devices, an OpenWrt router. It is one small C binary with no runtime dependencies and it will outlive us all. &lt;code&gt;ttyd -W bash&lt;/code&gt; and you are done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TermPair&lt;/strong&gt; if you want end to end encryption with a simple mental model and the option to self host the relay. The three-key design with rotation is easy to reason about and the code is small enough to read in an afternoon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;sshx&lt;/strong&gt; if you want the polished product experience. Multiple terminals on an infinite canvas, other people's cursors moving in real time, predictive echo, global mesh, automatic reconnect. Worth noting the README says self hosting is explicitly not supported, so this is "use sshx.io" rather than "run your own."&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%2Fw0qrnllfu3xtqgzn815h.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%2Fw0qrnllfu3xtqgzn815h.png" alt=" " width="360" height="312"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Every one of these tools is, at its core, &lt;code&gt;fork()&lt;/code&gt;, a PTY master fd, and a loop that shovels bytes into a WebSocket. &lt;/p&gt;

&lt;p&gt;That part is 1970s technology wearing a 2020s hoodie.&lt;/p&gt;

&lt;p&gt;Everything else, the encryption, the sequence numbers, the key rotation, the predictive echo, the session eviction, exists to answer one question: &lt;strong&gt;how much do you trust the machine in the middle?&lt;/strong&gt; ttyd says "it's mine, so completely."&lt;/p&gt;

&lt;p&gt;TermPair and sshx say "not at all, and here is the math."&lt;/p&gt;

&lt;p&gt;Reading three implementations of the same idea side by side is the fastest architecture lesson I have had in a while.&lt;/p&gt;

&lt;p&gt;If a concept keeps showing up in every version, it is essential. &lt;/p&gt;

&lt;p&gt;If it only shows up in one, it is a product decision. &lt;/p&gt;

&lt;p&gt;That distinction is hard to see from a single codebase and obvious from three.&lt;/p&gt;

&lt;p&gt;Go clone them. &lt;/p&gt;

&lt;p&gt;It is more fun than another framework tutorial, and you will never look at &lt;code&gt;location.hash&lt;/code&gt; the same way again.&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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
      <category>cli</category>
    </item>
    <item>
      <title>I'm a Dev Who Barely Knows the Kernel. Here's How I'm Learning How to Track a Packet with pwru</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Sat, 25 Jul 2026 13:01:01 +0000</pubDate>
      <link>https://dev.to/lovestaco/im-a-dev-who-barely-knows-the-kernel-heres-how-im-learning-how-to-track-a-packet-with-pwru-5937</link>
      <guid>https://dev.to/lovestaco/im-a-dev-who-barely-knows-the-kernel-heres-how-im-learning-how-to-track-a-packet-with-pwru-5937</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Hey, so here's a confession. &lt;/p&gt;

&lt;p&gt;My idea of "networking debugging" used to top out at &lt;code&gt;iptables -L -v -n&lt;/code&gt;, squinting at counters, and occasionally yelling "WHY IS IT DROPPING" at a terminal that does not care about my feelings. &lt;/p&gt;

&lt;p&gt;I know just enough kernel to be dangerous: I know packets go in, sometimes they don't come out, and somewhere in between there's a black box that I was told is "the kernel networking stack."&lt;/p&gt;

&lt;p&gt;Then I found &lt;a href="https://github.com/cilium/pwru" rel="noopener noreferrer"&gt;&lt;code&gt;cilium/pwru&lt;/code&gt;&lt;/a&gt; sitting in a repo I was poking around, and it broke my brain a little. &lt;/p&gt;

&lt;p&gt;So I read the source, dragged my devops brain through it, and now I'm going to explain it to you the way I wish someone had explained it to me. &lt;/p&gt;

&lt;p&gt;No kernel PhD required. &lt;/p&gt;

&lt;p&gt;Some puns required.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: packets don't have a call stack you can just &lt;code&gt;console.log&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;As a web dev, when something's broken, I add a &lt;code&gt;console.log&lt;/code&gt;, or I set a breakpoint, or worst case I &lt;code&gt;print&lt;/code&gt; my way to enlightenment. &lt;/p&gt;

&lt;p&gt;The request comes in, flows through my middleware, my handlers, my ORM, and I can trace every step because it's all just... functions calling functions, in my process, in my language.&lt;/p&gt;

&lt;p&gt;A packet inside the Linux kernel does not work like that. &lt;/p&gt;

&lt;p&gt;It gets handed from function to function across the network stack (&lt;code&gt;ip_rcv&lt;/code&gt;, &lt;code&gt;tcp_v4_rcv&lt;/code&gt;, netfilter hooks, qdiscs, the works), it can get cloned, NAT'd, encapsulated in a tunnel, handed to a virtual ethernet pair, sent through an eBPF program of its own, and dropped at literally any of a few thousand points, often silently. &lt;/p&gt;

&lt;p&gt;There's no stack trace. &lt;/p&gt;

&lt;p&gt;There's no &lt;code&gt;packet.log()&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The tool you reach for as a network person is &lt;code&gt;tcpdump&lt;/code&gt;, which is great for seeing a packet arrive and a packet leave, but useless for seeing what happened to it &lt;em&gt;in between&lt;/em&gt;, inside the kernel, especially when it never arrives at all.&lt;/p&gt;

&lt;p&gt;That's the gap &lt;code&gt;pwru&lt;/code&gt; (packet, where are you?) fills. &lt;/p&gt;

&lt;p&gt;It's basically an eBPF-powered &lt;code&gt;console.log&lt;/code&gt; for the kernel's packet path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attaching a debugger to (checks notes) every function in the kernel
&lt;/h2&gt;

&lt;p&gt;Here's the first thing that made my jaw drop reading this codebase. &lt;/p&gt;

&lt;p&gt;Instead of the pwru authors hand-picking a list of "interesting" kernel functions to trace (which is what I assumed, naively), they let BTF (BPF Type Format, basically the kernel's own type metadata) do the work for them.&lt;/p&gt;

&lt;p&gt;In &lt;a href="//internal/pwru/utils.go"&gt;&lt;code&gt;internal/pwru/utils.go&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;GetFuncs&lt;/code&gt; walks the &lt;em&gt;entire&lt;/em&gt; kernel type database, looks at every single function's signature, and asks one question: "does any of your first five parameters point to a &lt;code&gt;struct sk_buff&lt;/code&gt;?" If yes, that function touches a packet, and it gets added to the hit list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;btf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;ok&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;strct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;btf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;ok&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;strct&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"sk_buff"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;funcs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
            &lt;span class="k"&gt;continue&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's it. That's the whole discovery mechanism. &lt;/p&gt;

&lt;p&gt;On a typical kernel this turns up a few thousand functions, and pwru then kprobes (or &lt;code&gt;kprobe-multi&lt;/code&gt; attaches, in batches, concurrently) all of them, at once. &lt;/p&gt;

&lt;p&gt;Every one of those probes runs the same handler, checks your filter, and if it matches, ships an event to userspace.&lt;/p&gt;

&lt;p&gt;Coming from web dev, this is the equivalent of somebody saying "instead of adding logging to the 12 functions we think matter, let's just instrument literally every function in the entire codebase that touches a &lt;code&gt;Request&lt;/code&gt; object" and then actually shipping it in a way that doesn't melt the server.&lt;/p&gt;

&lt;p&gt;It's completely unhinged in the best way, and it works because eBPF probes are cheap enough (and the filtering happens &lt;em&gt;in-kernel&lt;/em&gt;, before the event ever reaches userspace) that "just trace everything" is a viable strategy.&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%2Frg9fo0qsv7ipz9l33g1b.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%2Frg9fo0qsv7ipz9l33g1b.png" alt="Boardroom Meeting Suggestion: rejected suggestion 1 is " width="360" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's roughly what that discovery-and-attach pipeline looks like:&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%2F3h7aqzw6t1bh58g43sno.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%2F3h7aqzw6t1bh58g43sno.png" alt=" " width="800" height="1320"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Wait, you can reuse tcpdump filters here?
&lt;/h2&gt;

&lt;p&gt;The second thing that made me feel at home: you don't write eBPF filter logic by hand, you write a normal &lt;code&gt;pcap-filter&lt;/code&gt; expression. &lt;/p&gt;

&lt;p&gt;The same syntax from &lt;code&gt;tcpdump&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The stuff every devops person already has muscle memory for, like &lt;code&gt;tcp and port 443&lt;/code&gt; or &lt;code&gt;host 10.0.0.5&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pwru &lt;span class="nt"&gt;--output-tuple&lt;/span&gt; &lt;span class="s1"&gt;'host 1.1.1.1 and tcp'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;How does that actually work under the hood? This is the part where I had to reread the code three times. &lt;/p&gt;

&lt;p&gt;pwru takes your pcap expression, compiles it to &lt;em&gt;classic&lt;/em&gt; BPF (cBPF, the old-school packet filter bytecode, same lineage as &lt;code&gt;tcpdump -d&lt;/code&gt;) using &lt;a href="https://github.com/cloudflare/cbpfc" rel="noopener noreferrer"&gt;&lt;code&gt;cloudflare/cbpfc&lt;/code&gt;&lt;/a&gt; to translate cBPF into modern eBPF instructions, and then &lt;strong&gt;splices those raw instructions directly into the loaded eBPF program&lt;/strong&gt;, at a specific marker function it planted for exactly this purpose:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inst&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instructions&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;inst&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"filter_pcap_ebpf"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;suffix&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;injectIdx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In &lt;code&gt;bpf/kprobe_pwru.c&lt;/code&gt; there's a deliberately empty, &lt;code&gt;__noinline&lt;/code&gt; placeholder function called &lt;code&gt;filter_pcap_ebpf_l3&lt;/code&gt; / &lt;code&gt;_l2&lt;/code&gt; whose only job is to exist as an injection point:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;__noinline&lt;/span&gt; &lt;span class="n"&gt;bool&lt;/span&gt;
&lt;span class="nf"&gt;filter_pcap_ebpf_l3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;_skb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;__skb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;___skb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;data_end&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="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;data_end&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;_skb&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;__skb&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;__skb&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;___skb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;pwru finds that function in the compiled instruction stream and literally performs bytecode surgery, cutting it out and stitching in your compiled filter instead, before the program is even loaded into the kernel. &lt;/p&gt;

&lt;p&gt;It's like if your reverse proxy let you write &lt;code&gt;location&lt;/code&gt; blocks in nginx syntax, but under the hood it was actually recompiling and hot-patching the C binary per request. Deranged. Also very cool.&lt;/p&gt;
&lt;h2&gt;
  
  
  The hard part: knowing it's "the same packet"
&lt;/h2&gt;

&lt;p&gt;Okay here's the thing that actually made me appreciate why this tool needed to exist and isn't just "kprobe everything and print." &lt;/p&gt;

&lt;p&gt;A packet does not keep a fixed identity as it flows through the kernel.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NAT rewrites its IP/port, so your 5-tuple filter (&lt;code&gt;host 1.1.1.1&lt;/code&gt;) can stop matching halfway through the journey, right after the thing you were trying to debug happens.&lt;/li&gt;
&lt;li&gt;It can get cloned (&lt;code&gt;skb_clone&lt;/code&gt;) or copied (&lt;code&gt;skb_copy&lt;/code&gt;) for things like local delivery plus forwarding.&lt;/li&gt;
&lt;li&gt;It can cross a veth pair into another network namespace, get handed to an XDP program, or get bridged, where the original &lt;code&gt;sk_buff&lt;/code&gt; might legitimately get freed and a &lt;em&gt;new&lt;/em&gt; one used to represent conceptually "the same" packet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So pwru maintains a small state machine to keep following a packet even after your original filter would technically stop matching. &lt;/p&gt;

&lt;p&gt;Once a packet matches your filter once, its pointer gets remembered in a &lt;code&gt;skb_addresses&lt;/code&gt; BPF hash map (&lt;code&gt;bpf/kprobe_pwru.c&lt;/code&gt;), and every subsequent kprobe hit checks that map first, before even bothering with your filter again:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;track_skb&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;bpf_map_lookup_elem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;skb_addresses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;skb_addr&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tracked_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_stackid&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;TRACKED_BY_STACKID&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TRACKED_BY_SKB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;cont&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When the skb gets cloned, an &lt;code&gt;fexit&lt;/code&gt; probe on &lt;code&gt;skb_clone&lt;/code&gt;/&lt;code&gt;skb_copy&lt;/code&gt; propagates the "yes, we're tracking this one" flag from the old pointer to the new one. &lt;/p&gt;

&lt;p&gt;When the kernel finally frees it (&lt;code&gt;kfree_skbmem&lt;/code&gt;), pwru cleans up the tracking entry so the map doesn't grow forever. &lt;/p&gt;

&lt;p&gt;For the gnarlier case, like bridging, where the pointer identity is genuinely lost, there's a &lt;code&gt;--filter-track-skb-by-stackid&lt;/code&gt; mode that fingerprints by call stack instead of pointer, so it can pick the "same" packet back up under a new address.&lt;/p&gt;

&lt;p&gt;This is basically the eBPF equivalent of trying to correlate a request across microservices without a trace ID: you're stitching identity back together from context clues because nobody handed you a clean handle to hold onto.&lt;/p&gt;
&lt;h2&gt;
  
  
  Turning "packet vanished" into an actual reason
&lt;/h2&gt;

&lt;p&gt;The best part for someone coming from iptables-land: when a packet dies, pwru doesn't just say "the trace stopped here, good luck." &lt;/p&gt;

&lt;p&gt;It hooks &lt;code&gt;kfree_skb_reason&lt;/code&gt; and decodes the kernel's &lt;code&gt;skb_drop_reason&lt;/code&gt; enum, so you get an actual human string like &lt;code&gt;SKB_DROP_REASON_NETFILTER_DROP&lt;/code&gt; instead of silence. &lt;/p&gt;

&lt;p&gt;That's the difference between "curl hung and I have no idea why" and "curl hung, and here is the literal function and the literal drop reason, with a timestamp."&lt;/p&gt;

&lt;p&gt;The &lt;a href="//README.md"&gt;README's example&lt;/a&gt; is exactly the workflow I'd have wanted a year ago: run a &lt;code&gt;curl&lt;/code&gt;, install an iptables DROP rule, run pwru, and watch it print the exact function where the packet gets killed, instead of you bisecting &lt;code&gt;iptables -L&lt;/code&gt; rules by hand at 2am.&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%2Fwmo77wtje7mm6c0xossw.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%2Fwmo77wtje7mm6c0xossw.png" alt="Sad Pablo Escobar: me, staring at  raw `iptables -L -v -n` endraw  counters going up, having zero idea which rule ate my packet, before I found pwru" width="360" height="240"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why this clicked for a devops brain
&lt;/h2&gt;

&lt;p&gt;If you've ever debugged distributed systems, this whole tool is basically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Structured, filtered logging&lt;/strong&gt; (pcap-filter syntax you already know), applied to&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every relevant call site automatically&lt;/strong&gt; (via BTF introspection instead of a hand-maintained list), with&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identity tracking across mutation and forking&lt;/strong&gt; (NAT, clone, tunnel, bridge) so your trace doesn't just stop the moment the packet changes shape, and&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A resolved root cause&lt;/strong&gt; at the end (the drop reason) instead of a shrug emoji.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's a debugging methodology, not just a tool. &lt;/p&gt;

&lt;p&gt;It's the "attach a debugger to the whole system, but keep it filtered so it doesn't drown you, and correlate identity across boundaries" pattern, and it happens to be implemented with kprobes and BTF instead of trace IDs and Jaeger.&lt;/p&gt;

&lt;p&gt;Here's the shape of what happens end to end, from the kprobe firing to a line on your screen:&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%2F25n72o1aj85eewk8k0bj.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%2F25n72o1aj85eewk8k0bj.png" alt=" " width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;I still don't "know the kernel." &lt;/p&gt;

&lt;p&gt;But I don't need to anymore, at least not for this. pwru turned "the kernel is a black box that eats packets" into "the kernel is a big, well-typed program I can attach cheap breakpoints to, filter with syntax I already know, and follow even when it tries to shapeshift on me." &lt;/p&gt;

&lt;p&gt;If you're a web/devops person who's only ever fought the network stack from the outside with &lt;code&gt;iptables&lt;/code&gt; and &lt;code&gt;tcpdump&lt;/code&gt;, go read &lt;a href="https://github.com/cilium/pwru/tree/main/bpf/kprobe_pwru.c" rel="noopener noreferrer"&gt;&lt;code&gt;bpf/kprobe_pwru.c&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/cilium/pwru/blob/main/internal/pwru/utils.go" rel="noopener noreferrer"&gt;&lt;code&gt;internal/pwru/utils.go&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It reads less like kernel wizardry and more like a really well-designed observability tool that happens to live one layer lower than you're used to.&lt;/p&gt;

&lt;p&gt;Packet, where are you? Increasingly, I actually know how to ask.&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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>debugging</category>
      <category>linux</category>
      <category>networking</category>
      <category>opensource</category>
    </item>
    <item>
      <title>6 Open Source Tools That Give You the Web Back</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Fri, 24 Jul 2026 12:39:50 +0000</pubDate>
      <link>https://dev.to/lovestaco/6-open-source-tools-that-give-you-the-web-back-5hak</link>
      <guid>https://dev.to/lovestaco/6-open-source-tools-that-give-you-the-web-back-5hak</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You've probably noticed this if you've tried to fetch a public webpage in the last two years.&lt;/p&gt;

&lt;p&gt;You write four lines of Python. You get a 403. You add a User-Agent header. You get a 403 with better manners. &lt;/p&gt;

&lt;p&gt;You spin up headless Chrome and get a challenge page that spins forever. &lt;/p&gt;

&lt;p&gt;Eventually you go looking at pricing pages for data APIs, where a cheerful landing page offers to sell you back the exact same public HTML for $100 a month.&lt;/p&gt;

&lt;p&gt;Meanwhile the big labs vacuumed up that same web at a scale none of us will ever match, and now the standard way to read a page programmatically is to pay somebody.&lt;/p&gt;

&lt;p&gt;The web went from "open by default" to "open, but only if you look like a person holding a mouse."&lt;/p&gt;

&lt;p&gt;So let's talk about eight open source projects quietly clawing that access back. I've used most of these in anger. Some are brilliant.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Quick note on star counts: I mention a few, because they are a decent signal of "somebody else has hit the bugs before you." They are not a signal of maintenance. A repo can have 70k stars and a last commit from 2023. Always check the commit graph before you check the star count.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Firecrawl: the one that turned a URL into a prompt
&lt;/h2&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%2Fvgq27h8p09pkbs830k38.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%2Fvgq27h8p09pkbs830k38.png" alt=" " width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; you hand it a URL, it hands you clean markdown. Not HTML with the nav bar and the cookie banner and four newsletter modals. Markdown. Headings, paragraphs, links, done.&lt;/p&gt;

&lt;p&gt;It also does more than single pages. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;crawl&lt;/code&gt; endpoint walks a whole site, &lt;code&gt;map&lt;/code&gt; gives you a URL inventory without fetching everything, and structured extraction pulls typed JSON out of a page if you describe the shape you want.&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;firecrawl&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Firecrawl&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Firecrawl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fc-...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scrape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/docs/getting-started&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;formats&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;markdown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Reach for it when:&lt;/strong&gt; you want the web as text, you want it now, and you would rather ship the feature than build a crawling team.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/firecrawl" rel="noopener noreferrer"&gt;
        firecrawl
      &lt;/a&gt; / &lt;a href="https://github.com/firecrawl/firecrawl" rel="noopener noreferrer"&gt;
        firecrawl
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      The API to search, scrape, and interact with the web at scale. 🔥
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/firecrawl/firecrawl/main/img/firecrawl_logo.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ffirecrawl%2Ffirecrawl%2Fmain%2Fimg%2Ffirecrawl_logo.png" height="200"&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;/div&gt;

&lt;div&gt;
  &lt;a href="https://github.com/firecrawl/firecrawl/blob/main/LICENSE" rel="noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/3c07040d6a3169f201d49e5b382fa18328ea7becb0aee301273176173df29c36/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f66697265637261776c2f66697265637261776c" alt="License"&gt;
  &lt;/a&gt;
  &lt;a href="https://pepy.tech/project/firecrawl-py" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/4d690413f05873008084304acda246c7591dae2535f0fa717a8864f6513b526d/68747470733a2f2f7374617469632e706570792e746563682f62616467652f66697265637261776c2d7079" alt="Downloads"&gt;
  &lt;/a&gt;
  &lt;a href="https://GitHub.com/firecrawl/firecrawl/graphs/contributors" rel="noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/82f769cebb0b4ff9439d44cd1246000c45c30daa8a9104b5ae258b0e084593a5/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f636f6e7472696275746f72732f66697265637261776c2f66697265637261776c2e737667" alt="GitHub Contributors"&gt;
  &lt;/a&gt;
  &lt;a href="https://firecrawl.dev" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/c39f0819823f65e2ca813eb2119bf963dd48f1d4c752a33f546ecce5538f77f6/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f56697369742d66697265637261776c2e6465762d6f72616e6765" alt="Visit firecrawl.dev"&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div&gt;
  &lt;p&gt;
    &lt;a href="https://twitter.com/firecrawl" rel="nofollow noopener noreferrer"&gt;
      &lt;img src="https://camo.githubusercontent.com/8c6c7b3530573136a2550b2858664b1e2f38d3926e8b844a051f4ec182c99fac/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f466f6c6c6f772532306f6e253230582d3030303030303f7374796c653d666f722d7468652d6261646765266c6f676f3d78266c6f676f436f6c6f723d7768697465" alt="Follow on X"&gt;
    &lt;/a&gt;
    &lt;a href="https://www.linkedin.com/company/104100957" rel="nofollow noopener noreferrer"&gt;
      &lt;img src="https://camo.githubusercontent.com/1bfbe75bb6d8d9de3f18926d86ceb987aefd6e272a607eb48c1ec88d20e6db88/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f466f6c6c6f772532306f6e2532304c696e6b6564496e2d3030373742353f7374796c653d666f722d7468652d6261646765266c6f676f3d6c696e6b6564696e266c6f676f436f6c6f723d7768697465" alt="Follow on LinkedIn"&gt;
    &lt;/a&gt;
    &lt;a href="https://discord.gg/firecrawl" rel="nofollow noopener noreferrer"&gt;
      &lt;img src="https://camo.githubusercontent.com/8f64c117b97be1456fe243da19bd34f6aa782a09ad06ba162a33ef665cac9d27/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4a6f696e2532306f7572253230446973636f72642d3538363546323f7374796c653d666f722d7468652d6261646765266c6f676f3d646973636f7264266c6f676f436f6c6f723d7768697465" alt="Join our Discord"&gt;
    &lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;




&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;&lt;strong&gt;🔥 Firecrawl&lt;/strong&gt;&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The API to search, scrape, and interact with the web at scale. 🔥&lt;/strong&gt; The web context API to find sources, extract content, and turn it into clean Markdown or structured data your agents can ship with. Open source and available as a &lt;a href="https://firecrawl.dev/?ref=github" rel="nofollow noopener noreferrer"&gt;hosted service&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pst. Hey, you, join our stargazers :)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/firecrawl/firecrawl" rel="noopener noreferrer"&gt;&lt;br&gt;
  &lt;img src="https://camo.githubusercontent.com/e59605fecb5d1f029d591ccbb6726727d709976b0471276016a8adca35b4e797/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f66697265637261776c2f66697265637261776c2e7376673f7374796c653d736f6369616c266c6162656c3d53746172266d61784167653d32353932303030" alt="GitHub stars"&gt;&lt;br&gt;
&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why Firecrawl?&lt;/h2&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Industry-leading reliability&lt;/strong&gt;: Covers 96% of the web, including JS-heavy pages — no proxy headaches, just clean data (&lt;a href="https://www.firecrawl.dev/blog/the-worlds-best-web-data-api-v25" rel="nofollow noopener noreferrer"&gt;see benchmarks&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blazingly fast&lt;/strong&gt;: P95 latency of 3.4s across millions of pages, built for real-time agents and dynamic apps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM-ready output&lt;/strong&gt;: Clean markdown, structured JSON, screenshots, and more — spend fewer tokens, build better AI apps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We handle the hard stuff&lt;/strong&gt;: Rotating proxies, orchestration, rate limits, JS-blocked content, and more — zero configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent ready&lt;/strong&gt;: Connect Firecrawl to any AI agent or MCP client with a single command&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Media parsing&lt;/strong&gt;…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/firecrawl/firecrawl" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;
  
  
  2. Crawl4AI: the same idea, but yours
&lt;/h2&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%2Fd6ng94m9kapt5kiu7hnm.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%2Fd6ng94m9kapt5kiu7hnm.png" alt=" " width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; an open source, async Python crawler that outputs LLM-ready markdown. &lt;/p&gt;

&lt;p&gt;No API key. No credits. No rate limit other than the one your target site imposes and the one your conscience should.&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;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crawl4ai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncWebCrawler&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;AsyncWebCrawler&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;crawler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;crawler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;asyncio&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="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Reach for it when:&lt;/strong&gt; you have volume, you have infra people, or you have data you cannot legally hand to a third party service.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/unclecode" rel="noopener noreferrer"&gt;
        unclecode
      &lt;/a&gt; / &lt;a href="https://github.com/unclecode/crawl4ai" rel="noopener noreferrer"&gt;
        crawl4ai
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🚀🤖 Crawl4AI: Open-source LLM Friendly Web Crawler &amp;amp; Scraper. Don't be shy, join here: https://discord.gg/jP8KfhDhyN
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🚀🤖 Crawl4AI: Open-source LLM Friendly Web Crawler &amp;amp; Scraper.&lt;/h1&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p&gt;&lt;a href="https://trendshift.io/repositories/11716" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/12cf49ee9cc68bbed4b35565e46721f76db8481c3b762eaea7bc34b400b2118f/68747470733a2f2f7472656e6473686966742e696f2f6170692f62616467652f7265706f7369746f726965732f3131373136" alt="unclecode%2Fcrawl4ai | Trendshift" width="250" height="55" class="js-gh-image-fallback"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/unclecode/crawl4ai/stargazers" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/5645daa00297fb49269ce163f2324a36b972a0090fe6bdde1ac459a7c557ef80/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f756e636c65636f64652f637261776c3461693f7374796c653d736f6369616c" alt="GitHub Stars"&gt;&lt;/a&gt;
&lt;a href="https://github.com/unclecode/crawl4ai/network/members" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e44481900d8547d1992fa7b9e4d0ea4d3c446057a743f615e7566f78da4bb5bc/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f666f726b732f756e636c65636f64652f637261776c3461693f7374796c653d736f6369616c" alt="GitHub Forks"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://badge.fury.io/py/crawl4ai" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/33482ca0100c60abf64930364bc0467d96bea4e66a7294c7c4e079773abdc822/68747470733a2f2f62616467652e667572792e696f2f70792f637261776c3461692e737667" alt="PyPI version"&gt;&lt;/a&gt;
&lt;a href="https://pypi.org/project/crawl4ai/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/bd3b70d6386c3fc3d99c61ab3f5f71c3dbeebe23873aeeb478fe22f6f19e2842/68747470733a2f2f696d672e736869656c64732e696f2f707970692f707976657273696f6e732f637261776c346169" alt="Python Version"&gt;&lt;/a&gt;
&lt;a href="https://pepy.tech/project/crawl4ai" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2b53a22dbcab64a3786226c3963af35be1bccc75ffbd8a451f42c06a62f61ead/68747470733a2f2f7374617469632e706570792e746563682f62616467652f637261776c3461692f6d6f6e7468" alt="Downloads"&gt;&lt;/a&gt;
&lt;a href="https://github.com/sponsors/unclecode" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/04a37c966f571a9631b1e3646c68db365615508db92c5642e27dd4544902d8ff/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73706f6e736f72732f756e636c65636f64653f7374796c653d666c6174266c6f676f3d4769744875622d53706f6e736f7273266c6162656c3d53706f6e736f727326636f6c6f723d70696e6b" alt="GitHub Sponsors"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;🚀 Crawl4AI Cloud API — Closed Beta (Launching Soon)&lt;/h4&gt;
&lt;/div&gt;
&lt;p&gt;Reliable, large-scale web extraction, now built to be &lt;em&gt;&lt;strong&gt;drastically more cost-effective&lt;/strong&gt;&lt;/em&gt; than any of the existing solutions.&lt;/p&gt;
&lt;p&gt;👉 &lt;strong&gt;Apply &lt;a href="https://forms.gle/E9MyPaNXACnAMaqG7" rel="nofollow noopener noreferrer"&gt;here&lt;/a&gt; for early access&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;We’ll be onboarding in phases and working closely with early users
Limited slots.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;
    &lt;a href="https://x.com/crawl4ai" rel="nofollow noopener noreferrer"&gt;
      &lt;img src="https://camo.githubusercontent.com/8c6c7b3530573136a2550b2858664b1e2f38d3926e8b844a051f4ec182c99fac/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f466f6c6c6f772532306f6e253230582d3030303030303f7374796c653d666f722d7468652d6261646765266c6f676f3d78266c6f676f436f6c6f723d7768697465" alt="Follow on X"&gt;
    &lt;/a&gt;
    &lt;a href="https://www.linkedin.com/company/crawl4ai" rel="nofollow noopener noreferrer"&gt;
      &lt;img src="https://camo.githubusercontent.com/1bfbe75bb6d8d9de3f18926d86ceb987aefd6e272a607eb48c1ec88d20e6db88/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f466f6c6c6f772532306f6e2532304c696e6b6564496e2d3030373742353f7374796c653d666f722d7468652d6261646765266c6f676f3d6c696e6b6564696e266c6f676f436f6c6f723d7768697465" alt="Follow on LinkedIn"&gt;
    &lt;/a&gt;
    &lt;a href="https://discord.gg/jP8KfhDhyN" rel="nofollow noopener noreferrer"&gt;
      &lt;img src="https://camo.githubusercontent.com/8f64c117b97be1456fe243da19bd34f6aa782a09ad06ba162a33ef665cac9d27/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4a6f696e2532306f7572253230446973636f72642d3538363546323f7374796c653d666f722d7468652d6261646765266c6f676f3d646973636f7264266c6f676f436f6c6f723d7768697465" alt="Join our Discord"&gt;
    &lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Crawl4AI turns the web into clean, LLM ready Markdown for RAG, agents, and data pipelines. Fast, controllable, battle tested by a 50k+ star community.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/unclecode/crawl4ai#-recent-updates" rel="noopener noreferrer"&gt;✨ Check out latest update v0.9.2&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;✨ &lt;strong&gt;New in v0.9.2&lt;/strong&gt;: Maintenance patch release. Fixes a &lt;code&gt;MemoryAdaptiveDispatcher&lt;/code&gt; task/page leak when a streaming crawl is closed, Docker Playground "Advanced Config" and Monitor WebSocket auth, Playwright headless-shell packaging, and GPU (&lt;code&gt;ENABLE_GPU=true&lt;/code&gt;) Docker builds. &lt;a href="https://github.com/unclecode/crawl4ai/blob/main/docs/blog/release-v0.9.2.md" rel="noopener noreferrer"&gt;Release notes →&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;✨ Recent v0.9.0: Major secure-by-default release of the Docker API server. Auth is on by default, the server binds loopback unless given a token, and the…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/unclecode/crawl4ai" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Also, both of these projects have converged on the same insight, which is fun to watch.&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%2Ftjksb61wkse8gt8oqfcw.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%2Ftjksb61wkse8gt8oqfcw.png" alt=" " width="360" height="202"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Browser-use: an agent with hands
&lt;/h2&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%2Fyx9lvx446johx0nu4mnq.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%2Fyx9lvx446johx0nu4mnq.png" alt=" " width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; wires an LLM to a real browser and lets it drive. Clicking, typing, scrolling, filling forms, logging in, navigating a multi step checkout. &lt;/p&gt;

&lt;p&gt;You describe the goal in English, it figures out the DOM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reach for it when:&lt;/strong&gt; the target is genuinely interactive, the volume is low, and the alternative is a human doing it by hand.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/browser-use" rel="noopener noreferrer"&gt;
        browser-use
      &lt;/a&gt; / &lt;a href="https://github.com/browser-use/browser-use" rel="noopener noreferrer"&gt;
        browser-use
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🌐 Make websites accessible for AI agents. Automate tasks online with ease.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;

  
  
  &lt;img alt="Shows a black Browser Use Logo in light color mode and a white one in dark color mode." src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F2ccdb752-22fb-41c7-8948-857fc1ad7e24" width="full"&gt;

&lt;div&gt;
    
    
    
    &lt;img alt="The AI browser agent." src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F9955dda9-ede3-4971-8ee0-91cbc3850125" width="400"&gt;
    
&lt;/div&gt;
&lt;div&gt;
&lt;a href="https://cloud.browser-use.com?utm_source=github&amp;amp;utm_medium=readme-badge-downloads" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/f67549cea6229347ae19319784e0bbd39a6f987cab1d48cd464017f02e955a04/68747470733a2f2f6d656469612e62726f777365722d7573652e746f6f6c732f6261646765732f7061636b616765" height="48" alt="Browser-Use Package Download Statistics"&gt;&lt;/a&gt;
&lt;/div&gt;

&lt;div&gt;
&lt;a href="https://github.com/browser-use/browser-use#what-can-browser-use-do" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7e9025c343a4acd95c54d84d83b23f875323ee3f58cb4ec2c764f4c07c7835e7/68747470733a2f2f6d656469612e62726f777365722d7573652e746f6f6c732f6261646765732f64656d6f73" alt="Demos"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer" href=""&gt;&lt;img width="16" height="1" alt=""&gt;&lt;/a&gt;
&lt;a href="https://docs.browser-use.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/da24e49e145642fb3a3fe1910e856d59d6da1491afd35998b8624f6732262f49/68747470733a2f2f6d656469612e62726f777365722d7573652e746f6f6c732f6261646765732f646f6373" alt="Docs"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer" href=""&gt;&lt;img width="16" height="1" alt=""&gt;&lt;/a&gt;
&lt;a href="https://browser-use.com/posts" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/f59d6529dd474485d76d72dfa57622e93de03c2f2147d15645f237057d6ac1e2/68747470733a2f2f6d656469612e62726f777365722d7573652e746f6f6c732f6261646765732f626c6f67" alt="Blog"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer" href=""&gt;&lt;img width="16" height="1" alt=""&gt;&lt;/a&gt;
&lt;a href="https://browsermerch.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/6854add877f53678eb94c1903d14b88427b87dfcbc7b46fa04d9023826b4c2c2/68747470733a2f2f6d656469612e62726f777365722d7573652e746f6f6c732f6261646765732f6d65726368" alt="Merch"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer" href=""&gt;&lt;img width="100" height="1" alt=""&gt;&lt;/a&gt;
&lt;a href="https://github.com/browser-use/browser-use" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/6b321c4c3a0fe9ebd185f7e1ed0c34908ed763b0670359685038826220fa0c73/68747470733a2f2f6d656469612e62726f777365722d7573652e746f6f6c732f6261646765732f676974687562" alt="Github Stars"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer" href=""&gt;&lt;img width="4" height="1" alt=""&gt;&lt;/a&gt;
&lt;a href="https://x.com/intent/user?screen_name=browser_use" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/0c0d368a2b84e6c55dfa4b61dfcc6b20fc79911ddac2890188b6f5aa4d9e1ff9/68747470733a2f2f6d656469612e62726f777365722d7573652e746f6f6c732f6261646765732f74776974746572" alt="Twitter"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer" href=""&gt;&lt;img width="4" height="1" alt=""&gt;&lt;/a&gt;
&lt;a href="https://link.browser-use.com/discord" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/4924db0665fc88bd7ed197fa3535511406743b1025c5086a32e252a613ad1176/68747470733a2f2f6d656469612e62726f777365722d7573652e746f6f6c732f6261646765732f646973636f7264" alt="Discord"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer" href=""&gt;&lt;img width="4" height="1" alt=""&gt;&lt;/a&gt;
&lt;a href="https://cloud.browser-use.com?utm_source=github&amp;amp;utm_medium=readme-badge-cloud" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/af449ec43f198f27fd42b59996473270e5dd2eeb482196bfca572ee48a2afbcc/68747470733a2f2f6d656469612e62726f777365722d7573652e746f6f6c732f6261646765732f636c6f7564" height="48" alt="Browser-Use Cloud"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;What can Browser Use do?&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Browser Use lets an AI agent use a web browser the same way you do — it opens pages, clicks buttons, types, and fills in forms. You describe the task, and it completes it. For example, you can have it:&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;📋 Fill Forms&lt;/h3&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;Task: "Fill in this job application with my resume and information."&lt;/h4&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/43824272/501209081-57865ee6-6004-49d5-b2c2-6dff39ec2ba9.gif?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3ODQ4OTcxMDksIm5iZiI6MTc4NDg5NjgwOSwicGF0aCI6Ii80MzgyNDI3Mi81MDEyMDkwODEtNTc4NjVlZTYtNjAwNC00OWQ1LWIyYzItNmRmZjM5ZWMyYmE5LmdpZj9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjA3MjQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNzI0VDEyNDAwOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWNjN2EzOWQxNWJjMjk1OWI5YTQ2YmJhZGMwNzAwZTJiMjU4YjcwZjFkZTJiNjc1OTlmMzIxODVlNmZkNzdlM2MmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnJlc3BvbnNlLWNvbnRlbnQtdHlwZT1pbWFnZSUyRmdpZiJ9.UCb5lqq3XM6hUGDwLZxpUUNa7KJYRrQPkq3BfMaBT3I"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F43824272%2F501209081-57865ee6-6004-49d5-b2c2-6dff39ec2ba9.gif%3Fjwt%3DeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3ODQ4OTcxMDksIm5iZiI6MTc4NDg5NjgwOSwicGF0aCI6Ii80MzgyNDI3Mi81MDEyMDkwODEtNTc4NjVlZTYtNjAwNC00OWQ1LWIyYzItNmRmZjM5ZWMyYmE5LmdpZj9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjA3MjQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNzI0VDEyNDAwOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWNjN2EzOWQxNWJjMjk1OWI5YTQ2YmJhZGMwNzAwZTJiMjU4YjcwZjFkZTJiNjc1OTlmMzIxODVlNmZkNzdlM2MmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnJlc3BvbnNlLWNvbnRlbnQtdHlwZT1pbWFnZSUyRmdpZiJ9.UCb5lqq3XM6hUGDwLZxpUUNa7KJYRrQPkq3BfMaBT3I" alt="Job Application Demo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/browser-use/browser-use/blob/main/examples/use-cases/apply_to_job.py" rel="noopener noreferrer"&gt;Example code ↗&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;🍎 Extract data&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;Task: "Extract structured data about my followers and export it as a CSV."&lt;/h4&gt;

&lt;/div&gt;

  
    
    &lt;span class="m-1"&gt;extract-followers.mp4&lt;/span&gt;
    
  

  

  


&lt;p&gt;&lt;a href="https://docs.browser-use.com/cloud/quickstart" rel="nofollow noopener noreferrer"&gt;Browser Use Cloud Docs ↗&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;💻 QA Automation&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;Task: "QA test my local website and report any bugs, usability issues, and visual inconsistencies."&lt;/h4&gt;

&lt;/div&gt;
&lt;a rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/129362299/620507927-bf590697-df9c-4e79-b646-d6f52bfea976.gif?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3ODQ4OTcxMDksIm5iZiI6MTc4NDg5NjgwOSwicGF0aCI6Ii8xMjkzNjIyOTkvNjIwNTA3OTI3LWJmNTkwNjk3LWRmOWMtNGU3OS1iNjQ2LWQ2ZjUyYmZlYTk3Ni5naWY_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjYwNzI0JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI2MDcyNFQxMjQwMDlaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT02NGMzMzFiYWMxYzUxNDkwOTJjZGU1MjM0NjhiYzZkYWE1MDNiMzk3YTE4OWVmZjU4MjI5ODg5NjQwZWZiNjNiJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZyZXNwb25zZS1jb250ZW50LXR5cGU9aW1hZ2UlMkZnaWYifQ.QIaKt_yUj4Rt0HDz9KWirgtKQctDZ36F2uGay-5WNR8"&gt;&lt;img width="1920" height="1080" alt="qa-demo-small" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F129362299%2F620507927-bf590697-df9c-4e79-b646-d6f52bfea976.gif%3Fjwt%3DeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3ODQ4OTcxMDksIm5iZiI6MTc4NDg5NjgwOSwicGF0aCI6Ii8xMjkzNjIyOTkvNjIwNTA3OTI3LWJmNTkwNjk3LWRmOWMtNGU3OS1iNjQ2LWQ2ZjUyYmZlYTk3Ni5naWY_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjYwNzI0JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI2MDcyNFQxMjQwMDlaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT02NGMzMzFiYWMxYzUxNDkwOTJjZGU1MjM0NjhiYzZkYWE1MDNiMzk3YTE4OWVmZjU4MjI5ODg5NjQwZWZiNjNiJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZyZXNwb25zZS1jb250ZW50LXR5cGU9aW1hZ2UlMkZnaWYifQ.QIaKt_yUj4Rt0HDz9KWirgtKQctDZ36F2uGay-5WNR8" class="js-gh-image-fallback"&gt;&lt;/a&gt;
&lt;p&gt;&lt;a href="https://docs.browser-use.com/open-source/browser-use-cli" rel="nofollow noopener noreferrer"&gt;Browser Use CLI ↗&lt;/a&gt;&lt;/p&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Quickstart&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;If you want to use Browser Use in your agent (Claude Code, Codex, Cursor, Hermes, OpenClaw, etc.), paste this prompt, and it sets everything up itself:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;Install or upgrade browser-use to the latest stable version with uv using Python 3.12, run `browser-use skill&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/browser-use/browser-use" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;
  
  
  4. Scrapy: the one that was doing this before it was cool
&lt;/h2&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%2Fty8oeghzcdqfhjdd5ntc.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%2Fty8oeghzcdqfhjdd5ntc.png" alt=" " width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; the Python crawling framework. Request scheduling, concurrency, retries, middleware, item pipelines, autothrottle, the works. &lt;/p&gt;

&lt;p&gt;It has been in production since roughly 2008 and it has crawled more pages than every other tool on this list combined.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reach for it when:&lt;/strong&gt; high volume, stable, mostly static targets. It is the boring correct answer and boring correct answers pay the bills.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/scrapy" rel="noopener noreferrer"&gt;
        scrapy
      &lt;/a&gt; / &lt;a href="https://github.com/scrapy/scrapy" rel="noopener noreferrer"&gt;
        scrapy
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Scrapy, a fast high-level web crawling &amp;amp; scraping framework for Python.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="rst"&gt;&lt;p&gt;&lt;a href="https://scrapy.org" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Scrapy" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fscrapy%2Fscrapy%2Fmaster%2Fdocs%2F_static%2Flogo.svg" width="480px"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://pypi.org/pypi/Scrapy" rel="nofollow noopener noreferrer"&gt;&lt;img alt="PyPI Version" src="https://camo.githubusercontent.com/c6e5cf8390893a8a251117f8ed53e246bf4ac6e4ec4ed9e1e7244d313bf6b316/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f5363726170792e737667"&gt;
&lt;/a&gt; &lt;a href="https://pypi.org/pypi/Scrapy" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Supported Python Versions" src="https://camo.githubusercontent.com/126656e2f64dc33e98e0788a9d4af3170c84e9941a568ae6e6732de34402997e/68747470733a2f2f696d672e736869656c64732e696f2f707970692f707976657273696f6e732f5363726170792e737667"&gt;
&lt;/a&gt; &lt;a href="https://github.com/scrapy/scrapy/actions?query=workflow%3AUbuntu" rel="noopener noreferrer"&gt;&lt;img alt="Ubuntu" src="https://github.com/scrapy/scrapy/workflows/Ubuntu/badge.svg"&gt;
&lt;/a&gt; &lt;a href="https://github.com/scrapy/scrapy/actions?query=workflow%3AmacOS" rel="noopener noreferrer"&gt;&lt;img alt="macOS" src="https://github.com/scrapy/scrapy/workflows/macOS/badge.svg"&gt;
&lt;/a&gt; &lt;a href="https://github.com/scrapy/scrapy/actions?query=workflow%3AWindows" rel="noopener noreferrer"&gt;&lt;img alt="Windows" src="https://github.com/scrapy/scrapy/workflows/Windows/badge.svg"&gt;
&lt;/a&gt; &lt;a href="https://codecov.io/github/scrapy/scrapy?branch=master" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Coverage report" src="https://camo.githubusercontent.com/d7ed79d0e710327ba38026e9137c069c1682689d91f9753743c1ee8eb8f80be3/68747470733a2f2f696d672e736869656c64732e696f2f636f6465636f762f632f6769746875622f7363726170792f7363726170792f6d61737465722e737667"&gt;
&lt;/a&gt; &lt;a href="https://anaconda.org/conda-forge/scrapy" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Conda Version" src="https://camo.githubusercontent.com/243d9a22e34216cb3bc6492290e30eef6cc7494827e86e780135314d2b4ecda3/68747470733a2f2f616e61636f6e64612e6f72672f636f6e64612d666f7267652f7363726170792f6261646765732f76657273696f6e2e737667"&gt;
&lt;/a&gt; &lt;a href="https://deepwiki.com/scrapy/scrapy" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Ask DeepWiki" src="https://camo.githubusercontent.com/0f5ae213ac378635adeb5d7f13cef055ad2f7d9a47b36de7b1c67dbe09f609ca/68747470733a2f2f6465657077696b692e636f6d2f62616467652e737667"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://scrapy.org/" rel="nofollow noopener noreferrer"&gt;Scrapy&lt;/a&gt; is a web scraping framework to extract structured data from websites.
It is cross-platform, and requires Python 3.10+. It is maintained by &lt;a href="https://www.zyte.com/" rel="nofollow noopener noreferrer"&gt;Zyte&lt;/a&gt;
(formerly Scrapinghub) and &lt;a href="https://github.com/scrapy/scrapy/graphs/contributors" rel="noopener noreferrer"&gt;many other contributors&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Install with:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;pip install scrapy&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;And follow the &lt;a href="https://docs.scrapy.org/en/latest/" rel="nofollow noopener noreferrer"&gt;documentation&lt;/a&gt; to learn how to use it.&lt;/p&gt;
&lt;p&gt;If you wish to contribute, see &lt;a href="https://docs.scrapy.org/en/master/contributing.html" rel="nofollow noopener noreferrer"&gt;Contributing&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/scrapy/scrapy" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;
  
  
  5. Crawlee: the infrastructure you were about to rebuild badly
&lt;/h2&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%2Fknwpsj2myrg92kdwyy6r.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%2Fknwpsj2myrg92kdwyy6r.png" alt=" " width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; the Node.js (and now Python) crawling library from the Apify folks. &lt;/p&gt;

&lt;p&gt;Proxy rotation, session management, browser fingerprint spoofing, automatic retries with backoff, request queues that survive a restart, and a unified API whether you are using plain HTTP or Playwright or Puppeteer under the hood.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reach for it when:&lt;/strong&gt; you are in the Node ecosystem, or your targets are actively hostile and you need the anti-blocking stack without paying for one.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/apify" rel="noopener noreferrer"&gt;
        apify
      &lt;/a&gt; / &lt;a href="https://github.com/apify/crawlee" rel="noopener noreferrer"&gt;
        crawlee
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Crawlee—A web scraping and browser automation library for Node.js to build reliable crawlers. In JavaScript and TypeScript. Extract data for AI, LLMs, RAG, or GPTs. Download HTML, PDF, JPG, PNG, and other files from websites. Works with Puppeteer, Playwright, Cheerio, JSDOM, and raw HTTP. Both headful and headless mode. With proxy rotation.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;
    &lt;a href="https://crawlee.dev" rel="nofollow noopener noreferrer"&gt;
        
          
          &lt;img alt="Crawlee" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fapify%2Fcrawlee%2Fmaster%2Fwebsite%2Fstatic%2Fimg%2Fcrawlee-light.svg%3Fsanitize%3Dtrue" width="500"&gt;
        
    &lt;/a&gt;
    &lt;br&gt;
    A web scraping and browser automation library
&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;
    &lt;a href="https://trendshift.io/repositories/5179" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/637b59019961350090b4c6adbb9ea2d9a4d92fdc058290b1e724fa728328b184/68747470733a2f2f7472656e6473686966742e696f2f6170692f62616467652f7265706f7369746f726965732f35313739" alt="apify%2Fcrawlee | Trendshift" width="250" height="55" class="js-gh-image-fallback"&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    &lt;a href="https://www.npmjs.com/package/@crawlee/core" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/4a5aaecc20fbd77756db8cff1cc4f2b2ee8e3e28f6069fe4d0c32918e815c797/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f40637261776c65652f636f72652e737667" alt="NPM latest version"&gt;&lt;/a&gt;
    &lt;a href="https://www.npmjs.com/package/@crawlee/core" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/b0561f3b9194f330524fd252052dc1ffa8331c370b8a17ad2ec3d407b4ccf4bd/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f646d2f40637261776c65652f636f72652e737667" alt="Downloads"&gt;&lt;/a&gt;
    &lt;a href="https://discord.gg/jyEM2PRvMU" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/6f7b3c5c601da8d5435705bbb3ee19f645e97d4ce29be98b633a6768346fa4f9/68747470733a2f2f696d672e736869656c64732e696f2f646973636f72642f3830313136333731373931353537343332333f6c6162656c3d646973636f7264" alt="Chat on discord"&gt;&lt;/a&gt;
    &lt;a href="https://github.com/apify/crawlee/actions/workflows/test-ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/apify/crawlee/actions/workflows/test-ci.yml/badge.svg?branch=master" alt="Build Status"&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Crawlee covers your crawling and scraping end-to-end and &lt;strong&gt;helps you build reliable scrapers. Fast.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Your crawlers will appear human-like and fly under the radar of modern bot protections even with the default configuration. Crawlee gives you the tools to crawl the web for links, scrape data, and store it to disk or cloud while staying configurable to suit your project's needs.&lt;/p&gt;
&lt;p&gt;Crawlee is available as the &lt;a href="https://www.npmjs.com/package/crawlee" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;crawlee&lt;/code&gt;&lt;/a&gt; NPM package.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 &lt;strong&gt;View full documentation, guides and examples on the &lt;a href="https://crawlee.dev" rel="nofollow noopener noreferrer"&gt;Crawlee project website&lt;/a&gt;&lt;/strong&gt; 👈&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Do you prefer 🐍 Python instead of JavaScript? &lt;a href="https://github.com/apify/crawlee-python" rel="noopener noreferrer"&gt;👉 Checkout Crawlee for Python 👈&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;We recommend visiting the &lt;a href="https://crawlee.dev/js/docs/introduction" rel="nofollow noopener noreferrer"&gt;Introduction tutorial&lt;/a&gt; in Crawlee documentation for more information.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Crawlee requires &lt;strong&gt;Node.js 16 or higher&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;With Crawlee CLI&lt;/h3&gt;
&lt;/div&gt;
&lt;p&gt;The fastest way to try Crawlee out is to use the &lt;strong&gt;Crawlee CLI&lt;/strong&gt; and choose the &lt;strong&gt;Getting started example&lt;/strong&gt;. The CLI will…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/apify/crawlee" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;
  
  
  6. Scrapling: the scraper that heals itself
&lt;/h2&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%2Fe0uvv4lgaomtrj2z0zba.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%2Fe0uvv4lgaomtrj2z0zba.png" alt=" " width="799" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; a Python scraper that remembers what an element &lt;em&gt;was&lt;/em&gt;, so when the site redesigns and your &lt;code&gt;.product-title&lt;/code&gt; becomes &lt;code&gt;.ProductCard__title--x7f2&lt;/code&gt;, it re-locates the element by similarity instead of returning an empty list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reach for it when:&lt;/strong&gt; you maintain many spiders over a long time and selector rot is your actual bottleneck. &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/D4Vinci" rel="noopener noreferrer"&gt;
        D4Vinci
      &lt;/a&gt; / &lt;a href="https://github.com/D4Vinci/Scrapling" rel="noopener noreferrer"&gt;
        Scrapling
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl!
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;
    &lt;a href="https://scrapling.readthedocs.io" rel="nofollow noopener noreferrer"&gt;
        
          
          &lt;img alt="Scrapling Poster" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FD4Vinci%2FScrapling%2Fmain%2Fdocs%2Fassets%2Fcover_light.svg%3Fsanitize%3Dtrue"&gt;
        
    &lt;/a&gt;
    &lt;br&gt;
    Effortless Web Scraping for the Modern Web
&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;
    &lt;a href="https://trendshift.io/repositories/14244" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/835790e5d69ed074841a94212e23721278ed5831280d57e2b1dd0622cb67dd80/68747470733a2f2f7472656e6473686966742e696f2f6170692f62616467652f7265706f7369746f726965732f3134323434" alt="D4Vinci%2FScrapling | Trendshift" width="250" height="55" class="js-gh-image-fallback"&gt;&lt;/a&gt;
    &lt;br&gt;
    &lt;a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_AR.md" rel="noopener noreferrer"&gt;العربيه&lt;/a&gt; | &lt;a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_ES.md" rel="noopener noreferrer"&gt;Español&lt;/a&gt; | &lt;a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_PT_BR.md" rel="noopener noreferrer"&gt;Português (Brasil)&lt;/a&gt; | &lt;a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_FR.md" rel="noopener noreferrer"&gt;Français&lt;/a&gt; | &lt;a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_DE.md" rel="noopener noreferrer"&gt;Deutsch&lt;/a&gt; | &lt;a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_CN.md" rel="noopener noreferrer"&gt;简体中文&lt;/a&gt; | &lt;a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_JP.md" rel="noopener noreferrer"&gt;日本語&lt;/a&gt; |  &lt;a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_RU.md" rel="noopener noreferrer"&gt;Русский&lt;/a&gt; | &lt;a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_KR.md" rel="noopener noreferrer"&gt;한국어&lt;/a&gt;
    &lt;br&gt;
    &lt;a href="https://github.com/D4Vinci/Scrapling/actions/workflows/tests.yml" alt="Tests" rel="noopener noreferrer"&gt;
        &lt;img alt="Tests" src="https://github.com/D4Vinci/Scrapling/actions/workflows/tests.yml/badge.svg"&gt;&lt;/a&gt;
    &lt;a href="https://badge.fury.io/py/Scrapling" alt="PyPI version" rel="nofollow noopener noreferrer"&gt;
        &lt;img alt="PyPI version" src="https://camo.githubusercontent.com/a2451de72943408acd742793b9e8e2ad762d23d92cf8acca928564d46381a193/68747470733a2f2f62616467652e667572792e696f2f70792f53637261706c696e672e737667"&gt;&lt;/a&gt;
    &lt;a href="https://clickpy.clickhouse.com/dashboard/scrapling" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/b97f66b56104ea04dc756df5bcde664994102257c6efdeb63e07df07ba87409d/68747470733a2f2f696d672e736869656c64732e696f2f707970692f646d2f73637261706c696e67" alt="PyPI package downloads"&gt;&lt;/a&gt;
    &lt;a href="https://github.com/D4Vinci/Scrapling/tree/main/agent-skill" alt="AI Agent Skill directory" rel="noopener noreferrer"&gt;
        &lt;img alt="Static Badge" src="https://camo.githubusercontent.com/4d771ee11a039f6e668a65b8c8b30bec7f308b1f8542b58e1b8bc1c55189f957/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f536b696c6c2d626c61636b3f7374796c653d666c6174266c6162656c3d4167656e74266c696e6b3d68747470732533412532462532466769746875622e636f6d253246443456696e636925324653637261706c696e67253246747265652532466d61696e2532466167656e742d736b696c6c"&gt;&lt;/a&gt;
    &lt;a href="https://clawhub.ai/D4Vinci/scrapling-official" alt="OpenClaw Skill" rel="nofollow noopener noreferrer"&gt;
        &lt;img alt="OpenClaw Skill" src="https://camo.githubusercontent.com/931b89bb439f1a592bf378ceff524910ed4ca5d712b72df0aee3d6a4b247f35b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f436c61776875622d6461726b7265643f7374796c653d666c6174266c6162656c3d4f70656e436c6177266c696e6b3d6874747073253341253246253246636c61776875622e6169253246443456696e636925324673637261706c696e672d6f6666696369616c"&gt;&lt;/a&gt;
    &lt;br&gt;
    &lt;a href="https://discord.gg/EMgGbDceNQ" alt="Discord" rel="nofollow noopener noreferrer"&gt;
      &lt;img alt="Discord" src="https://camo.githubusercontent.com/b6358467d1214bdcbee437c9fa9f91a73f69d7354395b143fa6f7881ae78ca77/68747470733a2f2f696d672e736869656c64732e696f2f646973636f72642f313336303738363338313034323838303533323f7374796c653d736f6369616c266c6f676f3d646973636f7264266c696e6b3d6874747073253341253246253246646973636f72642e6767253246454d6747624463654e51"&gt;
    &lt;/a&gt;
    &lt;a href="https://x.com/Scrapling_dev" alt="X (formerly Twitter)" rel="nofollow noopener noreferrer"&gt;
      &lt;img alt="X (formerly Twitter) Follow" src="https://camo.githubusercontent.com/55173a12196081f2837de50932ca6c22d14366767889d3d3738d5924e2677d03/68747470733a2f2f696d672e736869656c64732e696f2f747769747465722f666f6c6c6f772f53637261706c696e675f6465763f7374796c653d736f6369616c266c6f676f3d78266c696e6b3d6874747073253341253246253246782e636f6d25324653637261706c696e675f646576"&gt;
    &lt;/a&gt;
    &lt;br&gt;
    &lt;a href="https://pypi.org/project/scrapling/" alt="Supported Python versions" rel="nofollow noopener noreferrer"&gt;
        &lt;img alt="Supported Python versions" src="https://camo.githubusercontent.com/deba7a59c1312d07f6ad5bb1792e9c1321f0f10704704eb4d8b76e7e822d7586/68747470733a2f2f696d672e736869656c64732e696f2f707970692f707976657273696f6e732f73637261706c696e672e737667"&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    &lt;a href="https://scrapling.readthedocs.io/en/latest/parsing/selection.html" rel="nofollow noopener noreferrer"&gt;&lt;strong&gt;Selection methods&lt;/strong&gt;&lt;/a&gt;
    ·
    &lt;a href="https://scrapling.readthedocs.io/en/latest/fetching/choosing.html" rel="nofollow noopener noreferrer"&gt;&lt;strong&gt;Fetchers&lt;/strong&gt;&lt;/a&gt;
    ·
    &lt;a href="https://scrapling.readthedocs.io/en/latest/spiders/architecture.html" rel="nofollow noopener noreferrer"&gt;&lt;strong&gt;Spiders&lt;/strong&gt;&lt;/a&gt;
    ·
    &lt;a href="https://scrapling.readthedocs.io/en/latest/spiders/proxy-blocking.html" rel="nofollow noopener noreferrer"&gt;&lt;strong&gt;Proxy Rotation&lt;/strong&gt;&lt;/a&gt;
    ·
    &lt;a href="https://scrapling.readthedocs.io/en/latest/cli/overview.html" rel="nofollow noopener noreferrer"&gt;&lt;strong&gt;CLI&lt;/strong&gt;&lt;/a&gt;
    ·
    &lt;a href="https://scrapling.readthedocs.io/en/latest/ai/mcp-server.html" rel="nofollow noopener noreferrer"&gt;&lt;strong&gt;MCP&lt;/strong&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Scrapling is an adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl.&lt;/p&gt;
&lt;p&gt;Its parser learns from website changes and automatically relocates your elements when pages update. Its fetchers bypass anti-bot systems like Cloudflare Turnstile out of the box. And its spider framework lets you scale up to concurrent, multi-session crawls with pause/resume and automatic proxy rotation - all in a few lines of Python. One library, zero compromises.&lt;/p&gt;
&lt;p&gt;Blazing fast crawls with real-time stats and streaming. Built by Web Scrapers for Web Scrapers and regular users, there's something for everyone.&lt;/p&gt;
&lt;div class="highlight highlight-source-python notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;scrapling&lt;/span&gt;.&lt;span class="pl-s1"&gt;fetchers&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-v"&gt;Fetcher&lt;/span&gt;, &lt;span class="pl-v"&gt;AsyncFetcher&lt;/span&gt;, &lt;span class="pl-v"&gt;StealthyFetcher&lt;/span&gt;, &lt;span class="pl-v"&gt;DynamicFetcher&lt;/span&gt;
&lt;span class="pl-v"&gt;StealthyFetcher&lt;/span&gt;.&lt;span class="pl-c1"&gt;adaptive&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/D4Vinci/Scrapling" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;
  
  
  About that "sell this as a service for $5,000 a month" thing
&lt;/h2&gt;

&lt;p&gt;Every video on this topic ends with the same pitch: lead gen agencies charge thousands a month for this data, your cost is zero, go get rich.&lt;/p&gt;

&lt;p&gt;I want to be careful here, because there is a real business in web data, but "your cost is zero" is doing an enormous amount of work in that sentence.&lt;/p&gt;

&lt;p&gt;Your cost is not zero. &lt;/p&gt;

&lt;p&gt;Your cost is residential proxies, which are the single biggest line item and which no open source project ships. &lt;/p&gt;

&lt;p&gt;Your cost is CAPTCHA solving. &lt;/p&gt;

&lt;p&gt;Your cost is the engineer-hours when four targets redesign in the same week. &lt;/p&gt;

&lt;p&gt;Your cost is storage, monitoring, and the pager. &lt;/p&gt;

&lt;p&gt;What those agencies are actually charging for is not the scraping code, which as you have just seen is free and excellent. &lt;/p&gt;

&lt;p&gt;They are charging for the operational layer and the guarantee that the data shows up on Monday even though the site changed on Sunday.&lt;/p&gt;

&lt;p&gt;That is a real service and it is worth real money. &lt;/p&gt;

&lt;p&gt;Just go in knowing what you are actually selling.&lt;/p&gt;
&lt;h2&gt;
  
  
  The boring section that keeps you out of trouble
&lt;/h2&gt;

&lt;p&gt;You knew this was coming.&lt;/p&gt;

&lt;p&gt;Being able to scrape something is not the same as being allowed to, and the gap between those two is where people get their infrastructure banned or their company sued. &lt;/p&gt;

&lt;p&gt;A short, non-lawyer summary of what I actually do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read robots.txt and honour it.&lt;/strong&gt; Beyond ethics, it is self-interested: polite crawlers survive longer. 
Aggressive ones get IP ranges blocked, and then nobody on your team can scrape that domain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limit yourself before they rate limit you.&lt;/strong&gt; Concurrency of 2 and a delay is almost always enough. 
Nobody needs 200 requests a second against a small site's origin server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the Terms of Service&lt;/strong&gt;, especially for anything behind a login. 
"Publicly accessible" and "you have permission" are different claims.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personal data is a different legal universe.&lt;/strong&gt; GDPR, CCPA and friends do not care that the data was on a public page. 
Scraping product prices and scraping names with email addresses are not the same activity.&lt;/li&gt;
&lt;/ul&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%2Fbjjnqhgvwq4gvwbpqqwd.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%2Fbjjnqhgvwq4gvwbpqqwd.png" alt=" " width="360" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Seriously. Open devtools, filter the network tab by Fetch/XHR, and look. &lt;/p&gt;

&lt;p&gt;Roughly a third of the scrapers I have written could have been three lines against a JSON endpoint that was sitting there the whole time. &lt;/p&gt;

&lt;p&gt;Scraping is what you do after that fails, not instead of checking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Six projects, one idea: the web is still readable, the tooling to read it is free and open, and most of the wall that showed up over the last two years is made of TLS fingerprints and inertia rather than actual locks.&lt;/p&gt;

&lt;p&gt;Pick by job, not by star count. Start light. Check for an API. Be a polite guest on other people's servers.&lt;/p&gt;

&lt;p&gt;And if you only take one thing from this: next time you get an inexplicable 403, before you reach for the browser, try &lt;code&gt;impersonate="chrome"&lt;/code&gt;. You will feel very silly and very happy.&lt;/p&gt;

&lt;p&gt;What did I miss? I know some of you have a favourite obscure Go crawler you have been waiting for an excuse to talk about. Drop it in the comments, I read all of them.&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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Tried building GitHub's search box in Go</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Thu, 23 Jul 2026 11:39:49 +0000</pubDate>
      <link>https://dev.to/lovestaco/tried-building-githubs-search-box-in-go-2dk6</link>
      <guid>https://dev.to/lovestaco/tried-building-githubs-search-box-in-go-2dk6</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You know that little search box on GitHub issues? The one where you type &lt;code&gt;is:open label:bug author:me created:&amp;gt;2024-01-01&lt;/code&gt; and it just &lt;em&gt;gets it&lt;/em&gt;? I always assumed there was some gnarly hand-rolled parser behind it, held together with regex and prayers.&lt;/p&gt;

&lt;p&gt;So I decided to build my own version in Go, using a neat little library called &lt;a href="https://github.com/alecthomas/participle" rel="noopener noreferrer"&gt;participle&lt;/a&gt; (by Alec Thomas) that lets you define a parser as a plain Go struct.&lt;/p&gt;

&lt;p&gt;Turns out the whole thing is surprisingly little code, and most of it reads like the grammar you would sketch on a napkin.&lt;/p&gt;

&lt;p&gt;By the end of this post we will have a tiny tool that takes a search string, turns it into a typed syntax tree, and compiles that tree into safe, parameterized SQL. &lt;/p&gt;

&lt;p&gt;Here is the money shot, so you know where we are headed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ make run q='is:open label:bug'
query: is:open label:bug
6 issue(s)

#   STATE  AUTHOR   CREATED     COMMENTS  LABELS         TITLE
1   open   octocat  2024-02-10  12        bug,urgent     Login button unresponsive on mobile
6   open   bob      2024-04-18  15        bug,p0,urgent  Memory leak in worker pool
8   open   octocat  2024-09-03  9         bug            Flaky test in CI
12  open   bob      2024-07-19  6         bug,p0         Rate limit not enforced
15  open   alice    2024-11-08  11        bug,urgent     Search returns stale results
18  open   bob      2024-10-22  10        bug,p0         Slow query on dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let's build it.&lt;/p&gt;
&lt;h2&gt;
  
  
  The filter string nobody wants to parse
&lt;/h2&gt;

&lt;p&gt;Here is the version we all write first, be honest:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// the "I'll clean it up later" special&lt;/span&gt;
&lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fields&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;parts&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;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"label:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrimPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"label:"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"is:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrimPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"is:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;// ... 40 more else-ifs, each one a tiny betrayal&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This works right up until someone types &lt;code&gt;label:"help wanted"&lt;/code&gt; with a space in it. Or &lt;code&gt;-author:bot&lt;/code&gt; to exclude a user. Or &lt;code&gt;created:&amp;gt;2024-01-01&lt;/code&gt; with a comparison. &lt;/p&gt;

&lt;p&gt;Every one of those pushes you deeper into regex territory, and hand-rolled regex for this kind of thing has a way of solving one edge case and quietly creating two more.&lt;/p&gt;

&lt;p&gt;Once you stop treating this as string munging and start treating it as an actual parsing problem, the solution gets a lot friendlier.&lt;/p&gt;
&lt;h2&gt;
  
  
  Participle: a grammar is just a struct
&lt;/h2&gt;

&lt;p&gt;participle has a lovely core idea: you describe your grammar as a struct, annotate the fields with tags, and it builds the parser for you. &lt;/p&gt;

&lt;p&gt;No separate &lt;code&gt;.y&lt;/code&gt; grammar file, no code generation step. &lt;/p&gt;

&lt;p&gt;The struct &lt;em&gt;is&lt;/em&gt; the grammar.&lt;/p&gt;

&lt;p&gt;Here is a taste. If you wanted to parse a single &lt;code&gt;key:value&lt;/code&gt; pair, you would write:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Pair&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Key&lt;/span&gt;   &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`parser:"@Ident"`&lt;/span&gt;
    &lt;span class="n"&gt;Colon&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`parser:"@Colon"`&lt;/span&gt;
    &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`parser:"@Ident"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;@&lt;/code&gt; means "capture this into the field." &lt;/p&gt;

&lt;p&gt;That's basically the whole trick. &lt;/p&gt;

&lt;p&gt;Now let's scale it up to a real search language.&lt;/p&gt;
&lt;h2&gt;
  
  
  The AST: three little structs
&lt;/h2&gt;

&lt;p&gt;Our search syntax is what I call "GitHub minimal": qualifiers like &lt;code&gt;is:open&lt;/code&gt;, comparisons like &lt;code&gt;comments:&amp;gt;=5&lt;/code&gt;, free text like &lt;code&gt;login&lt;/code&gt;, negation with a leading &lt;code&gt;-&lt;/code&gt;, and implicit AND between terms. &lt;/p&gt;

&lt;p&gt;No &lt;code&gt;OR&lt;/code&gt;, no parentheses (we will talk about those at the end).&lt;/p&gt;

&lt;p&gt;The entire grammar fits in three structs:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Query is the whole thing: a flat list of terms, all ANDed together.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Query&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Terms&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Term&lt;/span&gt; &lt;span class="s"&gt;`parser:"@@*"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Term is one search unit: a key:value qualifier OR a free-text word,&lt;/span&gt;
&lt;span class="c"&gt;// with an optional leading "-" to negate it.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Term&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Negated&lt;/span&gt;   &lt;span class="kt"&gt;bool&lt;/span&gt;       &lt;span class="s"&gt;`parser:"@Dash?"`&lt;/span&gt;
    &lt;span class="n"&gt;Qualifier&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Qualifier&lt;/span&gt; &lt;span class="s"&gt;`parser:"( @@"`&lt;/span&gt;
    &lt;span class="n"&gt;Text&lt;/span&gt;      &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;     &lt;span class="s"&gt;`parser:"| @(String | Ident | Number) )"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Qualifier is "key:value", e.g. is:open or created:&amp;gt;2024-01-01.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Qualifier&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`parser:"@Ident Colon"`&lt;/span&gt;
    &lt;span class="n"&gt;Op&lt;/span&gt;  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`parser:"@Op?"`&lt;/span&gt;
    &lt;span class="n"&gt;Val&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="s"&gt;`parser:"@(String | Date | Number | Ident)"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Read those tags out loud and they say what they mean. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;@@*&lt;/code&gt; is "zero or more sub-structs." The &lt;code&gt;?&lt;/code&gt; after &lt;code&gt;@Dash&lt;/code&gt; makes the negation optional. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;( @@ | @... )&lt;/code&gt; in &lt;code&gt;Term&lt;/code&gt; is an alternation: a term is either a qualifier or a bare word.&lt;/p&gt;

&lt;p&gt;What I like about this approach is that the AST doubles as documentation. &lt;/p&gt;

&lt;p&gt;When a teammate asks what the search syntax supports, I point them at these three structs instead of a README that's probably out of date, because the structs are the parser and can't drift from what actually runs.&lt;/p&gt;

&lt;p&gt;There is one subtle line doing a lot of work when we build the parser:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;participle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustBuild&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;participle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lexer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searchLexer&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;participle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Elide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"whitespace"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;participle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UseLookahead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That &lt;code&gt;UseLookahead(2)&lt;/code&gt; matters.&lt;/p&gt;

&lt;p&gt;Both &lt;code&gt;login&lt;/code&gt; (free text) and &lt;code&gt;label:bug&lt;/code&gt; (a qualifier) start with an identifier. &lt;/p&gt;

&lt;p&gt;The parser needs to peek ahead and check for a &lt;code&gt;:&lt;/code&gt; before it decides which branch to take. &lt;/p&gt;

&lt;p&gt;Lookahead of 2 lets it see the colon coming.&lt;/p&gt;
&lt;h2&gt;
  
  
  How the lexer handles the dash
&lt;/h2&gt;

&lt;p&gt;The tokenizer has one detail worth slowing down for, because the dash character does three different jobs depending on where it shows up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;-author:bot&lt;/code&gt; -&amp;gt; the leading dash is &lt;strong&gt;negation&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;help-wanted&lt;/code&gt; -&amp;gt; the dash is part of a &lt;strong&gt;label name&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2024-01-01&lt;/code&gt; -&amp;gt; the dashes are &lt;strong&gt;date separators&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your lexer treats every &lt;code&gt;-&lt;/code&gt; as a negation token, &lt;code&gt;help-wanted&lt;/code&gt; becomes "help, NOT wanted," and your date turns into subtraction. &lt;/p&gt;

&lt;p&gt;Neither of those is what you want.&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%2Ff2etcyfgl5vokqvc0p5h.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%2Ff2etcyfgl5vokqvc0p5h.png" alt=" " width="360" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fix is worth understanding on its own, because it's a technique you'll reuse elsewhere. &lt;/p&gt;

&lt;p&gt;participle's simple lexer is an ordered list of regex rules, and it takes the first one that matches. Order decides everything:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;searchLexer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lexer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustSimple&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;lexer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SimpleRule&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"whitespace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Pattern&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;`\s+`&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"String"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Pattern&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;`"(\\.|[^"])*"`&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Date"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Pattern&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;`\d{4}-\d{2}-\d{2}`&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Pattern&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;`\d+`&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Op"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Pattern&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;`&amp;gt;=|&amp;lt;=|&amp;gt;|&amp;lt;`&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Colon"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Pattern&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;`:`&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Ident"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Pattern&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;`[a-zA-Z_][a-zA-Z0-9_./-]*`&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Dash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Pattern&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;`-`&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Notice that &lt;code&gt;Date&lt;/code&gt;, &lt;code&gt;Number&lt;/code&gt;, and &lt;code&gt;Ident&lt;/code&gt; all come before the bare &lt;code&gt;Dash&lt;/code&gt; rule, and every one of them must start with a digit or a letter. &lt;/p&gt;

&lt;p&gt;Walk through &lt;code&gt;-author:bot&lt;/code&gt; one character at a time.&lt;/p&gt;

&lt;p&gt;The lexer is sitting at the &lt;code&gt;-&lt;/code&gt;. It tries &lt;code&gt;Date&lt;/code&gt;, which needs a digit first, so no. It tries &lt;code&gt;Number&lt;/code&gt;, same problem. It tries &lt;code&gt;Ident&lt;/code&gt;, which needs a letter or underscore, and &lt;code&gt;-&lt;/code&gt; is neither. Nothing longer can start here, so the plain &lt;code&gt;Dash&lt;/code&gt; rule finally gets its turn, and that becomes our negation token.&lt;/p&gt;

&lt;p&gt;Now walk through &lt;code&gt;help-wanted&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The lexer starts at &lt;code&gt;h&lt;/code&gt;, tries &lt;code&gt;Ident&lt;/code&gt;, and the &lt;code&gt;Ident&lt;/code&gt; pattern swallows the internal dash because I included &lt;code&gt;-&lt;/code&gt; in its character class. One token, &lt;code&gt;help-wanted&lt;/code&gt;, done. &lt;/p&gt;

&lt;p&gt;The date &lt;code&gt;2024-01-01&lt;/code&gt; gets grabbed whole by the &lt;code&gt;Date&lt;/code&gt; rule before &lt;code&gt;Number&lt;/code&gt; or &lt;code&gt;Dash&lt;/code&gt; ever get a look.&lt;/p&gt;

&lt;p&gt;So the dash only becomes a negation token when it literally cannot be anything else. &lt;/p&gt;

&lt;p&gt;No special cases in the parser, no lookbehind, no state machine — just careful ordering of regex rules.&lt;/p&gt;

&lt;p&gt;Here is the whole pipeline, from raw string to rows:&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%2Fupi3h091n0oeg9hukv7c.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%2Fupi3h091n0oeg9hukv7c.png" alt=" " width="798" height="60"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Compiling the tree into SQL
&lt;/h2&gt;

&lt;p&gt;A parsed tree is nice, but nobody can query a Go struct directly. &lt;/p&gt;

&lt;p&gt;We need SQL. &lt;/p&gt;

&lt;p&gt;This is where the AST pays off, because walking a typed tree and emitting strings is fairly mechanical work, which is exactly what you want near a database.&lt;/p&gt;

&lt;p&gt;The heart of the compiler is a registry that maps each qualifier key to a column and a comparison style:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;fieldDef&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"is"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kindString&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;"state"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kindString&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;"author"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kindString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userAware&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;"assignee"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"assignee"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kindString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userAware&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;"milestone"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"milestone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kindString&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;"label"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kindLabel&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;"created"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kindDate&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;"updated"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"updated_at"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kindDate&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;"comments"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"comment_count"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kindNumber&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Adding a new searchable field means adding one line here. &lt;/p&gt;

&lt;p&gt;The grammar doesn't change, and neither does the lexer.&lt;/p&gt;

&lt;p&gt;Each term compiles to a small SQL fragment plus its arguments:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;kindString&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;// is:open -&amp;gt; state = ?&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" = ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;

&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;kindDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kindNumber&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;// created:&amp;gt;2024-01-01 -&amp;gt; created_at &amp;gt; ?&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;qf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Op&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"="&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;

&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;kindLabel&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;// labels live in a join table, so we ask "does a matching row exist?"&lt;/span&gt;
    &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"EXISTS (SELECT 1 FROM issue_labels il "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="s"&gt;"JOIN labels l ON l.id = il.label_id "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="s"&gt;"WHERE il.issue_id = issues.id AND l.name = ?)"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Free text (a bare &lt;code&gt;login&lt;/code&gt;) becomes a &lt;code&gt;LIKE&lt;/code&gt; against the title and body. &lt;/p&gt;

&lt;p&gt;Negation wraps the whole fragment in &lt;code&gt;NOT (...)&lt;/code&gt;. Implicit AND joins the fragments with &lt;code&gt;AND&lt;/code&gt;. That's the entire compiler.&lt;/p&gt;
&lt;h3&gt;
  
  
  The part where I stop you from getting hacked
&lt;/h3&gt;

&lt;p&gt;Look carefully at those returns. &lt;/p&gt;

&lt;p&gt;The column names come from my registry, which is fixed code.&lt;/p&gt;

&lt;p&gt;The user's value never gets glued into the SQL string. It goes into a separate &lt;code&gt;[]any&lt;/code&gt; of arguments, and the SQL only ever contains a &lt;code&gt;?&lt;/code&gt; placeholder. &lt;/p&gt;

&lt;p&gt;Keeping user input and SQL structure in two separate places is what actually prevents injection here — it's not a cosmetic choice.&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%2Fkv77sd45kjpe273g8qe4.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%2Fkv77sd45kjpe273g8qe4.png" alt=" " width="360" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is a &lt;code&gt;--sql&lt;/code&gt; flag in the tool that prints exactly what gets sent to the database, so you can see the safety with your own eyes. &lt;/p&gt;

&lt;p&gt;Watch what happens to a spicy query full of user input:&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%2F5eycumx232zti0xcnh0p.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%2F5eycumx232zti0xcnh0p.png" alt=" " width="613" height="99"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And a bigger one with negation, a date, a label join, and free text all at once:&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%2Fo7wxm4thh9pzeghkty94.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%2Fo7wxm4thh9pzeghkty94.png" alt=" " width="794" height="62"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every value the user typed ends up in &lt;code&gt;args&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The SQL itself is all structure and question marks. &lt;/p&gt;

&lt;p&gt;If someone types &lt;code&gt;author:'; DROP TABLE issues; --&lt;/code&gt; it just becomes a string that matches zero authors, and your tables are unaffected.&lt;/p&gt;

&lt;p&gt;By the way, &lt;code&gt;author:me&lt;/code&gt; quietly became &lt;code&gt;octocat&lt;/code&gt; in that first example. &lt;/p&gt;

&lt;p&gt;The compiler resolves &lt;code&gt;me&lt;/code&gt; to the current user, the same way GitHub does.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the parsed tree actually looks like
&lt;/h2&gt;

&lt;p&gt;If you are a visual thinker, here is the AST for &lt;code&gt;is:open label:bug -author:bot&lt;/code&gt; drawn out. This is what those three structs produce in memory:&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%2Fhvay1htu7daskhnda4ym.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%2Fhvay1htu7daskhnda4ym.png" alt=" " width="633" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three terms, hanging off one query, the third carrying a &lt;code&gt;Negated: true&lt;/code&gt; flag. &lt;/p&gt;

&lt;p&gt;The compiler walks this top to bottom and stitches the fragments together.&lt;/p&gt;
&lt;h2&gt;
  
  
  Making it real with SQLite
&lt;/h2&gt;

&lt;p&gt;I wired this to a real database so it isn't just a toy that prints ASTs. &lt;/p&gt;

&lt;p&gt;I used &lt;a href="https://pkg.go.dev/modernc.org/sqlite" rel="noopener noreferrer"&gt;modernc.org/sqlite&lt;/a&gt;, a pure Go SQLite driver, which means no CGO, no C compiler, and &lt;code&gt;go test&lt;/code&gt; just works on any machine. &lt;/p&gt;

&lt;p&gt;The schema is three tables: &lt;code&gt;issues&lt;/code&gt;, &lt;code&gt;labels&lt;/code&gt;, and an &lt;code&gt;issue_labels&lt;/code&gt; join table, which is exactly the thing that makes &lt;code&gt;label:&lt;/code&gt; interesting to compile.&lt;/p&gt;

&lt;p&gt;There is a seeded sample dataset with twenty issues so every example in this post is reproducible. &lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;make demo&lt;/code&gt; and it fires a whole battery of queries at the database. A few highlights:&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%2Fh4tq3wbwz0m2m7n4jf93.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%2Fh4tq3wbwz0m2m7n4jf93.png" alt=" " width="677" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That second query, &lt;code&gt;-author:bot is:closed&lt;/code&gt;, taught me a separate lesson. When I first ran it through the CLI, it errored out, because a query that starts with &lt;code&gt;-&lt;/code&gt; looks like a command line flag to Go's &lt;code&gt;flag&lt;/code&gt; package. &lt;/p&gt;

&lt;p&gt;The fix is the standard POSIX &lt;code&gt;--&lt;/code&gt; separator (&lt;code&gt;go run . -- "-author:bot is:closed"&lt;/code&gt;) — the same "what does this dash mean" ambiguity from the lexer, showing up again one layer down at the command line.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where this goes next
&lt;/h2&gt;

&lt;p&gt;Right now there is no &lt;code&gt;OR&lt;/code&gt; and no parentheses, on purpose, to keep the first version teachable. &lt;/p&gt;

&lt;p&gt;Adding them is a satisfying next step: you introduce an &lt;code&gt;OrExpr&lt;/code&gt; and &lt;code&gt;AndExpr&lt;/code&gt; layer above the terms, and you get operator precedence essentially for free from how the structs nest. &lt;/p&gt;

&lt;p&gt;Same AST idea, one more level deep.&lt;/p&gt;

&lt;p&gt;Because the AST sits in the middle as a clean seam, SQL isn't the only possible backend.&lt;/p&gt;

&lt;p&gt;You could compile the same tree into an in-memory &lt;code&gt;func(Issue) bool&lt;/code&gt; for filtering a slice, or into an Elasticsearch query, or a Bleve query. &lt;/p&gt;

&lt;p&gt;Parse once, target anything.&lt;/p&gt;

&lt;p&gt;The whole thing is about 200 lines of Go: three structs for the grammar, one ordered lexer, one registry for the compiler. &lt;/p&gt;

&lt;p&gt;That's the entire GitHub-style search box, minus the parts that would get me sued.&lt;/p&gt;

&lt;p&gt;A big shoutout to Alec Thomas for participle. &lt;/p&gt;

&lt;p&gt;The whole "your grammar is just a struct" idea is what made this fun instead of a chore, and his library quietly did all the heavy lifting while I got to take credit for it in a blog post.&lt;/p&gt;

&lt;p&gt;All the code, the seeded database, and the tests are here: &lt;a href="https://github.com/lovestaco/gh-search-dsl" rel="noopener noreferrer"&gt;github.com/lovestaco/gh-search-dsl&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Clone it, run &lt;code&gt;make demo&lt;/code&gt;, and try adding a new qualifier, it should only take one line.&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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>programming</category>
      <category>beginners</category>
      <category>go</category>
    </item>
    <item>
      <title>Your AI Re-Reads Your Whole Codebase Every Session. Hand It a Map Instead</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Mon, 20 Jul 2026 17:42:39 +0000</pubDate>
      <link>https://dev.to/lovestaco/your-ai-re-reads-your-whole-codebase-every-session-hand-it-a-map-instead-3ocd</link>
      <guid>https://dev.to/lovestaco/your-ai-re-reads-your-whole-codebase-every-session-hand-it-a-map-instead-3ocd</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let me paint you a picture you already know too well.&lt;/p&gt;

&lt;p&gt;You open a fresh Claude Code (or Codex, or whatever coding agent you're loyal to this week) session. &lt;/p&gt;

&lt;p&gt;You type one tiny question. &lt;/p&gt;

&lt;p&gt;Something like "where do we handle retries?" And before your agent even &lt;em&gt;thinks&lt;/em&gt; about answering, it quietly inhales half your repo. &lt;/p&gt;

&lt;p&gt;Every file, every function, every stray &lt;code&gt;utils.js&lt;/code&gt; that nobody has touched since the Obama administration. Thousands of tokens, gone, before you got a single useful word back.&lt;/p&gt;

&lt;p&gt;That's the token tax. &lt;/p&gt;

&lt;p&gt;You pay it every single session. And it's the reason your &lt;code&gt;$20&lt;/code&gt; plan sometimes &lt;em&gt;feels&lt;/em&gt; like it's rationing you like it's 1943.&lt;/p&gt;

&lt;p&gt;So when a tool showed up that promised to fix exactly this, I did the responsible thing: I got suspicious, then I tried it anyway. &lt;/p&gt;

&lt;p&gt;The tool is called &lt;strong&gt;&lt;a href="https://github.com/Graphify-Labs/graphify" rel="noopener noreferrer"&gt;Graphify&lt;/a&gt;&lt;/strong&gt;, and the short version is that it stops your AI from cramming for the same exam every morning.&lt;/p&gt;

&lt;p&gt;Let me explain, because there's a genuinely clever idea underneath the hype.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem isn't your AI. It's the cramming.
&lt;/h2&gt;

&lt;p&gt;Here's the thing about large language models reading raw files: they're doing it blind, every time. &lt;/p&gt;

&lt;p&gt;There's no memory of the shape of your project between sessions. &lt;/p&gt;

&lt;p&gt;So the model re-derives "how does this codebase fit together" from scratch, token by expensive token, on question number one.&lt;/p&gt;

&lt;p&gt;The insight Graphify runs on isn't new, and the author is upfront about where it came from: an old note by &lt;strong&gt;Andrej Karpathy&lt;/strong&gt;  about building an LLM-friendly knowledge base. The pitch was simple. Instead of feeding a model your raw files over and over, &lt;em&gt;index&lt;/em&gt; them once into something structured, then let the model query that.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-2039805659525644595-189" src="https://platform.twitter.com/embed/Tweet.html?id=2039805659525644595"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-2039805659525644595-189');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=2039805659525644595&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Graphify is a real, working version of that idea for your local folders. &lt;/p&gt;

&lt;p&gt;You point it at a directory. &lt;/p&gt;

&lt;p&gt;It compiles that directory into a &lt;strong&gt;knowledge graph&lt;/strong&gt;. And from then on, your agent asks the graph instead of re-reading the world.&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%2Ffxfc11spe4zgctdct8ij.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%2Ffxfc11spe4zgctdct8ij.png" alt=" " width="360" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Okay, so what even &lt;em&gt;is&lt;/em&gt; a knowledge graph here?
&lt;/h2&gt;

&lt;p&gt;Fair question, because "knowledge graph" is one of those phrases people say to sound smart at meetups.&lt;/p&gt;

&lt;p&gt;Strip the jargon and it's two things: &lt;strong&gt;nodes&lt;/strong&gt; and &lt;strong&gt;edges&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Nodes are the pieces of your project (a function, a file, a component, a concept). &lt;/p&gt;

&lt;p&gt;Edges are the relationships between them (this function &lt;em&gt;calls&lt;/em&gt; that one, this doc &lt;em&gt;describes&lt;/em&gt; that module, this config &lt;em&gt;feeds&lt;/em&gt; that service).&lt;/p&gt;

&lt;p&gt;Draw enough of those and clusters start to appear on their own. &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%2Flje7ujfw6nzq7h1sbb5q.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%2Flje7ujfw6nzq7h1sbb5q.png" alt=" " width="616" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See that red node with everything hanging off it? Graphify calls those &lt;strong&gt;god nodes&lt;/strong&gt;, and they're not the little leaf nodes out at the edges. &lt;/p&gt;

&lt;p&gt;They're the opposite: the &lt;em&gt;most connected&lt;/em&gt; nodes in your whole project. &lt;/p&gt;

&lt;p&gt;Your core abstractions. The stuff that, if it broke, would take the afternoon down with it. &lt;/p&gt;

&lt;p&gt;Finding those automatically is weirdly delightful, because every codebase has three or four functions secretly holding the entire thing together, and now you get a list of them for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it actually builds the thing
&lt;/h2&gt;

&lt;p&gt;This is the part I found genuinely neat, so I'm going to nerd out for a second.&lt;/p&gt;

&lt;p&gt;Graphify doesn't just throw your whole repo at a model and pray. &lt;/p&gt;

&lt;p&gt;It splits the work in two:&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%2F2b8720nptxfvcdv7i1h1.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%2F2b8720nptxfvcdv7i1h1.png" alt=" " width="800" height="138"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;code&lt;/strong&gt;, it parses the actual syntax tree.&lt;/p&gt;

&lt;p&gt;No LLM, no tokens, no cost. &lt;/p&gt;

&lt;p&gt;It just &lt;em&gt;reads the code like a compiler would&lt;/em&gt; and pulls out functions, calls, and structure deterministically. That's the free lunch, and it's a big one.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;docs, papers, and images&lt;/strong&gt;, where meaning matters more than syntax, it brings in a model to extract concepts and relationships. &lt;/p&gt;

&lt;p&gt;That's the part that costs tokens, and it only runs on the stuff that actually needs interpreting.&lt;/p&gt;

&lt;p&gt;Then it merges both, runs community detection to find the natural clusters, and hands you three files: an interactive &lt;code&gt;graph.html&lt;/code&gt;, a plain-language &lt;code&gt;GRAPH_REPORT.md&lt;/code&gt;, and the raw &lt;code&gt;graph.json&lt;/code&gt; for anything programmatic.&lt;/p&gt;

&lt;p&gt;One detail I appreciated: the report keeps an honest audit trail. &lt;/p&gt;

&lt;p&gt;Every relationship is tagged as &lt;code&gt;EXTRACTED&lt;/code&gt; (it's really in the code), &lt;code&gt;INFERRED&lt;/code&gt; (the model reasoned it out, trust but verify), or &lt;code&gt;AMBIGUOUS&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;No pretending the guesses are gospel. I have trust issues with tools, and this one earned a little back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The payoff: an actual map you can poke
&lt;/h2&gt;

&lt;p&gt;Here's the interactive graph in motion:&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%2F0y6wl8fntgunav5kwter.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0y6wl8fntgunav5kwter.gif" alt="graph output" width="760" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can toggle communities on and off, isolate the cluster you care about, and &lt;em&gt;see&lt;/em&gt; how things connect instead of holding forty files in your head at once. &lt;/p&gt;

&lt;p&gt;The first time you spot a dependency you didn't know existed, staring right at you as a line between two nodes, it's a small "oh, &lt;em&gt;that's&lt;/em&gt; why that broke" moment. Cheaper than therapy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three commands that make it stick
&lt;/h2&gt;

&lt;p&gt;The graph is nice to look at, but the reason it earns a spot in your workflow is querying. &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%2Fh331kb05ql3qomzc7qh1.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%2Fh331kb05ql3qomzc7qh1.png" alt=" " width="614" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three commands do most of the heavy lifting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;query "how does auth work?"&lt;/code&gt; walks the graph and answers in plain language, citing the actual files. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;path "AdminPanel" "Database"&lt;/code&gt; finds the shortest route between two concepts and shows you every hop.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;explain "RateLimiter"&lt;/code&gt; gives you a human explanation of a single node using its neighborhood in the graph.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And when you change a bunch of files, you don't rebuild from zero. &lt;code&gt;--update&lt;/code&gt; re-extracts only what changed. &lt;/p&gt;

&lt;p&gt;Your map stays current without re-surveying the whole continent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bit your wallet cares about
&lt;/h2&gt;

&lt;p&gt;Now the money question, literally.&lt;/p&gt;

&lt;p&gt;Because your agent queries a compact graph instead of swallowing raw files, the token cost of "understand my project" drops hard. &lt;/p&gt;

&lt;p&gt;The project claims around &lt;strong&gt;70% fewer tokens&lt;/strong&gt;, and on individual questions the reported reductions are measured in multiples, not modest percentages. &lt;/p&gt;

&lt;p&gt;Your mileage will vary with repo size and question type, so take the exact number with a grain of salt, but the &lt;em&gt;direction&lt;/em&gt; is not subtle.&lt;/p&gt;

&lt;p&gt;Translation: the plan you're already paying for suddenly stretches a lot further. Same subscription, more actual work per dollar. That's the whole trick.&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%2Fekwimte482hy3pnsvyql.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%2Fekwimte482hy3pnsvyql.png" alt=" " width="360" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is actually for
&lt;/h2&gt;

&lt;p&gt;Straight answer: this shines when you &lt;strong&gt;read more than you write&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Onboarding onto a codebase somebody else built.&lt;/p&gt;

&lt;p&gt;Doing research across a pile of code and docs. &lt;/p&gt;

&lt;p&gt;Exploring a repo you cloned an hour ago and already regret.&lt;/p&gt;

&lt;p&gt;In those situations, a pre-built map is exactly what you want.&lt;/p&gt;

&lt;p&gt;If you're heads-down writing brand new code in a small project you already know cold, the graph buys you less. And you do pay an upfront cost to build it (the semantic pass on docs and images isn't free), though a code-only run leans on that deterministic parser and stays cheap. &lt;/p&gt;

&lt;p&gt;It's a "spend a little now to save a lot later" deal, so spend it where later actually shows up.&lt;/p&gt;

&lt;p&gt;Setup, if you're curious, is about as heavy as installing any Python tool: you need Python and &lt;code&gt;uv&lt;/code&gt; (think of &lt;code&gt;uv&lt;/code&gt; as &lt;code&gt;npm&lt;/code&gt;, but for Python and in a good mood), then one install command, then you point it at a folder. That's the whole ceremony.&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%2F8wauh6i66u8eyzvbk9h8.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%2F8wauh6i66u8eyzvbk9h8.png" alt=" " width="360" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;We spend a lot of energy making models smarter. &lt;/p&gt;

&lt;p&gt;Graphify makes a quieter bet: give the model a decent &lt;em&gt;memory&lt;/em&gt; of your project, and it stops wasting brainpower re-learning the map every morning.&lt;/p&gt;

&lt;p&gt;It won't write your code for you. &lt;/p&gt;

&lt;p&gt;But it will stop your AI from treating every session like the first day of school. Give your codebase a map. Your token budget might send a thank-you card.&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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>ai</category>
      <category>productivity</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Your PDFs Are Eating Your LLM's Tokens for Breakfast</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Sat, 18 Jul 2026 17:48:36 +0000</pubDate>
      <link>https://dev.to/lovestaco/your-pdfs-are-eating-your-llms-tokens-for-breakfast-1k96</link>
      <guid>https://dev.to/lovestaco/your-pdfs-are-eating-your-llms-tokens-for-breakfast-1k96</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Real talk for a second. &lt;/p&gt;

&lt;p&gt;You drag a chunky PDF into your favorite AI chat, ask one innocent little question, and somewhere in the background your token budget just quietly combusted. &lt;/p&gt;

&lt;p&gt;I want to walk you through why that happens, and the free Microsoft tool that has been sitting on GitHub the whole time waiting for you to notice it.&lt;/p&gt;

&lt;p&gt;Grab a coffee. This one is going to save you money.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem nobody told you about
&lt;/h2&gt;

&lt;p&gt;Here is the thing about PDFs. They are Pretty Darn Fat.&lt;/p&gt;

&lt;p&gt;When you upload one to a model, it does not just read the words like you and I do. &lt;/p&gt;

&lt;p&gt;It has to wade through the fonts, the layout coordinates, the half-broken tables, the embedded images, and every other bit of packaging that PDFs love to hoard. &lt;/p&gt;

&lt;p&gt;All of that formatting noise gets turned into tokens, and tokens are the currency you are paying with.&lt;/p&gt;

&lt;p&gt;Rough numbers: a single PDF page can run somewhere between 1,500 and 3,000 tokens depending on how messy it is. Do the math on a 20-page document and you can be looking at up to 70,000 tokens poof! That is before your first question. You paid the cover charge and you have not even ordered a drink yet.&lt;/p&gt;

&lt;p&gt;Here is that same idea as a picture, because I like pictures.&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%2Fe90dlwj1z19u047xf2ix.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%2Fe90dlwj1z19u047xf2ix.png" alt=" " width="800" height="138"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The left path is where most of us live. &lt;/p&gt;

&lt;p&gt;The right path is where I want to take you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter MarkItDown
&lt;/h2&gt;

&lt;p&gt;The fix is a free, open-source tool from Microsoft called &lt;strong&gt;MarkItDown&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;It has blown past 110,000 stars on GitHub (sitting around 119k as I write this), so this is not some abandoned weekend hack. &lt;/p&gt;

&lt;p&gt;It is a "boring tool that just works," which in developer language is the highest possible compliment.&lt;/p&gt;

&lt;p&gt;What it does is beautifully simple. You give it a file, and it hands you back clean Markdown. That is it. That is the whole trick.&lt;/p&gt;

&lt;p&gt;And by "a file" I mean almost anything you can throw at it: PDFs, Word docs, Excel sheets, PowerPoint decks, images, audio, HTML, and yes, even YouTube links, which it happily turns into a transcript. &lt;/p&gt;

&lt;p&gt;It is a bit of a "convert anything to Markdown" Swiss Army knife.&lt;/p&gt;

&lt;p&gt;Why does Markdown help so much? Two reasons, and both matter.&lt;/p&gt;

&lt;p&gt;First, Markdown is featherweight. It keeps the structure that carries meaning (headings, lists, tables, links) and throws out the formatting fluff that does not. Less noise, fewer tokens, up to around 70 percent lighter in the good cases. Your mileage will vary, but the direction is always down.&lt;/p&gt;

&lt;p&gt;Second, and this is the part people sleep on, models actually read Markdown better. &lt;/p&gt;

&lt;p&gt;Mainstream LLMs were trained on mountains of Markdown and speak it natively. &lt;/p&gt;

&lt;p&gt;They will even reach for it in their own answers when you did not ask them to. &lt;/p&gt;

&lt;p&gt;So you are not just spending fewer tokens, you are handing the model its favorite format on a silver platter.&lt;/p&gt;

&lt;p&gt;It is a genuine handshake moment.&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%2Fuaoxk0ezh41zb7dsyyol.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%2Fuaoxk0ezh41zb7dsyyol.png" alt=" " width="360" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mark my words, once you see the format click, you will not want to go back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The genuinely cool part: the MCP server
&lt;/h2&gt;

&lt;p&gt;Here is where it stops being a manual chore.&lt;/p&gt;

&lt;p&gt;MarkItDown ships an MCP server (the little &lt;code&gt;markitdown-mcp&lt;/code&gt; package). &lt;/p&gt;

&lt;p&gt;Wire it into any MCP-capable client (desktop assistants, editors like Cursor and VS Code, and friends), and instead of you converting files by hand, you just point the model at a file path or a URL and it reaches for the converter itself. &lt;/p&gt;

&lt;p&gt;Under the hood it exposes a single tool, &lt;code&gt;convert_to_markdown(uri)&lt;/code&gt;, and the model knows when to call it.&lt;/p&gt;

&lt;p&gt;So your flow goes from "export, convert, re-upload, sigh" to "hey, read this report and summarize it." &lt;/p&gt;

&lt;p&gt;The conversion happens in the middle, quietly, without you babysitting it.&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%2Fvkfl3izyzeqdns35j8pn.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%2Fvkfl3izyzeqdns35j8pn.png" alt=" " width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Setup is about a two-minute job. You install the package with pip, drop a small block into your client's MCP config, and then fully quit and reopen the app. &lt;/p&gt;

&lt;p&gt;That last step trips people up constantly, so let me say it loudly: the MCP server only loads on a clean launch. &lt;/p&gt;

&lt;p&gt;Closing the window is not quitting. Cmd or Ctrl plus Q, then reopen. Otherwise you will sit there wondering why nothing happened.&lt;/p&gt;

&lt;p&gt;One honest note. This auto-magic only kicks in on clients that support MCP. &lt;/p&gt;

&lt;p&gt;If you are in a plain browser chat, you can still get all the savings, you just run the file through MarkItDown yourself first and paste the Markdown in. Slightly more manual, same payoff.&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%2F7dtqu32qwaz5mjfotmro.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%2F7dtqu32qwaz5mjfotmro.png" alt=" " width="360" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The tl;dr
&lt;/h2&gt;

&lt;p&gt;You have been paying a formatting tax on every file you feed your model, and Microsoft handed out the coupon months ago. &lt;/p&gt;

&lt;p&gt;Convert to Markdown first, spend a fraction of the tokens, and get answers in the format the model likes best. &lt;/p&gt;

&lt;p&gt;Set up the MCP server once and it mostly disappears into the background.&lt;/p&gt;

&lt;p&gt;Two minutes of setup for up to 70 percent fewer tokens is the kind of trade I take every single time.&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%2F1ecbh3a0vf55vyijxve2.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%2F1ecbh3a0vf55vyijxve2.png" alt=" " width="360" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to poke at it yourself, here is where to start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The main repo: &lt;a href="https://github.com/microsoft/markitdown" rel="noopener noreferrer"&gt;markitdown&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The MCP server for the auto-convert trick: &lt;a href="https://github.com/microsoft/markitdown/tree/main/packages/markitdown-mcp" rel="noopener noreferrer"&gt;markitdown/tree/main/packages/markitdown-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Go convert something. Your token budget has suffered enough.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you tried this and it saved you a pile of tokens tell me in the comments. I read them.&lt;/em&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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Nobody Painted That Rectangle Black</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Thu, 16 Jul 2026 16:54:20 +0000</pubDate>
      <link>https://dev.to/lovestaco/nobody-painted-that-rectangle-black-a3</link>
      <guid>https://dev.to/lovestaco/nobody-painted-that-rectangle-black-a3</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You have done this. &lt;/p&gt;

&lt;p&gt;Netflix is playing, something on screen is worth sharing, you hit screenshot, and you get a black rectangle where the video was. &lt;/p&gt;

&lt;p&gt;The play button is there. The scrubber is there. &lt;/p&gt;

&lt;p&gt;The actual show? Gone.&lt;/p&gt;

&lt;p&gt;Same deal with screen recording, except weirder. &lt;/p&gt;

&lt;p&gt;You are staring directly at the video while it plays, perfectly visible, in full color, and the saved recording comes out black. &lt;/p&gt;

&lt;p&gt;Your eyes say one thing. The file says another.&lt;/p&gt;

&lt;p&gt;Most of us file this under "Netflix blocked it" and move on with our lives.&lt;br&gt;
But the mechanism underneath is stranger and honestly more elegant than "blocked."&lt;/p&gt;

&lt;p&gt;So let's go layer by layer. &lt;/p&gt;
&lt;h2&gt;
  
  
  The mental model I had is wrong
&lt;/h2&gt;

&lt;p&gt;Here is what I assumed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I press screenshot&lt;/li&gt;
&lt;li&gt;Something detects it&lt;/li&gt;
&lt;li&gt;That something paints black over the protected area&lt;/li&gt;
&lt;li&gt;I get a censored image&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Reasonable. Also completely wrong. &lt;/p&gt;

&lt;p&gt;Nothing detects anything. Nothing paints anything.&lt;/p&gt;

&lt;p&gt;Sit with that, because the rest of this post falls out of it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two consumers, two composites
&lt;/h2&gt;

&lt;p&gt;Here is the load-bearing idea, and it took me embarrassingly long to arrive at it: &lt;strong&gt;the screen and the screen recorder are not looking at the same image.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I had assumed there was one framebuffer, one canonical "what the phone currently looks like," and that a screenshot was simply a copy of it. &lt;/p&gt;

&lt;p&gt;Nope. &lt;a href="https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager" rel="noopener noreferrer"&gt;SurfaceFlinger&lt;/a&gt;, Android's compositor, does not render one image that everybody shares. &lt;/p&gt;

&lt;p&gt;It renders a &lt;strong&gt;separate composite per output destination.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The physical panel is one destination. &lt;/p&gt;

&lt;p&gt;The screenshot service is another. A cast session is a third.&lt;/p&gt;

&lt;p&gt;For each destination, SurfaceFlinger walks the layer list and asks exactly one question per layer: does this layer belong in &lt;em&gt;this&lt;/em&gt; output? For the panel, the answer is always yes. &lt;/p&gt;

&lt;p&gt;For the screenshot output, any layer carrying &lt;code&gt;FLAG_SECURE&lt;/code&gt; gets skipped.&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%2Fl2c0uyjjkigrchy03uji.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%2Fl2c0uyjjkigrchy03uji.png" alt=" " width="529" height="749"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the video layer is never &lt;em&gt;removed&lt;/em&gt; from the screenshot if its is never &lt;em&gt;added&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why it is black, specifically
&lt;/h2&gt;

&lt;p&gt;This is my favorite part.&lt;/p&gt;

&lt;p&gt;The capture buffer gets allocated and zeroed. &lt;/p&gt;

&lt;p&gt;Normal layers write into it. The secure layer does not. Then the buffer gets encoded and handed over.&lt;/p&gt;

&lt;p&gt;Zeros are black.&lt;/p&gt;

&lt;p&gt;That is the whole thing. Nobody chose black. &lt;/p&gt;

&lt;p&gt;There is no &lt;code&gt;paintItBlack()&lt;/code&gt; sitting in AOSP. &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%2Fy2qqahqtey36iqfj060p.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%2Fy2qqahqtey36iqfj060p.png" alt=" " width="360" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Black is simply what untouched memory looks like when you interpret it as pixels. The rectangle is not censored, it is &lt;strong&gt;vacant&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;It is the absence of a decision.&lt;/p&gt;

&lt;p&gt;This also explains something I had seen a hundred times and never once registered: the black is &lt;em&gt;exactly&lt;/em&gt; the video's shape, and the UI around it survives just fine. &lt;/p&gt;

&lt;p&gt;Play button, scrubber, title text, all present. &lt;/p&gt;

&lt;p&gt;Those are separate layers. Only one of them had the flag.&lt;/p&gt;
&lt;h2&gt;
  
  
  My bank is doing this too
&lt;/h2&gt;

&lt;p&gt;So I went and screenshotted my banking app. &lt;/p&gt;

&lt;p&gt;Same black frame. Password manager, same. &lt;/p&gt;

&lt;p&gt;Whatsapp one view image screenshot? same.&lt;/p&gt;

&lt;p&gt;Here is the entire implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;getWindow&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;setFlags&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;WindowManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;LayoutParams&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;FLAG_SECURE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;WindowManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;LayoutParams&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;FLAG_SECURE&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;One call. Thirty seconds of work. That is the feature, shipped.&lt;/p&gt;
&lt;h2&gt;
  
  
  Netflix is playing an entirely different game
&lt;/h2&gt;

&lt;p&gt;Everything so far is a &lt;strong&gt;policy&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;SurfaceFlinger &lt;em&gt;chooses&lt;/em&gt; to honor &lt;code&gt;FLAG_SECURE&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The pixels sit in perfectly ordinary memory the whole time. &lt;/p&gt;

&lt;p&gt;The compositor is just politely declining to copy them.&lt;/p&gt;

&lt;p&gt;Which means: root the phone, patch SurfaceFlinger, flag ignored, banking screenshots work again. The whole protection is a gentleman's agreement enforced by code that the root user owns.&lt;/p&gt;

&lt;p&gt;Netflix HD is not that.&lt;/p&gt;

&lt;p&gt;With Widevine L1, decoded frames live inside the TEE, a separate secure world running on the same SoC. &lt;/p&gt;

&lt;p&gt;The decrypted video never lands in memory the application processor can address. &lt;/p&gt;

&lt;p&gt;It goes from the TEE into a protected buffer, gets composited, reaches your eyeballs, and the kernel never touches a single pixel of it.&lt;/p&gt;

&lt;p&gt;There is no code to patch, because it is not code. &lt;/p&gt;

&lt;p&gt;It is the memory controller saying no, in silicon xD&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Enforced by&lt;/th&gt;
&lt;th&gt;Defeated by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Banking app&lt;/td&gt;
&lt;td&gt;&lt;code&gt;FLAG_SECURE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The OS, voluntarily&lt;/td&gt;
&lt;td&gt;Root&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Netflix HD&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;FLAG_SECURE&lt;/code&gt; + L1 secure buffers&lt;/td&gt;
&lt;td&gt;The chip itself&lt;/td&gt;
&lt;td&gt;Nothing on the device&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flgflafbz51jhwptrz1e8.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%2Flgflafbz51jhwptrz1e8.png" alt=" " width="360" height="312"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  So does rooting actually work?
&lt;/h2&gt;

&lt;p&gt;Here is where I stop explaining and start asking.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;FLAG_SECURE&lt;/code&gt;, I think yes. It is policy, and root owns the policy. Patch SurfaceFlinger, stop honoring the flag, banking screenshots come back.&lt;/p&gt;

&lt;p&gt;Netflix HD is where I run out of road. The frames are in the TEE. Root gets you the kernel, and the kernel is not the thing saying no. So what actually comes out the other end?&lt;/p&gt;

&lt;p&gt;I have not tried it. No spare handset to brick.&lt;/p&gt;

&lt;p&gt;So has anyone actually done this? Flashaholics, custom ROM people what did you get?&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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>android</category>
      <category>security</category>
      <category>mobile</category>
      <category>programming</category>
    </item>
    <item>
      <title>/proc, pkexec, and 678 commits</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Wed, 15 Jul 2026 18:12:51 +0000</pubDate>
      <link>https://dev.to/lovestaco/proc-pkexec-and-678-commits-1gig</link>
      <guid>https://dev.to/lovestaco/proc-pkexec-and-678-commits-1gig</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Day 1 of Qt on Linux. I installed Stacer, opened the source, and found a few things worth stealing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every Qt tutorial builds a calculator or a todo list. &lt;/p&gt;

&lt;p&gt;Those teach you the API. &lt;/p&gt;

&lt;p&gt;They don't teach you how an app is &lt;em&gt;shaped&lt;/em&gt;,  where the OS code goes, where the root code goes, what holds twelve screens together.&lt;/p&gt;

&lt;p&gt;So I went looking for a real app to read. &lt;/p&gt;

&lt;p&gt;I found &lt;a href="https://github.com/oguzhaninan/Stacer" rel="noopener noreferrer"&gt;Stacer&lt;/a&gt;, a Linux system optimizer, installed it, poked every button, then opened the source.&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%2Fwjegk132kygmq5cvbx3e.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%2Fwjegk132kygmq5cvbx3e.png" alt=" " width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  It's abandoned, which is why it's good
&lt;/h2&gt;

&lt;p&gt;Last commit August 2023, README says no further releases. 678 commits and then silence.&lt;/p&gt;

&lt;p&gt;Which is perfect for reading.&lt;/p&gt;

&lt;p&gt;Nothing moves under you, there's no "actually we refactored that last week," and at ~50 headers, 48 sources and 26 &lt;code&gt;.ui&lt;/code&gt; forms you can read all of it in an evening or two.&lt;/p&gt;

&lt;p&gt;A finished project sits still long enough to dissect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision everything else hangs off
&lt;/h2&gt;

&lt;p&gt;I read the build files first. &lt;/p&gt;

&lt;p&gt;The top level &lt;code&gt;CMakeLists.txt&lt;/code&gt; gives away the architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cmake"&gt;&lt;code&gt;&lt;span class="nb"&gt;add_subdirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;stacer-core&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;add_subdirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;stacer&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then from &lt;code&gt;stacer-core/CMakeLists.txt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cmake"&gt;&lt;code&gt;&lt;span class="nb"&gt;find_package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;Qt5 COMPONENTS Core Network REQUIRED&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;add_library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; STATIC &lt;span class="si"&gt;${${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nv"&gt;_srcs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;target_link_libraries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; Qt5::Core Qt5::Network&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;stacer-core&lt;/code&gt; is a static lib with no GUI components linked at all. &lt;/p&gt;

&lt;p&gt;No Widgets, no Charts, nothing that can draw a pixel.&lt;/p&gt;

&lt;p&gt;It's the whole "talk to the OS" layer, and it would happily compile into a headless CLI. &lt;/p&gt;

&lt;p&gt;The app links it plus Widgets, Charts, Svg and Concurrent on top.&lt;/p&gt;

&lt;p&gt;Most codebases claim a separation like this and then someone sneaks a &lt;code&gt;QMessageBox&lt;/code&gt; into the data layer. Stacer can't. The linker would slap it.&lt;/p&gt;

&lt;p&gt;The core splits three ways by verb:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Info/&lt;/code&gt; reads system state&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Tools/&lt;/code&gt; changes system state&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Utils/&lt;/code&gt; is the primitives both are built on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read, write, plumbing. I never had to grep for where anything lived, which in an unfamiliar codebase is a small miracle.&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%2Fkrkligtnj1uqzvx37132.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%2Fkrkligtnj1uqzvx37132.png" alt=" " width="742" height="1123"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every arrow points down.&lt;/p&gt;
&lt;h2&gt;
  
  
  /proc and roll
&lt;/h2&gt;

&lt;p&gt;How does a system monitor monitor the system? I assumed a library, or a daemon, something official looking.&lt;/p&gt;

&lt;p&gt;It reads text files.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#define PROC_CPUINFO "/proc/cpuinfo"
#define PROC_LOADAVG "/proc/loadavg"
#define PROC_STAT    "/proc/stat"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Core count: read &lt;code&gt;/proc/cpuinfo&lt;/code&gt;, filter &lt;code&gt;^processor&lt;/code&gt;, count the lines.&lt;/p&gt;

&lt;p&gt;Load average: read &lt;code&gt;/proc/loadavg&lt;/code&gt;, split on whitespace, take the first three.&lt;/p&gt;

&lt;p&gt;Clock speed: filter &lt;code&gt;^cpu MHz&lt;/code&gt;, split on the colon. &lt;/p&gt;

&lt;p&gt;The CPU section of a system monitor is a regex over a text file. And &lt;a href="https://man7.org/linux/man-pages/man5/proc.5.html" rel="noopener noreferrer"&gt;procfs&lt;/a&gt; is how everything on Linux exposes itself, so once you notice you can't stop.&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%2F5v76iykfudpt6xwczwqw.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%2F5v76iykfudpt6xwczwqw.png" alt=" " width="360" height="270"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The bit that broke my brain
&lt;/h2&gt;

&lt;p&gt;So CPU usage should be the same, right? Find the line, take the number.&lt;/p&gt;

&lt;p&gt;There is no number.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/proc/stat&lt;/code&gt; gives you cumulative tick counters since boot — ticks spent in user mode, system, idle, iowait, ever.&lt;/p&gt;

&lt;p&gt;They only go up. &lt;/p&gt;

&lt;p&gt;CPU usage isn't a value you can read. &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%2Fxgu1apf8ue50gva6pej7.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%2Fxgu1apf8ue50gva6pej7.png" alt=" " width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It only exists as a relationship between two samples: read the counters, wait, read again, and the shape of the change is the answer.&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%2Fnhyhlu8o11ty4tp4j2ja.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%2Fnhyhlu8o11ty4tp4j2ja.png" alt=" " width="360" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From &lt;code&gt;cpu_info.cpp&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;CpuInfo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;getCpuPercent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;QList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cpuTimes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getCpuCoreCount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;QVector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;l_idles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;QVector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;l_totals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;utilisation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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="n"&gt;cpuTimes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;idle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cpuTimes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;cpuTimes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// idle + iowait&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cpuTimes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;idle_delta&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;idle&lt;/span&gt;  &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;l_idles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;total_delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;l_totals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;processor&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="n"&gt;total_delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;utilisation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;total_delta&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;idle_delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total_delta&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;l_idles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;idle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;l_totals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// ...clamped to 0..100&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;utilisation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Those &lt;code&gt;static&lt;/code&gt; locals. The function remembers: it stashes the previous idle and total per processor, diffs against them, overwrites them for next time.&lt;/p&gt;

&lt;p&gt;Which makes an innocent looking getter stateful. &lt;/p&gt;

&lt;p&gt;Call it twice in a row from two places and the second caller gets garbage, because the first one already consumed the delta. &lt;/p&gt;

&lt;p&gt;The first call after launch is meaningless by definition.&lt;/p&gt;

&lt;p&gt;Not a dunk — it's the correct algorithm, it's what &lt;code&gt;top&lt;/code&gt; does, and the comment above it pastes in the &lt;code&gt;/proc/stat&lt;/code&gt; column meanings for the next reader. &lt;/p&gt;

&lt;p&gt;But it's a landmine, and it explains something further up the stack.&lt;/p&gt;

&lt;p&gt;The refresh loop is refreshingly dumb. From &lt;code&gt;dashboard_page.cpp&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mTimer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;QTimer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;timeout&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;DashboardPage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;updateCpuBar&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mTimer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;QTimer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;timeout&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;DashboardPage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;updateMemoryBar&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mTimer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;QTimer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;timeout&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;DashboardPage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;updateNetworkBar&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;QTimer&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;timerDisk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;QTimer&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="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timerDisk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;QTimer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;timeout&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;DashboardPage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;updateDiskBar&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;timerDisk&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;mTimer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;One timer at 1s fanned out to three slots, another at 5s for disk because disk changes slowly and checking it is expensive. &lt;/p&gt;

&lt;p&gt;Nothing pushes; the UI pulls on a clock. That's the whole update architecture.&lt;/p&gt;
&lt;h2&gt;
  
  
  The smartest thing in here
&lt;/h2&gt;

&lt;p&gt;Stacer kills processes, purges caches, toggles systemd services, uninstalls packages, edits &lt;code&gt;/etc/hosts&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;All of it needs root. Stacer does not run as root.&lt;/p&gt;

&lt;p&gt;From &lt;code&gt;command_util.cpp&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;QString&lt;/span&gt; &lt;span class="n"&gt;CommandUtil&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;sudoExec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;QString&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;QStringList&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;QByteArray&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push_front&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;QString&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CommandUtil&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pkexec"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QString&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;qCritical&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;ex&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="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's the function. Shove the command onto the front of the args, run &lt;a href="https://www.freedesktop.org/software/polkit/docs/latest/pkexec.1.html" rel="noopener noreferrer"&gt;&lt;code&gt;pkexec&lt;/code&gt;&lt;/a&gt; instead. &lt;/p&gt;

&lt;p&gt;polkit pops the auth dialog, tells the user which command wants elevation, and runs that one command as root.&lt;/p&gt;

&lt;p&gt;So the app process is unprivileged for its entire life — a bug in the theming code can't &lt;code&gt;rm -rf&lt;/code&gt; your home directory. &lt;/p&gt;

&lt;p&gt;Privilege is granted per operation, at the moment of use, not once at launch for everything forever. And &lt;code&gt;pkexec kill 1337&lt;/code&gt; is a more honest dialog than "Stacer would like admin access, kthx."&lt;/p&gt;

&lt;p&gt;Sixteen &lt;code&gt;sudoExec&lt;/code&gt; call sites, sixteen places the user gets asked. &lt;/p&gt;

&lt;p&gt;The alternative you see everywhere is a &lt;code&gt;.desktop&lt;/code&gt; file that launches the whole GUI under sudo, so now your event loop and your stylesheet parser are root, forever, for the sake of one &lt;code&gt;rm&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%2Fxbs233agcpemk966ugih.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%2Fxbs233agcpemk966ugih.png" alt=" " width="800" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Tools/package_tool.cpp&lt;/code&gt; sits on top of this: same intent, dispatched to &lt;code&gt;apt-get&lt;/code&gt;, &lt;code&gt;dnf&lt;/code&gt;, &lt;code&gt;yum&lt;/code&gt;, &lt;code&gt;pacman&lt;/code&gt; or &lt;code&gt;snap&lt;/code&gt; depending on the machine. &lt;/p&gt;

&lt;p&gt;The UI just says "uninstall", and the packaging ecosystem stays on the other side of the wall.&lt;/p&gt;
&lt;h2&gt;
  
  
  Twelve screens
&lt;/h2&gt;

&lt;p&gt;Each page is a directory under &lt;code&gt;Pages/&lt;/code&gt; with the Qt triple: &lt;code&gt;.h&lt;/code&gt;, &lt;code&gt;.cpp&lt;/code&gt;, &lt;code&gt;.ui&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;App&lt;/code&gt; news them all up at startup into a &lt;code&gt;SlidingStackedWidget&lt;/code&gt;, a &lt;code&gt;QStackedWidget&lt;/code&gt; subclass that animates transitions instead of hard cutting. Small thing, and most of why the app feels nicer than its feature list.&lt;/p&gt;

&lt;p&gt;Best part: two pages don't always exist.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// APT SOURCE MANAGER&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ToolManager&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ins&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;checkSourceRepository&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;aptSourceManagerPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;APTSourceManagerPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mSlidingStacked&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;mListPages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aptSourceManagerPage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;mListSidebarButtons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;btnAptSourceManager&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;btnAptSourceManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;An APT source manager on Arch is nonsense, so it checks for APT config and doesn't build the page.&lt;/p&gt;

&lt;p&gt;Same for Gnome settings, which checks &lt;code&gt;DESKTOP_SESSION&lt;/code&gt; and the distro string. &lt;/p&gt;

&lt;p&gt;One binary reshaping itself around the machine it woke up on, hiding the button rather than showing you something broken.&lt;/p&gt;
&lt;h2&gt;
  
  
  Theming, or: CSS variables, hand rolled
&lt;/h2&gt;

&lt;p&gt;QSS looks like CSS and lacks most of what makes CSS bearable. Notably, no variables.&lt;/p&gt;

&lt;p&gt;So Stacer built them. Each theme is a &lt;code&gt;style.qss&lt;/code&gt; plus a &lt;code&gt;values.ini&lt;/code&gt;, both baked into the Qt resource system, and &lt;code&gt;AppManager::updateStylesheet&lt;/code&gt; does this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;QString&lt;/span&gt; &lt;span class="n"&gt;appThemePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":/static/themes/%1/style"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mSettingManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;getThemeName&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="n"&gt;mStyleValues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;QSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%1/values.ini"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appThemePath&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;QSettings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;IniFormat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;mStylesheetFileContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FileUtil&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;readStringFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%1/style.qss"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appThemePath&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// set values example: @color01 =&amp;gt; #fff&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;QString&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mStyleValues&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;allKeys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mStylesheetFileContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mStyleValues&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;qApp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setStyleSheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mStylesheetFileContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;emit&lt;/span&gt; &lt;span class="n"&gt;SignalMapper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ins&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;sigChangedAppTheme&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Read the stylesheet, string replace every &lt;code&gt;@color01&lt;/code&gt; with &lt;code&gt;#fff&lt;/code&gt; from the ini, apply globally. &lt;/p&gt;

&lt;p&gt;A preprocessor in nine lines. Swapping themes is pointing at a different folder.&lt;/p&gt;

&lt;p&gt;Two details worth stealing. Widgets opt into styling with &lt;code&gt;setAccessibleName("danger")&lt;/code&gt; and the QSS selects on it — an accessibility field as a CSS class, which is either beautiful or slightly rude depending on your mood, but it's a known Qt move and it works.&lt;/p&gt;

&lt;p&gt;And that &lt;code&gt;emit SignalMapper::ins()-&amp;gt;sigChangedAppTheme()&lt;/code&gt; at the end. &lt;code&gt;SignalMapper&lt;/code&gt; is a global QObject that exists purely as a signal bus. &lt;/p&gt;

&lt;p&gt;No logic, three signals:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="nl"&gt;signals:&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;sigChangedAppTheme&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;sigUninstallStarted&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;sigUninstallFinished&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Widgets that paint themselves (the circle bars, the charts) can't get colors from a stylesheet, so they need to know when the theme changed and re-read. &lt;/p&gt;

&lt;p&gt;Without the bus, &lt;code&gt;AppManager&lt;/code&gt; needs a pointer to every widget that cares. &lt;/p&gt;

&lt;p&gt;With it, it shouts into the void and whoever cares listens. Good signal to noise ratio, and I'll see myself out.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I'm stealing
&lt;/h2&gt;

&lt;p&gt;Split the OS layer into a target that can't link Widgets — the linker enforces the boundary better than my discipline does. &lt;/p&gt;

&lt;p&gt;Split that layer by verb.&lt;/p&gt;

&lt;p&gt;Never run the whole app as root; &lt;code&gt;pkexec&lt;/code&gt; per command is four lines and it's the difference between a bug and a catastrophe.&lt;/p&gt;

&lt;p&gt;Poll with &lt;code&gt;QTimer&lt;/code&gt;, two rates for two costs, ship it.&lt;/p&gt;

&lt;p&gt;QSS plus a token file gives you variables. A signal bus beats N pointers when a global thing changes and unknown widgets care. And know when a singleton is load bearing versus lazy, in here it's holding the delta math together, and it's also why nothing is testable.&lt;/p&gt;

&lt;p&gt;If you want to trace one thing end to end, do the CPU percentage. &lt;/p&gt;

&lt;p&gt;Timer fires in &lt;code&gt;dashboard_page.cpp&lt;/code&gt;, through &lt;code&gt;InfoManager::getCpuPercents&lt;/code&gt;, into &lt;code&gt;CpuInfo::getCpuPercents&lt;/code&gt;, which reads &lt;code&gt;/proc/stat&lt;/code&gt; as text, diffs it against static state from a second ago, and hands a number to a hand painted &lt;code&gt;CircleBar&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Every layer of the app in one number, once a second, on a project nobody maintains anymore.&lt;/p&gt;

&lt;p&gt;Next I want to build something with &lt;code&gt;SlidingStackedWidget&lt;/code&gt;, and work out whether the &lt;code&gt;.ui&lt;/code&gt; files are worth it or whether I should just write layouts by hand. &lt;/p&gt;

&lt;p&gt;If you've got opinions, I'd love to hear them, because right now I have two and they're both wrong xD&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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>programming</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>linux</category>
    </item>
    <item>
      <title>A Dash of dev.to: My Blog Stats Now Live in the Terminal</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Tue, 14 Jul 2026 19:38:18 +0000</pubDate>
      <link>https://dev.to/lovestaco/a-dash-of-devto-my-blog-stats-now-live-in-the-terminal-4l4e</link>
      <guid>https://dev.to/lovestaco/a-dash-of-devto-my-blog-stats-now-live-in-the-terminal-4l4e</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I check my dev.to stats more often than I would ever admit in a job interview. &lt;/p&gt;

&lt;p&gt;Reactions, views, comments, the little numbers that go up, and the ones that stubbornly refuse to. &lt;/p&gt;

&lt;p&gt;Normally that ritual means opening a browser, clicking into the dashboard, and squinting at one article at a time like I'm reading tea leaves.&lt;/p&gt;

&lt;p&gt;I wanted something calmer. &lt;/p&gt;

&lt;p&gt;One terminal window. &lt;/p&gt;

&lt;p&gt;My top articles ranked by likes, by views, and by comments, all on screen at once, quietly refreshing itself while I pretend to do real work.&lt;/p&gt;

&lt;p&gt;So I built it in an afternoon. &lt;/p&gt;

&lt;p&gt;It took a tiny Go program, one wonderful archived project doing the heavy lifting, and two bugs I absolutely did not order but got served anyway.&lt;/p&gt;

&lt;p&gt;Let me walk you through it, bugs and all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The plan (which looked suspiciously simple)
&lt;/h2&gt;

&lt;p&gt;The plan had three moving parts.&lt;/p&gt;

&lt;p&gt;One, dev.to already hands you your own data. &lt;/p&gt;

&lt;p&gt;There is an endpoint, &lt;code&gt;GET /api/articles/me&lt;/code&gt;, you send your API key in an &lt;code&gt;api-key&lt;/code&gt; header, and you get back every article you have published with the fields that matter already counted for you: &lt;code&gt;positive_reactions_count&lt;/code&gt;, &lt;code&gt;page_views_count&lt;/code&gt;, and &lt;code&gt;comments_count&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;No scraping, no HTML parsing, no crying. &lt;/p&gt;

&lt;p&gt;You can generate a key at your dev.to settings under Extensions.&lt;/p&gt;

&lt;p&gt;Two, I did not want to build a whole TUI from scratch. &lt;/p&gt;

&lt;p&gt;Grids, colors, borders, keyboard handling, refresh loops. Life is short xD&lt;/p&gt;

&lt;p&gt;Three, therefore, I needed something that already draws pretty terminal dashboards and would happily show my numbers if I fed it nicely.&lt;/p&gt;

&lt;p&gt;That third thing exists, and it is called devdash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter devdash (and a well earned shoutout)
&lt;/h2&gt;

&lt;p&gt;{ % embed &lt;a href="https://github.com/Phantas0s/devdash" rel="noopener noreferrer"&gt;https://github.com/Phantas0s/devdash&lt;/a&gt; %}&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Phantas0s/devdash" rel="noopener noreferrer"&gt;devdash&lt;/a&gt; is a highly configurable terminal dashboard by Matthieu Cneude, better known as Phantas0s. &lt;/p&gt;

&lt;p&gt;It has widgets for GitHub, Google Analytics, Google Search Console, Travis, and more, and the entire layout is driven by a single YAML file. &lt;/p&gt;

&lt;p&gt;Rows, columns, t-shirt sizes, colors, all declarative. &lt;/p&gt;

&lt;p&gt;It is a genuinely lovely piece of Go.&lt;/p&gt;

&lt;p&gt;Two small catches. It has been archived since 2023. And it has precisely zero concept of what dev.to is.&lt;/p&gt;

&lt;p&gt;Now, I could have forked it, added a proper Forem service, wired up structs, written tests, and opened a pull request into a repo that is politely closed for business. &lt;/p&gt;

&lt;p&gt;Instead I found the lazy door, and it was already unlocked.&lt;/p&gt;

&lt;p&gt;devdash has a widget called &lt;code&gt;lh.table&lt;/code&gt;, the localhost table. &lt;/p&gt;

&lt;p&gt;You hand it a shell command, it runs that command, and it renders whatever the command prints as a bordered table. &lt;/p&gt;

&lt;p&gt;It splits each line of output on whitespace and slots the pieces into columns. That is the whole contract.&lt;/p&gt;

&lt;p&gt;So devdash does not need to know about dev.to. &lt;/p&gt;

&lt;p&gt;It just needs a command that prints rows. I can be that command.&lt;/p&gt;

&lt;p&gt;If Matthieu ever reads this: thank you for building a tool flexible enough to be abused this gracefully. &lt;/p&gt;

&lt;p&gt;Go bother &lt;a href="https://x.com/Cneude_Matthieu" rel="noopener noreferrer"&gt;Matthieu on X&lt;/a&gt; and tell him his archived project is still out here pulling shifts.&lt;/p&gt;

&lt;p&gt;Here is the shape of the whole thing.&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%2F5zqtj05d58f57kofef66.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%2F5zqtj05d58f57kofef66.png" alt=" " width="800" height="134"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The little Go binary in the middle is the only thing I actually had to write. &lt;/p&gt;

&lt;p&gt;I called it &lt;code&gt;devto-stats&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;It fetches all my articles (paginating 100 at a time until dev.to runs out), keeps only the published ones, sorts them by whichever metric devdash asks for, and prints clean rows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;// GET https://dev.to/api/articles/me?per_page=100&amp;amp;page=N&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And the config just points three table widgets at three flavors of that command.&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="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;lh.table&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;MOST&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;VIEWED&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./bin/devto-stats&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-mode=table&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-sort=views&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-limit=10"&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#,Article,Views"&lt;/span&gt;
    &lt;span class="na"&gt;border_color&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;green&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Three of those blocks, colored red, green, and yellow, sitting side by side. In theory, done.&lt;/p&gt;

&lt;p&gt;In theory.&lt;/p&gt;
&lt;h2&gt;
  
  
  my dashboard tried to DDoS my own account xD
&lt;/h2&gt;

&lt;p&gt;I turned it on. Overview strip populated. Most Liked, glorious. Most Commented, present. &lt;/p&gt;

&lt;p&gt;Most Viewed, a bright red ERROR box.&lt;/p&gt;

&lt;p&gt;The command worked perfectly by hand. &lt;/p&gt;

&lt;p&gt;It only failed inside devdash.&lt;/p&gt;

&lt;p&gt;The clue was in how devdash refreshes: it fires every widget concurrently, each in its own goroutine, all at the same instant. &lt;/p&gt;

&lt;p&gt;Four widgets meant four copies of &lt;code&gt;devto-stats&lt;/code&gt; sprinting at the dev.to API at once, elbows out. dev.to did the sensible thing and replied &lt;code&gt;429 Too Many Requests&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;One of the four always lost the race, usually Most Viewed.&lt;/p&gt;

&lt;p&gt;I had built a very small, very polite denial of service attack against myself.&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%2Fykw3706yjer7mrzoxxya.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%2Fykw3706yjer7mrzoxxya.png" alt=" " width="360" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fix is the boring, correct one: on a 429, wait and retry, with backoff plus jitter.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusTooManyRequests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;backoff&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;700&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt;
    &lt;span class="n"&gt;jitter&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Intn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;backoff&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;jitter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;continue&lt;/span&gt; &lt;span class="c"&gt;// try this page again&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The jitter is the important part. Without it, all four back off by the same amount and collide again, like four people apologizing and stepping the same way in a hallway. &lt;/p&gt;

&lt;p&gt;With a random offset, they spread out on their own. &lt;/p&gt;

&lt;p&gt;Zero errors after that.&lt;/p&gt;
&lt;h2&gt;
  
  
  a comma walked into my table and everything fell over
&lt;/h2&gt;

&lt;p&gt;Most Viewed finally rendered. And it rendered wrong. &lt;/p&gt;

&lt;p&gt;The first row looked fine, then every row below slid one column right. &lt;/p&gt;

&lt;p&gt;Titles in the numbers column, numbers nowhere.&lt;/p&gt;

&lt;p&gt;I recognized the article where it started. &lt;/p&gt;

&lt;p&gt;My top post is titled "Good Bye CRUD APIs, Hello Sync". &lt;/p&gt;

&lt;p&gt;Look at the punctuation.&lt;/p&gt;

&lt;p&gt;Here's what devdash does under the hood: it splits each line on whitespace to get cells, joins those cells back together with commas, then splits the whole batch on commas again to chunk it into rows of N columns. &lt;/p&gt;

&lt;p&gt;A comma inside a title is indistinguishable from a comma devdash added on purpose. &lt;/p&gt;

&lt;p&gt;One title becomes two cells, the row has four pieces instead of three, and because the chunking is global, every row after it is shifted forever.&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%2Fd22xcaocgpyx27o0cr0r.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%2Fd22xcaocgpyx27o0cr0r.png" alt=" " width="281" height="950"&gt;&lt;/a&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%2Fr4qcgk9sqnabgf3q0a25.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%2Fr4qcgk9sqnabgf3q0a25.png" alt=" " width="360" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The real fix belongs upstream, in a repo that isn't taking visitors. &lt;/p&gt;

&lt;p&gt;So I fixed it on my side, where I control the output. &lt;/p&gt;

&lt;p&gt;My binary already slugifies titles to survive the whitespace split. &lt;/p&gt;

&lt;p&gt;I just taught it to evict commas too.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReplaceAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;","&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// devdash re-splits the table on commas&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;One line. The comma-tose table woke right up.&lt;/p&gt;
&lt;h2&gt;
  
  
  The payoff
&lt;/h2&gt;

&lt;p&gt;Here is the raw feed one panel runs on, straight out of the binary, real numbers from my actual account:&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%2Fw1n2nxlqpmpa6ojnuir2.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw1n2nxlqpmpa6ojnuir2.gif" alt=" " width="799" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;devdash takes three of those feeds and frames them into colored, bordered, self refreshing tables sitting shoulder to shoulder: Most Liked in red, Most Viewed in green, Most Commented in yellow, with a summary strip across the top. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;Ctrl+R&lt;/code&gt; forces a refresh, &lt;code&gt;Ctrl+C&lt;/code&gt; quits, and left to its own devices it repaints itself every five minutes.&lt;/p&gt;

&lt;p&gt;444 articles, all of them accounted for, no browser, no clicking, just a quiet terminal telling me the truth about which posts people actually read.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I actually learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The best integration is often no integration.&lt;/strong&gt; devdash never learned about dev.to. &lt;/p&gt;

&lt;p&gt;I made dev.to speak devdash's language instead, and a tool that stopped being maintained in 2023 rendered 2026 data without a single change to its source.&lt;/p&gt;

&lt;p&gt;The whole thing, Go helper, YAML, and Makefile, is here: &lt;a href="https://github.com/lovestaco/devto_devdash" rel="noopener noreferrer"&gt;lovestaco/devto_devdash&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Bring your own API key.&lt;/p&gt;

&lt;p&gt;And real gratitude to Matthieu Cneude for devdash. &lt;/p&gt;

&lt;p&gt;Sometimes the best tool for the job is one somebody stopped working on years ago, sitting there quietly, still perfectly happy to dash off one more dashboard.&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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
      <category>bash</category>
    </item>
    <item>
      <title>Fusuma: Write Markdown, Get Slides, PDFs, and a Self-Made Social Card</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Mon, 13 Jul 2026 12:22:29 +0000</pubDate>
      <link>https://dev.to/lovestaco/fusuma-write-markdown-get-slides-pdfs-and-a-self-made-social-card-4b2k</link>
      <guid>https://dev.to/lovestaco/fusuma-write-markdown-get-slides-pdfs-and-a-self-made-social-card-4b2k</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A fusuma (襖), if you didn't grow up around one, is the sliding door in a traditional Japanese house. &lt;/p&gt;

&lt;p&gt;No hinges, no swinging into someone's face, you just slide it and the room becomes a different room. &lt;/p&gt;

&lt;p&gt;So when hiroppy named his Markdown-to-slides tool &lt;code&gt;fusuma&lt;/code&gt;, the pun was already built into the product: you write one flat file, and sliding between sections turns it into a presentation. &lt;/p&gt;

&lt;p&gt;I see what you did there, and I respect it.&lt;/p&gt;

&lt;p&gt;I hadn't used fusuma before this week. &lt;/p&gt;

&lt;p&gt;I installed it, scaffolded a real project, wrote actual slides explaining fusuma using fusuma, ran the build pipeline, and then went spelunking through &lt;code&gt;node_modules&lt;/code&gt; because the CLI output during &lt;code&gt;build&lt;/code&gt; said something that made me stop and go "wait, it's doing &lt;em&gt;what&lt;/em&gt;." &lt;/p&gt;

&lt;p&gt;We'll get to that. First, the basics.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pitch: one file, no editor
&lt;/h2&gt;

&lt;p&gt;You install it, run one command, and you have a deck:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;fusuma &lt;span class="nt"&gt;-D&lt;/span&gt;
npx fusuma init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That scaffolds a &lt;code&gt;slides/&lt;/code&gt; folder, a &lt;code&gt;style.css&lt;/code&gt;, and a &lt;code&gt;.fusumarc.yml&lt;/code&gt; config. &lt;/p&gt;

&lt;p&gt;Your actual content lives in plain Markdown, and every &lt;code&gt;---&lt;/code&gt; on its own line starts a new slide. &lt;/p&gt;

&lt;p&gt;That's the entire authoring model. &lt;/p&gt;

&lt;p&gt;No slide objects, no proprietary file format, no clicking "insert text box" forty times.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- classes: title --&amp;gt;&lt;/span&gt;

&lt;span class="gh"&gt;# Hello😃&lt;/span&gt;
&lt;span class="p"&gt;
---
&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- section-title: Bye👋 --&amp;gt;&lt;/span&gt;

&lt;span class="gu"&gt;## Bye👋&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That HTML-comment syntax above &lt;code&gt;# Hello😃&lt;/code&gt; isn't decoration, it's a directive fusuma's parser reads to apply a &lt;code&gt;.title&lt;/code&gt; CSS class to that slide, and &lt;code&gt;section-title&lt;/code&gt; sets what shows up in the sidebar navigation. &lt;/p&gt;

&lt;p&gt;It's a clever trick: keep the file valid, renderable Markdown for anyone viewing it on GitHub, while smuggling presentation metadata through comments a plain Markdown renderer just ignores. &lt;/p&gt;

&lt;p&gt;Here's that exact idea rendered for real, from a four-slide deck I built (using fusuma) to explain fusuma, running the &lt;code&gt;pop&lt;/code&gt; theme:&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%2Fb5wyl1huc6o26jkyfdjs.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%2Fb5wyl1huc6o26jkyfdjs.png" alt="Slide 2: It's just a Markdown file" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you're happy with the content, &lt;code&gt;npx fusuma start&lt;/code&gt; gets you a dev server on &lt;code&gt;:8080&lt;/code&gt; that hot reloads on every save, no full page refresh, no build step in the way of your edit loop:&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="nv"&gt;$ &lt;/span&gt;npx fusuma start
  fusuma  Compiled successfully
  fusuma  Server running at http://localhost:8080/
&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw1s0uxne89i9y5ya3xhu.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%2Fw1s0uxne89i9y5ya3xhu.png" alt="Slide 3: npx fusuma start" width="799" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, when you're actually done, the same file turns into whatever you need next:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx fusuma build   &lt;span class="c"&gt;# optimized static HTML/JS bundle&lt;/span&gt;
npx fusuma pdf     &lt;span class="c"&gt;# the deck as a single PDF&lt;/span&gt;
npx fusuma deploy  &lt;span class="c"&gt;# pushes build/ straight to GitHub Pages&lt;/span&gt;
&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm09papu3odjpfpg5fjbn.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%2Fm09papu3odjpfpg5fjbn.png" alt="Slide 4: Same file, three outputs" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One Markdown source, sliding into whichever output you need. There's the pun again. I promise I'll ease up.&lt;/p&gt;
&lt;h2&gt;
  
  
  I built the meta version: fusuma slides about fusuma
&lt;/h2&gt;

&lt;p&gt;Every screenshot above came out of that same real project, not a mockup: a four-slide deck (&lt;code&gt;slides/0-slide.md&lt;/code&gt;) that explains fusuma using fusuma, with the built-in &lt;code&gt;pop&lt;/code&gt; theme enabled through one CSS import, &lt;code&gt;@import '@fusuma/client/assets/style/themes/pop.css';&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;That's the entire theming API. &lt;/p&gt;

&lt;p&gt;The code blocks you see highlighted are running through Prism via &lt;code&gt;@fusuma/prism-loader&lt;/code&gt;, and the whole thing is bundled with webpack 5 underneath.&lt;/p&gt;
&lt;h2&gt;
  
  
  The part where I stopped and said "wait, what"
&lt;/h2&gt;

&lt;p&gt;Here's what actually made me open the source. I ran &lt;code&gt;npx fusuma build&lt;/code&gt; in a folder that had no git remote configured, and got this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Fetching the remote origin url...
 build  The remote origin url of this repo isn't found.
 build  If you want to generate og:image, please set fusumarc.meta.url
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Fine, minor warning, no big deal. But "generate og:image" made me curious about &lt;em&gt;how&lt;/em&gt; it generates that image, because most tools either template it with a canvas library or just skip it. &lt;/p&gt;

&lt;p&gt;So I went digging in &lt;code&gt;packages/fusuma/src/server/dynamicRenderingServer.js&lt;/code&gt;, and it turns out fusuma doesn't template the social preview card. &lt;/p&gt;

&lt;p&gt;It launches an actual headless Chrome via Puppeteer, points it at your freshly built deck on a throwaway local server, and takes a real screenshot of your real first slide to use as the &lt;code&gt;og:image&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Then, using that same open browser tab, it runs &lt;a href="https://github.com/pa11y/pa11y" rel="noopener noreferrer"&gt;pa11y&lt;/a&gt; (not Lighthouse, despite what you'd guess) against the live page for an accessibility audit, filtering out a couple of known-noisy rules and anything inside a &lt;code&gt;&amp;lt;code&amp;gt;&lt;/code&gt; block.&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%2F1aae81w6un2v23uod862.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%2F1aae81w6un2v23uod862.png" alt=" " width="799" height="154"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the "build" step isn't only a bundler running. &lt;/p&gt;

&lt;p&gt;It's a bundler, and then a whole disposable browser opening your own presentation and taking its own picture before anyone else sees it. &lt;/p&gt;

&lt;p&gt;That one got me.&lt;/p&gt;

&lt;p&gt;While I was in the &lt;code&gt;@fusuma/mdx-loader&lt;/code&gt; source, I found three directive-driven features that never came up in the docs I'd already read. &lt;/p&gt;

&lt;p&gt;Fusuma parses your Markdown as MDX, which means fenced blocks and HTML comments can get swapped for actual JSX components at build time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A fenced block tagged &lt;code&gt;&lt;/code&gt;&lt;code&gt;chart ` or `&lt;/code&gt;&lt;code&gt;mermaid&lt;/code&gt; gets swapped for a &lt;code&gt;&amp;lt;div class="mermaid"&amp;gt;&lt;/code&gt; that renders as a live Mermaid diagram in the deck itself.&lt;/li&gt;
&lt;li&gt;Wrap a JS code block with an &lt;code&gt;&amp;lt;!-- executable-code --&amp;gt;&lt;/code&gt; comment and fusuma adds an "execute" button next to it that runs your snippet live, in the browser, in front of your audience. Actual runtime demos, no separate tab.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;!-- qr: https, some-url --&amp;gt;&lt;/code&gt; drops in an inline SVG QR code generated at build time, presumably so people can scan your slide instead of squinting at a URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that is advertised anywhere near the top of the README. &lt;/p&gt;

&lt;p&gt;You'd only find it by writing the comment syntax from memory of some other tool and hoping, or by reading &lt;code&gt;mdxPlugin.js&lt;/code&gt; like I did.&lt;/p&gt;
&lt;h2&gt;
  
  
  Would I actually use it
&lt;/h2&gt;

&lt;p&gt;For a quick, versionable, git-diffable slide deck, yes, honestly. &lt;/p&gt;

&lt;p&gt;The zero-config setup is real, the hot reload loop for &lt;code&gt;start&lt;/code&gt; is fast, and the fact that your accessibility audit and your og:image both fall out of the same Puppeteer pass is a genuinely clever bit of engineering, not just marketing copy. &lt;/p&gt;

&lt;p&gt;I'd stop short of building a 200-slide conference keynote in it since there's no visual editor for anyone who isn't comfortable in Markdown, but for internal tech talks, this-is-how-our-service-works decks, or documenting a tool by making slides about the tool (see above), it's a solid, honest little door to slide open.&lt;/p&gt;

&lt;p&gt;If you want to try it yourself, the source is at &lt;a href="https://github.com/hiroppy/fusuma" rel="noopener noreferrer"&gt;hiroppy/fusuma&lt;/a&gt;, and if you're curious about the self-screenshotting build step, the exact file is right here: &lt;a href="https://github.com/hiroppy/fusuma/blob/v2.8.4/packages/fusuma/src/server/dynamicRenderingServer.js" rel="noopener noreferrer"&gt;&lt;code&gt;dynamicRenderingServer.js&lt;/code&gt;&lt;/a&gt;. Slide responsibly.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href="https://x.com/about_hiroppy" rel="noopener noreferrer"&gt;about_hiroppy&lt;/a&gt; for such a smooth software.&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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>beginners</category>
      <category>markdown</category>
    </item>
    <item>
      <title>I Poked a 10-Year-Old Chat Protocol With a Stick</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Sun, 12 Jul 2026 17:49:36 +0000</pubDate>
      <link>https://dev.to/lovestaco/i-poked-a-10-year-old-chat-protocol-with-a-stick-2g4h</link>
      <guid>https://dev.to/lovestaco/i-poked-a-10-year-old-chat-protocol-with-a-stick-2g4h</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;Star git-lrc&lt;/a&gt; to help devs discover the project. Do give it a try and share your feedback.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So here's how I spent my weekend. &lt;/p&gt;

&lt;p&gt;I went digging through old GitHub repos looking for something forgotten, found a project called &lt;strong&gt;lhttp&lt;/strong&gt;, and instead of just reading the README like a reasonable person, I cloned it, compiled it, ran it, and poked it with a stick until it did something weird.&lt;/p&gt;

&lt;p&gt;It did something weird. We'll get there.&lt;/p&gt;

&lt;h2&gt;
  
  
  What even is lhttp
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/fanux/lhttp" rel="noopener noreferrer"&gt;lhttp&lt;/a&gt; describes itself as "a http like protocol using websocket to provide long live, build your IM service quickly scalable without XMPP." &lt;/p&gt;

&lt;p&gt;Translation: it's a tiny text protocol, styled like HTTP, that rides on top of a websocket connection instead of a raw TCP socket.&lt;/p&gt;

&lt;p&gt;The pitch is that you get HTTP's readability (commands, headers, a body) but with a connection that stays open, so you can push messages to clients instead of waiting for them to ask nicely.&lt;/p&gt;

&lt;p&gt;Under the hood, when you want to run more than one lhttp server (because one server is never enough once your chat app takes off, right?), all the servers talk to each other through &lt;a href="https://nats.io" rel="noopener noreferrer"&gt;NATS&lt;/a&gt;, specifically an ancient bundled copy of &lt;code&gt;gnatsd&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Every server subscribes to the same message bus, so it doesn't matter which server a client is connected to. &lt;/p&gt;

&lt;p&gt;Publish a message on server 1, and a client sitting on server 3 still gets it.&lt;/p&gt;

&lt;p&gt;Here's the cluster idea, minus the ASCII art from the README:&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%2F0shfwhfexjoye10ba4gd.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%2F0shfwhfexjoye10ba4gd.png" alt=" " width="800" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nice idea, honestly. Small gnat-sized message broker, quietly doing the heavy lifting so the actual servers can stay dumb and stateless. &lt;/p&gt;

&lt;p&gt;Naming a broker after an insect that's small but annoyingly persistent feels like accidental self-awareness.&lt;/p&gt;

&lt;p&gt;The wire protocol itself is charmingly blunt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LHTTP/1.0 chat
content-type:json
publish:channel_jack

{"to":"jack","message":"hello jack"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Command line, headers, blank line, body. &lt;/p&gt;

&lt;p&gt;If you've ever hand-rolled an HTTP request in telnet for fun, this will feel instantly familiar.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting a decade-old Go project to build again
&lt;/h2&gt;

&lt;p&gt;Here's where I expected pain. &lt;/p&gt;

&lt;p&gt;This repo uses &lt;code&gt;Godeps&lt;/code&gt;, a &lt;code&gt;vendor/&lt;/code&gt; folder frozen in time, and a &lt;code&gt;Godeps.json&lt;/code&gt; that proudly declares &lt;code&gt;"GoVersion": "go1.6"&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;For context, Go modules didn't exist yet in 2016. &lt;/p&gt;

&lt;p&gt;People vendored dependencies by literally copying the source into your repo and hoping nobody touched it again.&lt;/p&gt;

&lt;p&gt;I have Go 1.24 installed. I fully expected to spend my evening untangling import paths.&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;GOPATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/some/path/gopath
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GOPATH&lt;/span&gt;&lt;span class="s2"&gt;/src/github.com/fanux"&lt;/span&gt;
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /path/to/lhttp &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GOPATH&lt;/span&gt;&lt;span class="s2"&gt;/src/github.com/fanux/lhttp"&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GOPATH&lt;/span&gt;&lt;span class="s2"&gt;/src/github.com/fanux/lhttp"&lt;/span&gt;
&lt;span class="nv"&gt;GO111MODULE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;off go build &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Exit code zero. First try. No fights, no missing symbols, no "this API doesn't exist anymore." &lt;/p&gt;

&lt;p&gt;Old-school GOPATH mode plus a vendor folder from the Obama administration, and Go 1.24 just shrugged and compiled it.&lt;/p&gt;

&lt;p&gt;Same story for the actual demo server (&lt;code&gt;websocketServer/test.go&lt;/code&gt;, which despite the filename is a real &lt;code&gt;main&lt;/code&gt; package) and the tiny static file server in &lt;a href="https://github.com/fanux/lhttp-web-demo" rel="noopener noreferrer"&gt;lhttp-web-demo&lt;/a&gt; that just serves the chat UI. Both compiled clean with zero source changes.&lt;/p&gt;

&lt;p&gt;One small archaeology note for anyone following along: the main README's Docker instructions tell you to expose port 8081 for the lhttp server. &lt;/p&gt;

&lt;p&gt;The actual source code hardcodes &lt;code&gt;:8581&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;One digit off, in the docs, for what appears to be the project's whole life. Nobody tell them. Actually, somebody should tell them.&lt;/p&gt;
&lt;h2&gt;
  
  
  Turning it on
&lt;/h2&gt;

&lt;p&gt;Three processes, three jobs:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./gnatsd &lt;span class="nt"&gt;-p&lt;/span&gt; 4222 &amp;amp;          &lt;span class="c"&gt;# the message bus&lt;/span&gt;
./lhttp_server &amp;amp;             &lt;span class="c"&gt;# the actual chat server, listening on :8581&lt;/span&gt;
./demo_server &amp;amp;               &lt;span class="c"&gt;# serves index.html on :9090&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;With everything up, I didn't even bother with a browser first. &lt;/p&gt;

&lt;p&gt;I went straight for a raw websocket connection in Python, because I wanted to see the bytes.&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="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LHTTP/1.0 chat&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="s"&gt;Hello from the blog experiment!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# 'LHTTP/1.0 auth\r\ncontent-type:image/png\r\n\r\nHello from the blog experiment!'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I sent plain text under the &lt;code&gt;chat&lt;/code&gt; command. &lt;/p&gt;

&lt;p&gt;I got back a response labeled command &lt;code&gt;auth&lt;/code&gt;, with a header claiming the content type is &lt;code&gt;image/png&lt;/code&gt;. My text message. Labeled as a PNG. &lt;/p&gt;

&lt;p&gt;The demo's &lt;code&gt;ChatProcessor&lt;/code&gt; apparently relabels every reply this way regardless of what you send it, which I can only assume was a debugging leftover that nobody removed before shipping the reference demo. &lt;/p&gt;

&lt;p&gt;It's the kind of bug you'd only find by actually running the thing instead of reading the README, which is exactly why I'm writing this instead of just linking you the repo.&lt;/p&gt;

&lt;p&gt;Pub/sub worked exactly as advertised, though. Two connections, one subscribes to a channel, the other publishes:&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="c1"&gt;# client A
&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LHTTP/1.0 subpub&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;subscribe:room1&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# client B
&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LHTTP/1.0 subpub&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;publish:room1&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="s"&gt;Hey room1, anyone here?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# client A receives:
# 'LHTTP/1.0 subpub\r\npublish:room1\r\n\r\nHey room1, anyone here?'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Clean fanout through NATS, no drama. Good, functioning building block.&lt;/p&gt;
&lt;h2&gt;
  
  
  The bug where you talk to yourself
&lt;/h2&gt;

&lt;p&gt;The actual chat demo (the one with the Vue.js frontend and the little GIF in the README) doesn't use a generic &lt;code&gt;subpub&lt;/code&gt; command. &lt;/p&gt;

&lt;p&gt;It uses &lt;code&gt;chat&lt;/code&gt; for everything, subscribing to a channel called &lt;code&gt;chatroom&lt;/code&gt; the moment the page loads, then publishing to that same channel every time you hit Send.&lt;/p&gt;

&lt;p&gt;So I replicated exactly what the browser does, using two raw connections named, obviously, Jack and Mike.&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="n"&gt;jack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LHTTP/1.0 chat&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;subscribe:chatroom&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mike&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LHTTP/1.0 chat&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;subscribe:chatroom&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;jack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LHTTP/1.0 chat&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;publish:chatroom&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="s"&gt;yo mike, lhttp works!&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;Jack received two messages back. Mike received one.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jack got:
  'LHTTP/1.0 auth\r\ncontent-type:image/png\r\n\r\nyo mike, lhttp works!'
  'LHTTP/1.0 chat\r\npublish:chatroom\r\n\r\nyo mike, lhttp works!'

mike got:
  'LHTTP/1.0 chat\r\npublish:chatroom\r\n\r\nyo mike, lhttp works!'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here's why. The &lt;code&gt;chat&lt;/code&gt; command handler always fires off a direct personal reply the instant it receives anything (that's the mislabeled "auth"/"image/png" echo from earlier). &lt;/p&gt;

&lt;p&gt;Separately, and completely independently, the pub/sub filter re-publishes your message to the NATS channel you're posting to. &lt;/p&gt;

&lt;p&gt;Since you subscribed to your own chatroom when you opened the connection, you're also a subscriber of the very channel you just published to.&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%2Fzqjdv5048q3jpr8lunc7.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%2Fzqjdv5048q3jpr8lunc7.png" alt=" " width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nobody else in the room sees the duplicate. Only the sender does. &lt;/p&gt;

&lt;p&gt;Which means if you were building on this today without checking, you'd get bug reports along the lines of "why does my own message show up twice but nobody else's does," and the answer would sound completely made up until you draw this exact diagram.&lt;/p&gt;
&lt;h2&gt;
  
  
  So, is it fast?
&lt;/h2&gt;

&lt;p&gt;The README claims 10,000 messages published in 0.04 seconds on a single core with 1GB of RAM, which is a wildly specific benchmark from 2016 that I have no way to reproduce on identical hardware anymore. &lt;/p&gt;

&lt;p&gt;Instead of just repeating someone else's decade-old number, I ran my own, on whatever this machine happens to be:&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LHTTP/1.0 chat&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="s"&gt;msg-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;10,000 request/response round trips over a single websocket, from a single-threaded Python client, no batching tricks: &lt;strong&gt;0.558 seconds, about 17,900 messages per second.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Not the same benchmark, not the same hardware, not really a fair comparison at all, but it does confirm the core loop isn't doing anything silly like blocking on disk or sleeping between messages. &lt;/p&gt;

&lt;p&gt;For a text parser held together with string indexing (&lt;code&gt;buildMessage&lt;/code&gt; in &lt;code&gt;wsHandler.go&lt;/code&gt; walks the raw string character by character, no regex, no fancy tokenizer), that's respectable.&lt;/p&gt;
&lt;h2&gt;
  
  
  Would I actually use this
&lt;/h2&gt;

&lt;p&gt;Honestly? Probably not for anything new. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;golang.org/x/net/websocket&lt;/code&gt; (what lhttp is built on) has been the "please use gorilla/websocket or nhooyr.io/websocket instead" example in Go circles for years now, and the protocol's own reference demo has a bug you can find in about ten minutes of poking.&lt;/p&gt;

&lt;p&gt;But as a piece of "here's how people solved real-time messaging before everyone standardized on Socket.IO or plain WebSocket JSON frames," it's a genuinely fun read, and it's kind of wonderful that a 2016 vendor folder can still be handed to a 2026 Go compiler and just work without complaint.&lt;/p&gt;

&lt;p&gt;If you want to try breaking it yourself, the two repos are linked above. Bring your own Jack and Mike.&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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.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.amazonaws.com%2Fuploads%2Farticles%2Fed6ratvd5eb5bp0ep9ck.png" alt=" " width="360" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.&lt;/p&gt;

&lt;p&gt;git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.&lt;/p&gt;

&lt;p&gt;Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;
        git-lrc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Free, Micro AI Code Reviews That Run on Git Commit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;p&gt;| &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.da.md" rel="noopener noreferrer"&gt;🇩🇰 Dansk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.es.md" rel="noopener noreferrer"&gt;🇪🇸 Español&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fa.md" rel="noopener noreferrer"&gt;🇮🇷 Farsi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.fi.md" rel="noopener noreferrer"&gt;🇫🇮 Suomi&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ja.md" rel="noopener noreferrer"&gt;🇯🇵 日本語&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.nn.md" rel="noopener noreferrer"&gt;🇳🇴 Norsk&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.pt.md" rel="noopener noreferrer"&gt;🇵🇹 Português&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.ru.md" rel="noopener noreferrer"&gt;🇷🇺 Русский&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.sq.md" rel="noopener noreferrer"&gt;🇦🇱 Shqip&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.zh.md" rel="noopener noreferrer"&gt;🇨🇳 中文&lt;/a&gt; | &lt;a href="https://github.com/HexmosTech/git-lrc/readme/README.hi.md" rel="noopener noreferrer"&gt;🇮🇳 हिन्दी&lt;/a&gt; |&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;img width="60" alt="git-lrc logo" src="https://camo.githubusercontent.com/948c8f2d5cf41b48985cd364d48c3a2dc9bfbfd42eab3e0a9a1b3e61f5f17ce3/68747470733a2f2f6865786d6f732e636f6d2f66726565646576746f6f6c732f7075626c69632f6c725f6c6f676f2e737667"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;git-lrc&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Free, Micro AI Code Reviews That Run on Commit&lt;/h2&gt;
&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.producthunt.com/products/git-lrc?embed=true&amp;amp;utm_source=badge-top-post-badge&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="git-lrc - Free, micro AI code reviews that run on commit | Product Hunt" width="200" src="https://camo.githubusercontent.com/87bf2d4283c1e0aa99e254bd17fefb1c67c0c0d39300043a243a4aa633b6cecc/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f746f702d706f73742d62616467652e7376673f706f73745f69643d31303739323632267468656d653d6c6967687426706572696f643d6461696c7926743d31373731373439313730383638"&gt;&lt;/a&gt;
&amp;nbsp;&lt;/p&gt;
&lt;br&gt;
&lt;a href="https://discord.gg/sGdnKwB3qq" rel="nofollow noopener noreferrer"&gt;
  &lt;img alt="Discord Community" src="https://camo.githubusercontent.com/b8f979318aaabc8dec512b9d4e6e2a12431fba3c8a3b8738e1a97a0722d4e4bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446973636f72642d436f6d6d756e6974792d3538363546323f6c6f676f3d646973636f7264266c6162656c436f6c6f723d7768697465"&gt;
&lt;/a&gt; &lt;a href="https://goreportcard.com/report/github.com/HexmosTech/git-lrc" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Go Report Card" src="https://camo.githubusercontent.com/e74c0651c3ee9165a2ed01cb0f6842c494029960df30eb9c24cf622d3d21bf46/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f4865786d6f73546563682f6769742d6c7263"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/git-lrc/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/git-lrc/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/git-lrc/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/git-lrc/./gfx/a_few_micro_reviews.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2Fgit-lrc%2FHEAD%2F.%2Fgfx%2Fa_few_micro_reviews.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GenAI today is a &lt;strong&gt;race car without brakes&lt;/strong&gt;. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents &lt;em&gt;silently break things&lt;/em&gt;: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;git-lrc&lt;/code&gt; is your braking system.&lt;/strong&gt; It hooks into &lt;code&gt;git commit&lt;/code&gt; and runs an AI review on every diff &lt;em&gt;before&lt;/em&gt; it lands. 60-second setup. Completely free.&lt;/p&gt;
&lt;p&gt;In short, git-lrc helps &lt;strong&gt;Prevent Outages, Breaches, and Technical Debt Before They Happen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At a glance:&lt;/strong&gt; &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;10 risk categories&lt;/a&gt; · &lt;a href="https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for" rel="noopener noreferrer"&gt;100+ failure patterns tracked&lt;/a&gt; · every commit…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/git-lrc" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>programming</category>
      <category>go</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
