Search Results: "beh"

19 June 2026

Wouter Verhelst: Agentic coding and Free Software

Through work, I have paid license to windsurf (recently renamed to "devin"), an application for LLM-based (aka, "Agentic") development. I hadn't been using it that much, but in an effort to more clearly understand how this whole AI development thing works, I decided to give it a closer look recently. My conclusions: In its current form, this whole LLM wave is problematic for multiple reasons. But ignoring that, and looking at the technology only, I can say that:

Problems Lest someone (incorrectly) assume that I am arguing in favour of the current state of affairs with regards to LLMs, let me state this first. The way LLMs are built today is highly parasitic. Websites are downloaded in whole, at unsustainable rates, regardless of the consent of the people who made the original content. The result is predictable: servers get overloaded, server administrators attempt to implement various mitigations. Some of these mitigations work; some do, for a while; some are entirely useless. In actual fact, the mitigations are an arms race -- if too many people implement the same mitigation, then the people who try to build yet another LLM so they can extract rent will just try to work around the mitigation, eventually they will succeed, and you'll just have to come up with another mitigation. It's a bit like spam; you introduce regex-based spam filters, they introduce spelling mistakes, you introduce bayesian filters, they add a large batch of markov chain-generated semi-nonsense words made invisible by markup, you add filters to block emails with such markup, they move the text into an image. We have working mitigations today, but eventually we'll run out of ideas. LLMs glob up everything they can while ignoring the license of the source material. The people who push those LLMs claim that pushing the source material through the machine learning algorithms makes the output of the algorithm distinct enough from the source material that the license no longer applies; I'm not so sure that this is true. I guess the New York Times v OpenAI lawsuit will teach us some of the answer to that question here, but even so the ethical questions about "is it OK to bring down another server just so we can download the internet for another for-pay LLM" are still open. And regardless of what the law states, my opinion on "you're using my copyleft code to generate code under a different license" is not something you might like if you agree with the rent seekers' opinion on the subject. That all being said and true, the technology works. You can have a "conversation" with an LLM that resembles a human one. If you pass it some data, you can use plain english to ask it questions about that data, which is a lot easier than to ask it about that in a formal way. You can request it to generate some code, and it will generate something that looks like what you need and that will be mostly correct for like 95% of the time. Now, yes, 95% of the time is not 100% of the time, and no, you can't ask it to "write me a piece of software that implements this 300-page requirements document and get back to me when you're done", because it will fail, and you won't know where it has failed, and you'll take it into production and expect everything to be fine because it won't and this one minor logic bug will cause half your servers to spin and consume credits with your infrastructure provider with nothing to show for it. But that doesn't mean you can't use an LLM to build a large piece of software. It just means you have to understand the LLMs limitations and strenghts, and use them correctly. Here's what an LLM is good at:
  • Generating plausible text
  • Interpreting text to figure out what a plausible meaning or summary of that text is
  • Giving vague indications as to what the probable context of a given body of text is.
It turns out that that's enough to use the LLM to build a reliable piece of software, provided you do it right.

Paradigm shift An LLM can generate text by the truckful. The generated text could be code. Given a good enough LLM, the generated text might even run and do something useful. You can try to blindly run the code, and if it doesn't run correctly, you can paste the error message to the LLM, and it can tell you what went wrong and how you could possibly fix it. This creates a feedback loop: you ask it for an amount of code, you run the code, you receive an error, you tell it that the code is problematic and give it the error message, it makes changes to the code, now you have something that at least no longer fails at startup. If you ask it to add tests to make sure that your code acts as per your specification, now you get an error if and when the code doesn't act as per your specification. Or, well, at least not as per the part of the specification that was correctly turned into a unit test by the LLM. LLMs have a context window, so if the error message is pasted in the same conversation as where the code was generated, it is able to reuse the earlier prompts to refine how it should interpret the error message that you received. You can't really paste the source code of an entire application into the prompt of your LLM, that would quickly overrun its context window. But LLMs also allow you to provide some form of background information -- a document, say -- on which you ask it to reason. It will interpret that document, but doing so uses less of the LLMs context window. So providing the LLM with your application's source code as background information can help it understand better how your code interacts. This is especially helpful if you only provide the LLM the background information relevant to the actual question. So now if you are able to:
  • Create background context with your application's source code
  • Have the LLM generate a first draft of your requested change, plus the tests to make sure it works
  • Compile (if applicable) the generated code (and tests) and run said tests
  • Return any error messages to the LLM with a request to correct the error
Then the combination of "getting it 95% right off the bat" and the above feedback loop means you can generate syntactically correct code, that probably does what you need, in minutes. I say "probably" for a reason. There are going to be cases where you specify a request without a number of details (because they are implied), and the LLM will get most of those details right but just not implement the one bit because it's an automaton and it doesn't think. Or you will ask it to make sure that two bits of the application look exactly the same, without specifying that they must act the same, now and in the future, and it will just generate the same block of code twice and then in a future change it will change one but not the other. But if you review the changes, and you have experience as a programmer, you will be able to spot most cases where the LLM got it wrong. And so it's possible, if not necessarily easy at first, to use an LLM to generate mostly correct code. There are certain places where "mostly correct" code is not desireable. But equally, there are also cases where, "mostly correct" is good enough. After all, most of the software you run today -- the bits of it that weren't, yet, generated by an LLM -- is only "mostly correct", too, because to err is human and we all make mistakes. If not, there wouldn't be any CVEs and your software would never do anything wrong. Now, doing the feedback loop described above is certainly something you could do manually. You could open an account on one of the LLM websites, upload the source code of your application, ask it to generate some new feature, download the newly generated feature, run it, and then copy/paste any error messages back into the LLM. But that's a lot of manual work of the type that computers are pretty good at. So that's what the "windsurf" tool helps you with: you run it inside your IDE -- either a VSCode-based tool that you download from their website which comes with their product preinstalled, or a separate JetBrains plugin that you can install. You can then open your entire relevant codebase in a workspace in your IDE. You then ask the LLM, through the IDE, to generate a new feature in your codebase, and to also generate the test while it's at it. It will use a mixture of LLM interpretation and non-LLM functionality to scoop out the relevant bits of your codebase to send to the LLM as background information, will send it your prompt, will download the generated code and patch or create files, will compile (if required) and run the newly generated code and tests, and will refine the generated code if the tests produce any errors. All mostly automatic; by default, running anything requires explicit confirmation. You can turn that off completely (probably not a good idea), or you can give it a whitelist of things that you don't want to confirm (perhaps OK), and the tool also passes standing instructions to the LLM to never generate any command that deletes a file (which, like with any LLM, can be overridden, but it requires you to be very stubborn and to use more credits than you'd probably like). All this put together means you can build something without writing any piece of code, provided you do it right.

A technically positive evolution Don't go and say, "here's a 300-page document, read it and write whatever the document says". It will get it wrong, it will write a massive test suite that it will only run at the end, it will choke itself up trying to interpret the massive amount of failures it encounters, it will fill up its context window and it will start to forget some of the requirements. That won't work. But what you can do -- what I did, in fact -- is this. First, create an empty workspace. Don't put any code in it. Then, tell the LLM to generate a backend framework using technology X and a frontend framework using technology Y that initially only says "hello, world". Also add tests to it, and run the tests. It will do that. You'll not get much, but it will work. Then, ask it to add some UI elements. A login page, perhaps. A navigation bar. Small things. Most of it doesn't have to be functional -- but tests must be there for the bits that are, and have it run the tests and evaluate the results. Rinse, repeat, until you have a working application. Importantly, in between the steps, you should also run the application yourself and see if the change was implemented correctly. Sometimes it won't be. Sometimes there will be a subtle bug -- I at one point had a the application hang after a few minutes. Sometimes you tell it that there's a subtle bug, and it will discover it more quickly than you could, and it will fix it, and in implementing the fix it will uncover another bug, and then you have to fix that one -- the fix it came up with for the hang was to move something to an async process on the server, which caused the application to start spinning while trying to create hundreds of async jobs (this is when I realized that the hang was a deadlock due to some part of the codebase doing something that indirectly triggered itself). Sometimes it will try to fix the bug you tell it about, and you'll see that it's going off on a tangent that has nothing to do with what you're seeing. It's important to keep an eye on what it's doing, so you can guide it back on track when that happens -- when I told it about the hang, it started investigating the part of the code which sends out emails, thinking that it could hang while waiting for sendmail to finish, but the hang was happening when the application was idle, not when it was sending out emails, and only when I told it about it happening when it was idle did it find the deadlock. So it's not a fully automatic process, and it needs to be guided by someone who knows what they're doing. But if that is the case, you can come up with something that works. I spent evenings and breaks for about a week, and I managed to create a working application which, had I written it by hand, would have taken me a few months of full-time work to come up with. And I now have a side project, fully complete and working, that I had been thinking about doing for more than a decade, but never got around to actually doing, because of all the work that would be involved and I just didn't see myself having the time for. It's not perfect code. But it's mostly good enough, and it will perform the job it needs to. And it looks far slicker than most of the side projects I've done in the past, because in the past I would prioritize between implementing new features or making something look slick, and I would decide that the new feature was more important because it's only for me and there's only me and nobody cares if it looks good or not and I don't have three weeks to come up with something that looks better. But here, I found myself sometimes spending 10 minutes writing a prompt with instructions on making things look better. Because what's 10 minutes when you just spent an hour writing down and refining specifications for functionality and tests? There are a number of other things in which an LLM can help a programmer. For instance. I received a bug report recently in a project I'm paid to maintain that I couldn't make heads or tails of. I opened the source code in my windsurf IDE, pasted the bug report in the prompt, and then requested the tool to analyze the source code and the associated logs and tell me how the described behavior could be happening. It turned out that I had overlooked something, but with the help of the tool, I found the bug in minutes. I was trying to understand a particular part of a large codebase that I didn't really grasp very well. I loaded the codebase in the tool, and asked it to explain to me how a particular action is performed by the code. I requested specific functions and line numbers. I now have a far better understanding of how the code works, and will be able to write that patch that I've been wanting to write for years -- without using the LLM. I have been struggling for, literally, years with understanding why another tool that I maintain was misbehaving in a particular way but only in Firefox. I opened the codebase in Firefox, explained the buggy behavior in plain English, and asked it to explain how this could be happening. It picked up some obscure corner case behavior of ffmpeg and mp4 containers that I was not aware of and that perfectly explained why things were misbehaving in the way that they were. At the same time, there are limitations. Giving an LLM a codebase that was originally generated by an LLM (either the same one or another one) seems to work well. Giving it a codebase that was written by a human and expecting it to correctly update it seems to be more error-prone. I did one or two of those as a trial, and it is more problematic than anything. An LLM is also not intelligent, notwithstanding the popular term of "Artificial Intelligence". On multiple occasions, I've asked it to write a test case for some code that was not set up to do so; and rather than suggesting a refactor is required, it would instead copy the code that needed to be tested and then test the copy, rather than the original. The tool has made multiple similar errors. I have sometimes people describe agentic coding as "similar to interacting with junior programmers", but that is not the case. A junior programmer will either fill in the gaps in your specifications, or ask for clarification when something seems off. The LLM will not do that; it will do what you ask, exactly that and nothing more. If you missed a corner case in your specification, then all bets are off. I remember learning about programming language generations in college. A first-generation language is "machine code", a second-generation language is "assembler", a third-generation language is any high-level language such as C, Perl, or Pascal. I've forgotten what set a 3rd-generation language apart from a 4th-generation language. But I remember the definition they gave me for a 5th-generation language: "you tell the computer what to do, and it will do it". At the time, I thought it was ridiculous. Nobody could ever write something like that. But it's here. And it's a threat to free software.

A threat to free software? Yes. There is the obvious part where most of the well-known LLMs are non-free software. I mean, there are some "open source" LLM models. The windsurf tool that I used doesn't allow you to use them (directly), but they're there. There are also open source applications that implement what the windsurf editor does. So it's definitely possible to work like this without resorting to non-free software and non-free services, even though the non-free LLMs might be a bit ahead of the curve of the free ones. But that's not what I mean. And there is also the obvious thing which I mentioned earlier in this post, which is that the people who try to build LLMs are doing it in unethical, disgusting ways, causing downtimes and disregarding licenses for whatever they can get their grubby hands on. Ideally we wouldn't be in that situation, and ideally this wouldn't be a problem, but we are where we are. And there's the obvious thing where the OSI sold itself out and declared that a machine learning program can be open source even when the very things it was built from -- the training data -- is not available. That's a major issue that the free software community needs to fight against, but there's not really anything that that is a threat to free software. You just build your own, free software, LLM, and you're done. The actual threat is in funding and developer support. Most large businesses do not care about free-as-in-freedom software. They like the free-as-in-beer part, and they appreciate that the free-as-in-freedom bits can make the software more customizable. They are (mostly) happy to do sponsorships of the free-as-in-freedom projects that they use if that means their free-as-in-beer usage of the software gets improved. But why would you care about all that when you can just generate the code you need, rather than interacting with an open source community that may or may not care about your business's interests?

Where to go from here Although I think the moral and environmental issues with LLMs are real and problematic, given the experiments I did I am not convinced that the concept of interacting with a computer system in natural language and to use it to generate code is necessarily deficient. There are pitfalls, but they can be managed. It is possible to use such a system to create throwaway, proof-of-concept type "good enough" code bases. It can be used to interpret code bases and to understand bug reports. I believe that the major issue with LLMs has to do with that saying about hammers and nails:
If all you have is a hammer, then everything looks like a nail.
LLMs are an outgrowth of machine learning, pushed by large corporations. These large corporations have a lot of money. If all you have is money, then every problem can be fixed by throwing more money at it. The initial language models were promising but not (yet) good enough, and it seemed that one way in which they could be improved was to increase the scale of the statistics: throw more hardware (and thus money) at it, and rather than improving the efficiency of the models, just scale up. Scaling up is something that megacorporations are very good at. It's only a money problem, after all. Does that mean that "scaling up" is the only way to improve the models, though? I'm not convinced. Some hardware, such as most modern Apple and Samsung devices, ship with accelerator hardware for machine learning algorithms. There are some models that are small enough to be able to run on these devices. I don't see why it should not be possible to create a small(er) language model that can do some useful part of the above-described use cases; if not locally, then at least on a server that one can run on-prem rather than requiring that you pay rent to one of the LLM companies. The Software Freedom Conservancy has published an aspirational statement on machine learning-assisted programming that, I think, gets a lot right. It's not quite a definition, but it's something to keep in mind. Perhaps that's the way forward? More questions than answers at this point, anyway.

16 June 2026

Vincent Bernat: Building a Soviet Nail Factory: how KPIs killed efficiency

In 2008, I landed my second job, in the network team at Orange Portails1, the division behind the websites and search engine of the French telecom operator Orange. The place ran like clockwork: a comprehensive technical setup, a dedicated team for every part of the business, and room to focus on what I do best. A few years later, none of that mattered: thanks to an obsession with the numbers, we could no longer deliver new services on time.

Disclaimer This is a story I like to tell to warn people about Goodhart s law.2 As these events happened almost 15 years ago, my recollection is a bit fuzzy. I left in 2012.

The first years During my first years, the department operated like a startup. Its cradle was the French company Echo. They built a search engine. France T l com bought it and renamed it Voila. It was the most visited search engine in France in the early 2000s. France T l com consolidated the portal activities into the Wanadoo Portails division, later renamed Orange Portails. The technical environment was excellent. We had many internal tools:3 a ticket system, an RRD-based graphing tool, an IPAM, a reporting tool, and an SNMP-based alerting tool.4 We deployed our Linux servers with CFEngine. We installed systems and applications from internal Debian repositories. We documented everything in a private MediaWiki instance. Supervision was performed with an ancestor of Xymon. The network architecture was clean and scalable with little legacy. We onboarded new people in a day. It was a nurturing environment for me. I developed several tools: lldpd, an 802.1AB implementation, Snimpy, a pythonic binding for Net-SNMP, Wiremaps, a layer-2 discovery tool with a time machine to know which device is connected where, Kit r , a tool to simulate network conditions, QCSS-3, a controller for load-balancers, and ipoo, a service available through a Jabber chatbot and a Greasemonkey script to expose IP-related information. I added SNMP support for Keepalived and Quagga. I also started this blog, with articles like Anycast DNS, TLS-related articles like TLS computational DoS mitigation, SNMP-related articles like Integration of Net-SNMP into an event loop, Linux-related articles like Tuning Linux IPv4 route cache, and an article about VXLAN long before it was cool.

The collapse When we needed new servers, the on-site team would take a set from the inventory, install our base Linux distribution on them, put them in the datacenter, and cable them to the top-of-the-rack switches. We opened a ticket describing the servers we needed, and one week later, our servers were available. Orange wanted to know if this team was performing well, so they asked for KPIs. They decided to use the number of tickets completed in a year. They asked to double this number. So instead of one ticket for a new service, we would open six tickets one per server. By the end of the year, the KPIs had more than doubled. Everybody saw it as a success for performance management. So, they asked to do the same for the next year. Now, we needed to open a ticket per server and per step. Again, the KPIs doubled. Behind the scenes, the tickets went to different people and were no longer handled in order. So, for the next year, it was decided to have meta-tickets and meetings to follow the progress of these tickets. Of course, all these extra steps pushed the KPI even higher. This performance management method spread to the other teams.5 Everything became slower. Instead of a couple of weeks, a new service now took six months. We built a Soviet nail factory. But the KPIs were good, and we stopped caring. Let me give you another example. We had to estimate the impact of each night operation. We weren t half bad: we declared most operations without any expected impact. Most of the time, there was no impact. One time out of five, there was a 5-second impact. We were told to try harder to meet our expected impact. What did we do? We started declaring a 5-second expected impact. One day, we got a 30-second impact and were told we failed to match the expected impact. In the end, we declared most operations with a 10-minute expected impact, and we stopped caring: instead of carefully shifting traffic around, we allowed ourselves a 5-minute impact. And our KPIs were never better.
Graph showing the impact of night operations. Year after year, the impact tolerance has been increased. In the final year, the expected impact is 10 minutes, and all operations remain under this threshold. However, the impacts are much more significant than they were in the first year.
An artist's rendering of the evolution of impacts over the years.

KPIs are not bad, but they are easy to break. Use them carefully: let the people doing the work help choose the metrics, and tie those metrics to the quality of the service for example, with service level objectives. Otherwise, even dedicated people stop caring, game the system, and eventually quit.6

  1. Internally, this division was named Hebex. It was located in Bagnolet (next to Paris) and Sophia-Antipolis (near Nice).
  2. Goodhart s law often gets the credit, but Campbell s law describes my experience even better: the more you lean on a number to make decisions, the faster people corrupt it.
  3. At the time, SaaS was not really a thing. I remember we considered, with a couple of colleagues, selling Wiremaps as a SaaS, with homomorphic encryption for the database. But who would outsource their observability stack?
  4. Snalert was a metacircular alerting tool in Perl. It was able to poll a very large number of SNMP targets in a short timespan. All our monitoring was SNMP-based, including system monitoring.
  5. My team also managed the rules of many Linux-based firewalls. To increase our KPIs, we used the same method: rather than accepting one ticket with a flow matrix, we requested one ticket per flow.
  6. Orange is not unique. Google s promotion process is another well-known example of a broken KPI. Michael Lynch explains it in Why I Quit Google to Work for Myself.

15 June 2026

Freexian Collaborators: Debian Contributions: Go default compatibility, Trimming build-essential, Python upstream engagement and more! (by Anupa Ann Joseph)

Debian Contributions: 2026-05 Contributing to Debian is part of Freexian s mission. This article covers the latest achievements of Freexian and their collaborators. All of this is made possible by organizations subscribing to our Long Term Support contracts and consulting services.

Go default compatibility, by Helmut Grohne At the MiniDebConf Hamburg, Andrew Lee had prepared a talk on how Debian accidentally chooses Go compatibility. Helmut joined Tobias Quathammer and Andrew Lee in looking into the problem. Go has a compatibility system where modules declare a desired Go version to be compatible with. This influences various features such as whether RSA keys smaller than 1024 bits are accepted. Unfortunately, Debian s way of building Go packages is unique in setting GO111MODULE=off, which practically implies a very old compatibility version that enables a number of insecure settings. Most Linux distributions use the default GO111MODULE=on and therefore consult a go.mod file that often declares a sensible version. While doing so is the way for Debian longer term, getting there involves major changes so we also sought a more short term workaround. We developed a patch to the Go compiler that would enable it to pick up a compatibility version from the environment. Tobias uploaded it to unstable. The next step is communicating the declared compatibility version from go.mod to the compiler via the new variable. Then, rebuilding the archive resolves the immediate symptoms. This does not save us from having to perform the larger transition to GO111MODULE=on, but this shortcut can be backported to trixie.

Trimming build-essential, by Helmut Grohne One of the harder problems of the architecture cross bootstrap is correctly expressing the Build-Depends of glib during the toolchain bootstrap. It implicitly depends on build-essential, which happens to depend on libc6-dev. This poses a cycle. It applies even for cross building, because it is interpreted for the host architecture and that there is no way of satisfying this dependency during the toolchain bootstrap. Given discussions at MiniDebConf Hamburg with Jochen Sprickerhof and others, a seemingly stupid idea evolved: Let s delete build-essential. What looks insane on the surface might deserve a second look. Given how we moved away from C, C++ and autotools, what is in build-essential no longer is required by much of the archive. With the rise of debputy, debian/rules no longer has to be a makefile. While the task would be huge, those packages relevant to architecture bootstrap could explicitly support building without the implied dependency making their dependencies explicit. In a number of cases, this amounts to issuing a dependency on g++-for-host. This dependency requires the use of architecture-prefixed tools. Therefore, Helmut wrote a debhelper change that makes it always pass build tools to various build systems. This also enables more packages to honour environment variables such as CC and CXX.

Python upstream engagement, by Stefano Rivera Stefano attended PyCon US (at personal expense) to improve upstream relations and ensure Debian s voice is heard where it needs to be. On Friday there was a packaging summit (notes) with good discussion on the future of the wheel format, and some discussion of the new abi3t shared library format for free-threaded python. In preparation for the event, Stefano did a complete review of the current patch stack. Stefano s primary goal was to get some of Debian s patches merged during the sprints, and results were mixed. Some trivial patches (e.g. GH-150098, made progress and merged, but the most consequential patch Debian is carrying is still blocked. Stefano will continue to try to drive progress on this.

Miscellaneous contributions
  • Carles worked on po-debconf-manager: Reviewed Catalan translations for 6 packages, submitted 10 packages to maintainers, and removed 3 packages from po-debconf-manager.
  • Carles worked on check-relations: Continued improving the backend, including importing source package build dependencies to better support analysis of Debian blends. Added support for ignoring packages using regular expressions and source package names in response to user feedback. Used the tool to report 5 new bugs and followed up on previously reported issues.
  • Helmut sent a cross build patch on behalf of a customer.
  • Helmut uploaded debvm and guess_concurrency both featuring improved reproducibility and documentation.
  • Helmut continued maintaining rebootstrap and made it correctly handle binNMUs of gcc-defaults. Additionally, he poked at existing gcc patches giving answers, rebasing or closing them.
  • Helmut supported the video team in Hamburg mixing audio.
  • Helmut continued to report undeclared file conflicts of various kinds and corresponded with maintainers about them.
  • Antonio attended a debate during the Brazil Internet Forum about the impacts of the child protection regulation (ECA Digital) on free software operating systems.
  • Antonio worked on Debian CI to improve the system transparency for users. This included listing any pending jobs explicitly in the job lists for each package/architecture/suite page, as well as adding a queue status page that users can check for an estimate of test latency.
  • Antonio worked on several Debian CI maintenance tasks, including but not limited to some monitoring improvements, replacing usage of fonts-font-awesome with fonts-fork-awesome, and adding the ability in debci to configure a global notice (which is being used in Debian CI to point to the system status pages).
  • Antonio started doing some tests related to the change of default Debian CI backend from lxc to incus-lxc. This helped identify an omission in the creation of incus-lxc images. It was missing dpkg-dev, which caused a few packages that assumed its presence to fail. In the end, the incus-lxc backend will be fixed to include dpkg-dev by default in the image, but that uncovered an undeclared dependency in gem2deb (Ruby packaging helper) and in ruby-byebug, both already fixed in unstable.
  • Stefano did some minimal work on debian-reimbursements to get it working with current versions of django-allauth.
  • May included the discovery of several high-severity Linux kernel root exploits. Stefano updated kernels and rebooted debian.social infrastructure several times.
  • Stefano supported the Hamburg miniDebConf s wafer website during the event, and set up an instance for the 2027 edition too.
  • Stefano supported the bursary team issuing bursaries for DebConf 26.
  • Stefano uploaded routine updates of python-pip, pystemmer, snowball-data, snowball (making up a mini, uncoordinated snowball transition), python-authlib, python-discovery, python-installer, python-mitogen, python-pipx, python-cachecontrol, platformdirs, and python-virtualenv.
  • Stefano fixed a small number of bugs in dh-python, culminating in the 7.20260524 upload.
  • Thorsten finally managed to upload a new upstream version of hplip. He also uploaded a new upstream version of epson-inkjet-printer-escpr. Last but not least with the help of other contributors he could fix bugs in lprng.
  • Lucas and Santiago contributed significantly to the DebConf 26 Content team; helping to organize the team, review and rate talk proposals.
  • Lucas also supported a packaging sprint held in India by rebuilding and publishing the latest results of the Ruby 3.4 transition effort.
  • Santiago continued contributing to the efforts to organize DebConf 26, especially supporting the local team with different tasks.
  • In collaboration with Emmanuel Arias, Santiago is mentoring Aryan Karamtoth, a GSoC participant that is working to introduce linux live-patching support in Debian. The GSoC project started in May, with community bonding and coding. Santiago reviewed a merge request to prepare the clang-extract package for debian. clang-extract is one of the building blocks that will help to extract specific functions from large C code, so only relevant code can be patched, without recompiling the whole original basecode.
  • Anupa assisted Jean-Pierre Giraud with the point release announcements for Debian 13.5 and Debian 12.14.
  • Colin backported various security fixes from OpenSSH 10.3 to all supported releases (including LTS and ELTS).
  • Colin backported IP quality-of-service fixes to OpenSSH in trixie. The situation there had been unsatisfactory for some time, and upstream reworked their QoS support in OpenSSH 10.1 in a way that typically produces much better results.
  • Colin imported new upstream versions of 26 Python packages, and fixed around 25 RC bugs for the Python team.

29 May 2026

Ravi Dwivedi: Budapest Travel

In September 2025, I attended the annual LibreOffice conference in Budapest, Hungary. This gave me an opportunity to explore the city, which I will cover in this post. Let s start with the currency. Although Hungary is a part of the European Union (EU), it doesn t use the euro as its currency. Instead, it uses Hungarian forints (denoted by Ft ). During my time in Hungary, 1 Indian rupee was equal to 4 Hungarian forints. After reaching the Budapest airport, I bought a 15-day public transport pass. The public transport counter is after you pass customs and immigration. The pass allows unlimited use of public transport in the city. I had to show my passport and pay 5950 Ft to get the pass. The pass had my passport number mentioned on it. The public transport passes can also be bought at any of the tram stations as well.
This is the counter from where I bought my public transport pass. This is the counter from where I bought my public transport pass.
Budapest pass. My unlimited public transport pass for Budapest. I have redacted my passport number from it.
An automatic ticket machine An automatic ticket machine at a tram station in Budapest.
Budapest is a union of two cities Buda and Pest lying on opposite sides of the Danube River. My hotel Corvin Hotel was on the Pest side. Budapest had good public transport. The buses, metros, and trams complemented each other. For example, the airport didn t have metro or tram connectivity, but it was served by the bus. Most of the metro was on the Pest side, with only a couple of stations falling in Buda. However, both sides had an extensive network of trams. Furthermore, the information about the public transport was easily accessible. For instance, the map of tram stops inside the trams also included the bus routes one could get after alighting at those stops. From the airport, I took a bus followed by taking a metro on the M3 line to reach within walking distance of my hotel.
An M3 line metro in Budapest. An M3 line metro in Budapest.
During the conference I would take the tram to the conference venue. The trams were modern and fast. They also had a smiley face at the front, which gave them a friendly look. It seemed like the trams were happily doing their job. The city also had a good pedestrian infrastructure along with separate cycling tracks.
A tram in Budapest. A tram in Budapest having a smiley face at the front.
Budapest s tap water is officially safe to drink, which was mentioned on a sticker posted on the wall of the bathroom of my hotel room. So, I did not need to buy any water bottles while I was there. On the 6th of September, I went on a sightseeing tour of Budapest with my Dione. Our friend Attila, who was a local (from Hungary), joined us. We went to the central market from our hotel by metro. If you read my post on Vienna, I mentioned that the metro stations don t have AFC gates but ticket validators instead. Budapest s metro also has the same system. If you buy individual tickets, you need to validate them using the validators on the station before boarding the metro. If you are using a public transport pass like I was, then you do not need to validate, and you can board the metro directly.
A ticket validator at a metro station in Budapest. A ticket validator at a metro station in Budapest.
In 10-15 minutes, we reached the central market. Attila showed us around. I bought a fridge magnet and paprika powder as souvenirs. Paprika powder is a signature spice of Hungary. It is mainly available in two forms one is sweet and the other being spicy. I wanted the spicy one, but I didn t get that in that market. Therefore, I had to contend with buying the sweet version. The sweet version isn t sweet though, it is just not spicy. After bringing that paprika powder home, it is mainly used for food coloring. I like it though and use it frequently in my omelets and other dishes.
Central Market. Central market.
A building with a tram in front of it. The building right behind the tram is the central market building.
At some point, Atilla had to join the The Document Foundation (TDF) sightseeing group, so we parted ways at the central market. Dione and I continued our sightseeing and decided to start with visiting the Hungarian parliament, which is a tourist attraction. It was because we were on the Pest side and the parliament was also on the same side, while other tourist attractions were on the Buda side. So, Dione and I hopped on a tram and went to the parliament. We got off at a tram station just outside the parliament. The parliament is the icon of Budapest. The building has a gothic architecture and colored brown and white. One can buy tickets and take an inside tour. However, we didn t have a lot of time, so we stayed outside the building.
Hungarian Parliament building. Hungarian Parliament building.
After spending some time outside the parliament building, we took a tram to the Chain Bridge. As I mentioned earlier, Budapest has two parts Buda and Pest separated by the Danube River. To go from one of the sides to the other requires crossing a bridge. Although Budapest has many bridges linking the two sides, the main one is the Chain Bridge. We walked on the chain bridge to get to the other side. The bridge gave a good view of the Danube River. It also had a statue of a lion. The Buda Castle (another major landmark of Budapest) was visible from the bridge.
Chain Bridge. A shot of Chain Bridge.
A lion statue The lion statue on the Chain Bridge.
After reaching the other side of the bridge (the Buda side), we sat on a bench for some time and then planned on where to go next. We decided to go to Fisherman s Bastion, which is another tourist attraction. We used the OSMAnd~ app to figure out which bus to take and hopped on one. Soon we reached Fisherman s Bastion, where we found a flight of stairs that led upwards. Upon climbing the stairs, we got a panoramic view of the city. It also gave us a good view of the Hungarian parliament across the river. Going further upstairs, we found a statue of Stephen I of Hungary. He was the first king of Hungary, getting the crown in the year 1900.
A view of Hungarian parliament from Fisherman's bastion A view of Hungarian parliament from Fisherman s bastion.
I found Fisherman s Bastion to be the best tourist attraction in the city. As mentioned earlier, it offers a panoramic view of the city, which I liked. I liked the arhitecture and open space there. If you find yourself in Budapest, I would highly recommend that you visit Fisherman s Bastion.
Fisherman's Bastion. Fisherman s Bastion.
A green colored statue of King Stephen Statue of Stephen I of Hungary at Fisherman s Bastion.
Next, we went downstairs and returned to where the bus dropped us. From here on, we walked in random streets to see the residential and non-touristy side of Budapest. It was not so random as we walked towards Batthy ny t r metro station. Upon reaching the metro station, we found a caf where we stopped for a while for some coffee. After injecting some caffeine into our blood, we proceeded to find a place to have lunch.
A metro station Batthy ny t r metro station.
For lunch, we decided to go to R k czi t r metro station after reading on the internet about the food options there. Upon exiting the metro station, we found a market inside a building that had a lot of shops, but most of them were closed. After roaming around inside a bit, we found an Italian place open and decided to eat there. The name of this place was Matteos. We ordered an eggplant parmigiana, a lasagna artichoke, and a classic tiramisu. It wasn t very tasty but filled us up for the day.
The Italian place we had our lunch at. A picture of Matteos, where we had our lunch.
Budapest has four metro lines, and we had been to three of them, so we decided to try the remaining line, which was the M1 line. It is the oldest line in the city and has a different vibe than the modern lines. This line was opened in 1896, one of the oldest subway systems in the world. The coaches were much smaller than the other metro lines, and the seating arrangement was something you would expect from a bus than a typical metro train. We rode all the way to the last stop, Mexik i t. Upon going outside, we found out there wasn t much to do here. At this point, I checked the map and realized that Heroes Square is just a couple of metro stations away. Heroes Square is a tourist attraction in Budapest. It is located in Zugl a and is a historically significant place in Budapest. It has a monument which features the Seven chieftains of the Magyars.
M1 line station and tracks. M1 line station and tracks. It is the oldest metro transit of Budapest and one of the oldest in the world. It started operations in 1896.
Here, our unlimited public transport pass was handy because if it was paid per trip, we would think of the stop as a wasted one because we would have to buy a ticket again, but in this case we could just hop on again without any regrets.
A metro train entering a station. An M1 line metro train entering the station.
So we took the M1 line again and deboarded at H s k tere station, followed by walking to the square. After roaming around for a while, we saw a trolleybus and decided to ride on that.
Heroes' Square Heroes Square.
A trolleybus This is the trolleybus we took in Budapest.
A trolleybus is an electric bus that is powered by overhead electric cables. It is like a tram but runs on roads instead of tracks. We got down at D zsa Gy rgy t metro station. Then we took a metro to our hotel. Before going to the hotel, we went to a place to eat something. We had coffee and l ngos. L ngos is a deep-fried Hungarian dish, which looks exactly like the Indian flatbread bhatura. I found it tasty, but since it was deep-fried, that was almost a given.
A deep friend dish called L ngos. L ngos a dish which looks like the Indian flatbread bhatura.
The next day we went to Vienna the capital of Austria which I have already posted about. Check it out here. I had a good time in Budapest, and it is a beautiful city with good public transport and some amazing sites to visit. That s it for now, and see you next time! Credits: Thanks Dione and Badri for proofreading.

24 May 2026

Vincent Bernat: Sharding a routing table for lock-free reads in Go

To associate routing information like AS paths or BGP communities to flows, Akvorado can import routes through the BGP Monitoring Protocol (BMP). As the Internet routing table contains more than 1 million routes, Akvorado needs to scale to tens of millions of routes.1 This has been a long-standing challenge,2 but I expect this issue is now fixed by using RIB sharding, a method that splits the routing database into several parts to enable concurrent updates.

Previous implementation Akvorado connects 2 elements to build its RIB:
  1. a prefix tree, and
  2. a list of routes attached to each prefix.
Akvorado BMP RIB implementation before sharding with the memory layout of each structure and a single lock.
Akvorado BMP RIB implementation without sharding. One single read/write lock.
In the diagram above, the RIB stores five IPv4 prefixes and two IPv6 prefixes. One of them, 2001:db8:1::/48, contains three routes:
  • from peer 3, next hop 2001:db8::3:1, AS 65402, AS path 65402, community 65402:31,
  • from peer 4, next hop 2001:db8::4:1, same ASN, AS path, and community,
  • from peer 5, next hop 2001:db8::5:1, AS 65402, AS path 65401 65402, community 65402:31.
The rib structure is defined in Go as follows:
type rib struct  
    tree          *bart.Table[prefixIndex]
    routes        map[routeKey]route
    nlris         *intern.Pool[nlri]
    nextHops      *intern.Pool[nextHop]
    rtas          *intern.Pool[routeAttributes]
    nextPrefixID  prefixIndex
    freePrefixIDs []prefixIndex
 
The prefix tree uses the bart package, an adaptation of Donald Knuth s ART algorithm. The benchmarks demonstrate it outperforms other packages for lookups, insertions, and memory usage.3 Plus, the author is quite helpful.

Storing routes in a map The list of routes for each prefix is not stored directly in the prefix tree: it would put too much pressure on the garbage collector by allocating per-prefix arrays. Instead, the RIB assigns a unique 32-bit prefix identifier for each prefix, either by picking the last available prefix identifier from the freePrefixIDs array if any, or using the nextPrefixID value before incrementing it. Then, the routes are stored in the routes map, leveraging the optimized Swiss table in Go. To retrieve routes attached to a prefix, we look them up one by one in the routes map with a 64-bit key combining the 32-bit prefix index with a 32-bit route index matching the position of the route in the list. Akvorado scans routes from the first to the last to find the best one.4 It knows there is no more route if the route key returns no result.
type prefixIndex uint32
type routeIndex uint32
type routeKey uint64

Interning routes A route contains a BGP peer identifier, a partial NLRI5, the next hop, and the attributes.
type route struct  
    peer       uint32
    nlri       intern.Reference[nlri]
    nextHop    intern.Reference[nextHop]
    attributes intern.Reference[routeAttributes]
    prefixLen  uint8
 
type nlri struct  
    family bgp.Family
    path   uint32
    rd     RD
 
type nextHop netip.Addr
type routeAttributes struct  
    asn              uint32
    asPath           []uint32
    communities      []uint32
    largeCommunities []bgp.LargeCommunity
 
To save memory and allocations, NLRI, next hops, and route attributes are interned : a 32-bit integer replaces the real value. The mechanism predates the unique package introduced in Go 1.23. We keep it because it has different trade-offs:
  • It uses explicit reference counting instead of relying on weak pointers.
  • It works with non-comparable values implementing Hash() and Equal() methods.6
  • It uses explicit pool instances. This will be useful for sharding.
  • It has better performance. See for example this benchmark.
  • It consumes half the memory thanks to unsigned 32-bit references instead of pointers.
  • But it is not safe for concurrent use.

Why does it not scale?

Note At AS 12322, we don t use BMP yet.7 But Gerhard Bogner had the patience, availability, and technical skills to help me debug this issue.

The global read/write lock is a bottleneck in this implementation. But how? There are several users of the RIB, each with its own set of constraints:
  • The Kafka workers look up the RIB to enrich flows with routing information. They are bound by the number of Kafka partitions.8 Akvorado also adjusts their number to ensure efficient batching to ClickHouse. On our setup, the number of workers oscillates between 8 and 16. As we want to observe the latest data, we cannot afford for the Kafka workers to lag too much.
  • The monitored routers send route updates through the BMP protocol. When connecting, they can send millions of routes.9 After the initial synchronization, updates are sent continuously and may spike from time to time. The router detects a stuck BMP station when its TCP window is full and resets the session in this case. While Akvorado implements a large incoming buffer, it still needs to update the received routes with the write lock held fast enough to avoid being detected as stuck.
  • When a remote BGP peer goes down, Akvorado flushes the associated routes by walking the RIB with the write lock held. When a monitored router goes down, Akvorado waits a bit but eventually flushes all the associated routes.
In short: on a busy setup, lock contention is high for both readers and writers, and neither can lag too much behind.

RIB sharding

First step: basic sharding To remove the global lock, the RIB is split into several shards, each one handling a subset of the prefixes:
Akvorado BMP RIB implementation after sharding with the memory layout of each structure and one lock per shard.
Akvorado BMP RIB implementation with sharding.
The prefix tree stays global and is protected by a single lock. Each shard gets its read/write lock, its route map, and its intern pools to store NLRIs, next hops, and route attributes, which would not have been possible with Go s unique package. The prefix indexes are also sharded: the 8 most significant bits are the shard index and the 24 remaining bits are the local prefix index. Gerhard confirmed that after this blind change, the BMP receiver chugged steadily. Later, I wrote a concurrent benchmark over half a million synthetic but plausible routes10 partitioned over 0 to 8 writers, churning routes as fast as possible, while 1 to 16 readers continuously look up a set of 10,000 routes. I don t know if this benchmark is realistic, but it confirms the improvements for both read and write latencies:
Two heatmaps. One for read latency ratio, the other for write latency ratio. Both of them comparing the speedup with colored tiles between the code before sharding and after sharding. Most tiles are green.
Read and write latency performance improvement after sharding.
It also shows that a high number of writers degrades read latency.

Second step: lock-free reads The single read/write lock protecting the prefix tree is the next target. The bart package provides alternative mutation methods returning an updated tree using copy-on-write. Readers don t need the global lock any more, leaving it only to synchronize writers. The prefix tree is boxed in an atomic pointer.
Akvorado BMP RIB implementation for sharding with lock-free reads. It shows the memory layout of each structure.
Akvorado BMP RIB implementation with sharding and lock-free reads.
Without a lock, readers can now fetch a stale prefix index when walking their copy of the tree if a concurrent writer removes the last route attached to this prefix index and recycles it for another prefix. To avoid this issue, we combine the prefix index with a generation number and store them in the tree:
type generation uint32
type prefixRef struct  
    idx prefixIndex
    gen generation
 
type rib struct  
    mu     sync.Mutex
    tree   atomic.Pointer[bart.Table[prefixRef]]
    shards []*ribShard
 
Each shard stores the generation number for each local prefix index. The generation number increases by one if the associated prefix index is freed. When looking up the routes attached to a prefix index, the reader checks if the generation number matches. Otherwise, it assumes the index was recycled and the list of routes is empty.11 You can see this case in the diagram above for prefix index 5, stored with a generation index of 3, while the current value in the []generations array is 4. The generation number could overflow, but it is not a problem as lookups are quick. Running the concurrent benchmark against this new implementation shows the improvements for the read latency as soon as the cost of the copy-on-write prefix tree is amortized.
Six heatmaps. Three for read latency ratio, three others for write latency ratio. They compare the numbers without sharding, with sharding, and with lock-free reads, pair by pair. For read latency, most tiles are green, showing an improvement of the second step. For write latency, the speedup is negative for a low number of readers.
Read and write latency performance improvement after lock-free reads. The middle column shows the cumulative improvements of both steps.

Among the multiple attempts to optimize the BMP component, RIB sharding is one of the more satisfying. Akvorado 2.2 implements the first step. PR #2433, drafted while writing this blog post, implements the second step and was released with Akvorado 2.4.

  1. Each router exporting flows doesn t need to send its routes. When Akvorado does not find a route from a specific device, it falls back to a route sent by another device. It is up to the operator to decide if this is a good enough approximation.
  2. I made many attempts to scale the BMP component. See for example PR #254, PR #255, PR #278, PR #2244, and PR #2245. Despite these efforts, this component remained problematic for some users. See discussion #2287 as the latest example.
  3. It keeps improving: bart 0.28.0 features a new implementation that trades a bit of memory for greater lookup performance. I did not test it yet, as I have been preparing this blog post for a couple of months already.
  4. Akvorado prefers the route matching the exact next hop. Otherwise, it falls back to any other route. This is an approximation. An alternative would be to have one prefix tree for each BGP peer but it would require configuring all routers to export their routes. pmacct s BMP daemon implements this approach.
  5. If we consider the BGP RIB as a database, the Network Layer Reachability Information (NLRI) is the primary key. Its content depends on the BGP family. With IPv4 or IPv6 unicast, this is the prefix. For VPNv4 and VPNv6 families, it includes the route distinguisher. If you enable the ADD-PATH extension, the NLRI also contains a path identifier. In our implementation, we don t store the prefix as we get it from the looked-up IP address using the prefix length stored separately.
  6. The Hash() methods rely on the hash/maphash package and on the unsafe package to avoid memory copies. See for example the Hash() function for the nlri structure.
  7. Despite being an author or co-author of the first BMP-related RFCs since 2016 (RFC 7854, RFC 8671, RFC 9069), Cisco did not implement it in a usable way in IOS XR until version 24.2.1. We still need to upgrade a few routers to enable this feature.
  8. KIP-932 introduces, in Kafka 4.2, the concept of share groups to enable cooperative consumption on the same partition. This is not supported in Akvorado yet.
  9. You can configure BMP to send routes for each BGP peer before or after applying the incoming policies. In this case, you can get more than one million routes for each transit peer. You can also tell BMP to send the local RIB, which only contains the best path for each prefix.
  10. The prefixes are random, but the prefix size distribution and the AS path length distribution follow the data provided by Geoff Huston.
  11. Alternatively, we could retry the lookup, but it would be pointless: the RIB is an eventually consistent database, and an empty list was a correct answer at some point in the recent past.

23 May 2026

Petter Reinholdtsen: Command line Norse God of Wind Hr svelg move the clouds

A while back, I came across the AI Fabric system created by Daniel Miessler. I liked its approach of providing command-line tools for filtering text using artificial idiocy services, allowing stepwise operations to be applied to a piece of text. The output of one operation can then serve as the input for another in other words, Unix pipeline processing powered by large language models. I do no longer remember exactly how I discovered it, but suspect it was via Matthew Berman's video "How To Install Fabric - Open-Source AI Framework That Can Automate Your Life". While the idea and concept behind AI Fabric appealed to me, its implementation has continued to rub me the wrong way. It started off as a Python project that I could only get running by downloading random programs from the internet using Poetry. I tried to assess how much work it would take to package all its missing dependencies for Debian. However, before I got very far, the project shifted away from Python and over to Go. This new implementation also relied on a build system that seemed to encourage users to run arbitrary code downloaded from the internet to get software working, and further moved to a language I do not master as well as Python. The change bothered me enough that I set my effort to set up a working command line LLM tool in Debian aside for several months. By chance, I came across a simple Python recipe in January demonstrating how to communicate with a llama.cpp API server. I had already been working on packaging llama.cpp for Debian together with the rest of Debian's AI team, and was fortunate enough to own a working instance with a 24 GiB VRAM GPU from AMD, allowing me to run useful models. Until that point, I had only used the basic web client provided by the Debian package, lacking the spare time to explore what else could be done. Then, I found this simple 50 line Python script demonstrating how to interact with llama.cpp's OpenAI-compatible API. I decided to revive the AI Fabric concept, and implement the Unix pipeline filter tool with as few dependencies as possible. It is now operational and working very well, relying solely on standard Python features. The tool include a copy of the LLM recipes from the AI Fabric project (called "patterns"), enabling easy access to request summaries, translations, code review and other useful tasks. Several hundred patterns are included, though I have only tested about ten so far. The LLM API server can be specified in ~/.config/hraesvelgr/config.ini like this:
[server]
url=https://some.llm.example.com:8080/v1/
model=Qwen/Qwen3.6-27B-FP8
With this configuration in place (you can also specify these values directly on the command line), you can specify a pattern and a file to process like this:
% bin/hraesvelgr --pattern explain_code bin/hraesvelgr
EXPLANATION:
This Python script is a client tool for interacting with an AI
service (likely a local LLM server) to process text using prompts
defined in the "AI Fabric" repository. It reads system and user
prompts from markdown files, sends them along with input text to a
chat completion API endpoint, and prints the generated response.
Key components:
1. It uses argparse for command-line argument parsing
2. The  send_chat_completion_request  function formats messages
   (system, user, query) into JSON and sends them via HTTP POST to
   an AI service endpoint
3.  read_file  function reads markdown files, replacing placeholders
   like  lang_code  with actual values from arguments
4. In main():
 - Parses command-line arguments for input file, API base URL,
   pattern type, language code, and debug flag
 - Ensures the base URL ends with a slash
 - Reads system prompt from data/patterns/ pattern /system.md
 - Optionally reads user prompt from data/patterns/ pattern /user.md
 - Reads input text either from stdin (when "-" is passed) or a file
 - Handles encoding fallback to ISO-8859-1 if UTF-8 fails
 - Sends the formatted request to the AI service and prints the response
The script assumes it's running in a directory containing a git
clone of https://github.com/danielmiessler/fabric/, which contains
the necessary prompt files.
This tool is designed to interface with local LLM servers that
support OpenAI-compatible chat completion APIs.
%
The list of available patterns can be viewed by running bin/hraesvelgr --list-patterns. I have found the summarize, translate, improve_writing, review_code, and explain_terms_and_conditions patterns particularly useful. For example using the latter combined with a text based web browser capable of dumping a page as plain text, can be done like this (originally formatted in markdown, I converted to HTML using pandoc for easier readability):
% w3m  -dump https://runbox.com/about/terms-service/   \
  hraesvelgr --pattern explain_terms_and_conditions
Executive Summary
This is a transparent, privacy-focused contract from
a Norwegian provider that generally respects user data rights and
operates under strict EU/EEA standards. However, it carries
strict liability limitations and an aggressive
data-deletion policy upon cancellation. The vibe is  Professional &
Privacy-First,  but you must manage your own backups and understand that
the company heavily shields itself from financial responsibility during
technical failures.
Key Takeaways
  • Your Data Stays Yours: Section 10.2 explicitly states Runbox will never use your transmitted or stored data for commercial purposes. This is a major privacy win.
[... trimmed output, as it is not the focus of this blog post ...] If you sign:
  1. Set up automated backups immediately. Use IMAP sync to a local drive or a secondary email provider before storing any critical documents or emails. Do not rely on Runbox as your only archive.
  2. Mark your calendar for the 30-day trial end date. Miss the payment window, and access closes instantly with no recovery period.
  3. Monitor price changes at renewal. Since they can adjust fees anytime, check their pricing page a few days before your subscription renews to avoid unexpected charges.
NO FORCED ARBITRATION CLAUSE FOUND.
REFUND POLICY IS STRICTLY CONDITIONAL (see Sections 4.2 4.5).
As you might have already noticed, I name my project after the Norse God of Wind. I found a nice description of the origin of the name on Wikipedia:
In Vaf r nism l (The Lay of Vaf r nir), Odin questions the wise j tunn Vaf r nir about the origin of the wind, and the j tunn answers:
He is called Hr svelg,
who sits at heaven s end,
a giant, in the shape of an eagle;
from his wings
they say the wind comes over all people.
(translated by John Lindow in Norse Mythology: A Guide to Gods, Heroes, Rituals, and Beliefs 2002)
The latest version of the code can be found at https://codeberg.org/pere/hraesvelgr/. Perhaps you will find it as useful as I did? As usual, if you use Bitcoin and wish to show your support of my activities, please send Bitcoin donations to my address 15oWEoG9dUPovwmUL9KWAnYRtNJEkP1u1b.

21 May 2026

Amin Bandali: ffs 0.2.2 released

ffs provides a minor mode for simple plain text presentations in Emacs, where the slides are separated using the page-delimiter, by default the form feed character (^L). I wrote ffs in early 2022 for my LibrePlanet 2022 presentation the Net beyond the Web, and earlier this year decided to polish it towards being a proper package and submit it to GNU ELPA. The manual still needs some more work, but the overall package is in pretty good shape so I submitted for inclusion in GNU ELPA. ffs and I owe a debt of gratitude to Protesilaos for rounds of code review and feedback for improving and polishing the package in preparation for submission to GNU ELPA. You can watch videos of these sessions posted earlier on my website: Further, inspiration for parts of ffs's implementation was gratefully drawn from Protesilaos's Logos package for Emacs. Dedicated to the loving memory of Farangis Yousefinia. Below are the release notes.

Version 0.2.2 on 2026-05-21
First release of ffs on GNU ELPA. The attempted build of ffs 0.2.1 within GNU ELPA build sandbox failed with an Error: void-function (org-texinfo-kbd-macro) due to use of #+macro: kbd (eval (org-texinfo-kbd-macro $1)) in ffs.org for better formatting of key sequences in the exported Texinfo copy. This seems to have happened for the specific case of generating a plain text README using ox-ascii where ELPA didn't load ox-texinfo. To try and mitigate this, a README.md has been added for use as the package README instead of ffs.org. If not sufficient, a Texinfo copy of the ffs manual will be shipped instead of the Org one in the next release. ffs 0.2.2 also includes small fixes and improvements throughout ffs.el from Stefan Monnier, and additional feedback to be addressed in future releases.

Version 0.2.1 on 2026-05-20
The attempted build of ffs 0.2.0 within GNU ELPA build sandbox failed with a "Cannot include file" error on the "#+include: fdl.org" in the manual. So, as a workaround, we switch to using the official Texinfo copy of the GNU FDL license rather than an Org copy.

Version 0.2.0 on 2026-05-19
First release of ffs intended for GNU ELPA. After a few years of inactivity, in early 2026 I decided to dust off ffs.el, polish and document it, and offer for inclusion in GNU ELPA as a proper package.

Default value of ffs-default-face-height changed to nil
To minimize unexpected and/or unnecessary changes out-of-the-box, the default value of ffs-default-face-height has been changed to nil.

ffs-edit-buffer-name demoted from user option to variable
This is not an important user-facing setting, so to help avoid overwhelming users with many options, this has been demoted from a user option to a variable.

Several new user options for customizing ffs's behaviour
As part of the effort to bring ffs more in line with the conventions of other existing Emacs packages, the mechanisms for toggling various parts of Emacs's interface to minimize visual clutter were changed from being minor modes to being customizable user options. These are the replacement new user options, with a default value of nil:
  • ffs-hide-cursor
  • ffs-hide-mode-line
  • ffs-hide-header-line
Their value is buffer-local, and may be set globally using setq-default. See the sample configuration in the manual for an example of how to customize them. The new ffs-page-delimiter user option defines the page delimiter inserted by ffs-edit-done when inserting a new slide. Emacs's page-delimiter regexp should be able to match ffs-page-delimiter's value, so if you use a custom page-delimiter be sure to customize ffs-page-delimiter accordingly. The new ffs-echo-progress user option controls whether to display in echo area the progress through the slides. When non-nil, changing slides will also display the progress through the slides in the echo area. The format of the displayed progress can be customized using the new ffs-echo-progress-format user option. The new ffs-edit-display-buffer-alist user option may be used to control the Window configuration for the ffs-edit buffer. By default, it will display the ffs-edit buffer in the same window. The new ffs-edit-done-hook user option may be used to define hooks to be run at the end of ffs-edit-done after returning to the main ffs presentation buffer. Lastly, a new ffs-find-speaker-notes-function variable was added to allow customizing the find function used for opening the speaker's notes file, defaulting to find-file-other-frame.

Version 0.1.0 on 2022-05-19
Initial publication of ffs.el as part of my personal configurations for GNU Emacs. My first attempt at this concept was a now-archived ffsanim.el, a major mode implementation that used Emacs's animate library to animate slide texts onto the screen. Shortly after realizing the shortcomings of that approach, I abandoned it in favour a minor mode implementation and published version 0.1.0 of what is now ffs in my personal configs repository. I used this implementation for presenting my LibrePlanet 2022 talk, The Net beyond the Web. I picked "ffs" as the package name, the acronym for form feed slides.

Dirk Eddelbuettel: nanotime 0.3.15 on CRAN: Coping

Another very minor update, now at 0.3.15, for our nanotime package is now on CRAN, and has been built for r2u and Debian. nanotime relies on the RcppCCTZ package (as well as the RcppDate package for additional C++ operations) and offers efficient high(er) resolution time parsing and formatting up to nanosecond resolution, using the bit64 package for the actual integer64 arithmetic. Initially implemented using the S3 system, it has benefitted greatly from a rigorous refactoring by Leonardo who not only rejigged nanotime internals in S4 but also added new S4 types for periods, intervals and durations. This release adjusts the package for the maybe overly hasty switch R 4.6.0 has undertaken with respect to using C++20 as a default C++ compilation standard. I am of course largely in favour of such a switch to more modern C++. But I am also cognizant of the fact that not all compilers and machines are ready. And just as I have already seen one other package fail to compile on a particular CRAN system (!!) under C++20, this package all of a sudden, and only on that same system, started to throw two (harmless) compiler warnings. We could call these erroneous as newer versions of the same compiler do not throw them but it does not matter. The decision to default to C++20 has been made, and now we live with it. But maybe some hardware platforms should be moved behind the barn. Either way, this release both adds an explicit cast to two lines that may not really need it (but this will not hurt) and also dials the compilation standard down to C++17 on one particular platform. So once again there are no user-facing changes, or behavioural changes or enhancements, in this release. The NEWS snippet below has the fuller details.

Changes in version 0.3.15 (2026-05-21)
  • Add extra const_cast as one CRAN machine with more ancient setup whines otherwise and is obviously less C++20 ready than it thinks
  • tools/configure also checks where this is being built and as needed' downgrades the compilation to C++17

Thanks to my CRANberries, there is a diffstat report for this release. More details and examples are at the nanotime page; code, issue tickets etc at the GitHub repository and all documentation is provided at the nanotime documentation site.

This post by Dirk Eddelbuettel originated on his Thinking inside the box blog. If you like this or other open-source work I do, you can now sponsor me at GitHub. You can also sponsor my Tour de Shore 2026 ride in support of the Maywood Fine Arts Center.

20 May 2026

Michael Prokop: The mysterious XF86AudioPlay issue

I was getting <XF86AudioPlay> is undefined in the status bar of Emacs displayed every 2-3 seconds. Nowhere else I noticed any misbehavior or problems, and also couldn t find any related log entries. It didn t stop, though didn t want to reboot my system to see whether that would fix the problem, but it was driving me nuts. Now, as a starting point I adjusted my sway configuration, to react to the XF86AudioPlay key press event:
bindsym XF86AudioPlay exec playerctl play-pause
After reloading sway, my music player started to play for 2-3 seconds, stopped playing, started again, etc. It wasn t a Emacs bug, but something indeed seemed to send the XF86AudioPlay key event every 2-3 seconds. It wasn t my USB keyboard or any stuck key on it, as verified also by unplugging it. So which device was causing this? libinput from libinput-tools to the rescue:
% sudo libinput debug-events
[...]
-event12  KEYBOARD_KEY                 +0.000s  KEY_PLAYPAUSE (164) pressed
 event12  KEYBOARD_KEY                 +0.000s  KEY_PLAYPAUSE (164) released
 event12  KEYBOARD_KEY                 +2.887s  KEY_PLAYPAUSE (164) pressed
 event12  KEYBOARD_KEY                 +2.887s  KEY_PLAYPAUSE (164) released
 event12  KEYBOARD_KEY                 +5.773s  KEY_PLAYPAUSE (164) pressed
 event12  KEYBOARD_KEY                 +5.774s  KEY_PLAYPAUSE (164) released
[...]
The event12 device was sending this event, what s behind this?
% sudo udevadm info /dev/input/event12
P: /devices/pci0000:00/0000:00:1f.3/skl_hda_dsp_generic/sound/card0/input17/event12
M: event12
R: 12
J: c13:76
U: input
D: c 13:76
N: input/event12
L: 0
S: input/by-path/pci-0000:00:1f.3-platform-skl_hda_dsp_generic-event
E: DEVPATH=/devices/pci0000:00/0000:00:1f.3/skl_hda_dsp_generic/sound/card0/input17/event12
E: DEVNAME=/dev/input/event12
E: MAJOR=13
E: MINOR=76
E: SUBSYSTEM=input
E: USEC_INITIALIZED=12468722
E: ID_INPUT=1
E: ID_INPUT_KEY=1
E: ID_INPUT_SWITCH=1
E: ID_PATH=pci-0000:00:1f.3-platform-skl_hda_dsp_generic
E: ID_PATH_TAG=pci-0000_00_1f_3-platform-skl_hda_dsp_generic
E: XKBMODEL=pc105
E: XKBLAYOUT=us
E: XKBOPTIONS=lv3:ralt_switch,compose:rctrl
E: BACKSPACE=guess
E: LIBINPUT_DEVICE_GROUP=0/0/0:ALSA
E: DEVLINKS=/dev/input/by-path/pci-0000:00:1f.3-platform-skl_hda_dsp_generic-event
E: TAGS=:power-switch:
E: CURRENT_TAGS=:power-switch:
% sudo udevadm info -a /dev/input/event12   grep -iE 'kernels drivers name'
    KERNELS=="input17"
    DRIVERS==""
    ATTRS name =="sof-hda-dsp Headphone"
    KERNELS=="card0"
    DRIVERS==""
    KERNELS=="skl_hda_dsp_generic"
    DRIVERS=="skl_hda_dsp_generic"
    KERNELS=="0000:00:1f.3"
    DRIVERS=="sof-audio-pci-intel-tgl"
    KERNELS=="pci0000:00"
    DRIVERS==""
Behind this event12 is sof-hda-dsp Headphone, and evtest confirms that:
% sudo evtest
No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0:      AT Translated Set 2 keyboard
/dev/input/event1:      Sleep Button
/dev/input/event10:     ThinkPad Extra Buttons
/dev/input/event11:     sof-hda-dsp Mic
/dev/input/event12:     sof-hda-dsp Headphone
/dev/input/event13:     sof-hda-dsp HDMI/DP,pcm=3
/dev/input/event14:     sof-hda-dsp HDMI/DP,pcm=4
/dev/input/event15:     sof-hda-dsp HDMI/DP,pcm=5
/dev/input/event16:     Yubico YubiKey OTP+FIDO+CCID
/dev/input/event17:     Apple Inc. Magic Keyboard with Numeric Keypad
/dev/input/event18:     Apple Inc. Magic Keyboard with Numeric Keypad
[...]
Select the device event number [0-24]: ^C
We can even get further information:
% sudo evtest /dev/input/event12
Input driver version is 1.0.1
Input device ID: bus 0x0 vendor 0x0 product 0x0 version 0x0
Input device name: "sof-hda-dsp Headphone"
Supported events:
  Event type 0 (EV_SYN)
  Event type 1 (EV_KEY)
    Event code 114 (KEY_VOLUMEDOWN)
    Event code 115 (KEY_VOLUMEUP)
    Event code 164 (KEY_PLAYPAUSE)
    Event code 582 (KEY_VOICECOMMAND)
  Event type 5 (EV_SW)
    Event code 2 (SW_HEADPHONE_INSERT) state 0
Properties:
Testing ... (interrupt to exit)
Event: time 1779295060.175766, type 5 (EV_SW), code 2 (SW_HEADPHONE_INSERT), value 1
Event: time 1779295060.175766, -------------- SYN_REPORT ------------
Event: time 1779295061.951168, type 1 (EV_KEY), code 164 (KEY_PLAYPAUSE), value 1
Event: time 1779295061.951168, -------------- SYN_REPORT ------------
Event: time 1779295061.951194, type 1 (EV_KEY), code 164 (KEY_PLAYPAUSE), value 0
Event: time 1779295061.951194, -------------- SYN_REPORT ------------
Event: time 1779295064.548671, type 1 (EV_KEY), code 164 (KEY_PLAYPAUSE), value 1
Event: time 1779295064.548671, -------------- SYN_REPORT ------------
Event: time 1779295064.548689, type 1 (EV_KEY), code 164 (KEY_PLAYPAUSE), value 0
Event: time 1779295064.548689, -------------- SYN_REPORT ------------
Event: time 1779295067.437172, type 1 (EV_KEY), code 164 (KEY_PLAYPAUSE), value 1
Event: time 1779295067.437172, -------------- SYN_REPORT ------------
Event: time 1779295067.437187, type 1 (EV_KEY), code 164 (KEY_PLAYPAUSE), value 0
Event: time 1779295067.437187, -------------- SYN_REPORT ------------
Event: time 1779295070.323775, type 1 (EV_KEY), code 164 (KEY_PLAYPAUSE), value 1
Event: time 1779295070.323775, -------------- SYN_REPORT ------------
Event: time 1779295070.323790, type 1 (EV_KEY), code 164 (KEY_PLAYPAUSE), value 0
Event: time 1779295070.323790, -------------- SYN_REPORT ------------
Event: time 1779295073.200350, type 1 (EV_KEY), code 164 (KEY_PLAYPAUSE), value 1
Event: time 1779295073.200350, -------------- SYN_REPORT ------------
Event: time 1779295073.200373, type 1 (EV_KEY), code 164 (KEY_PLAYPAUSE), value 0
Event: time 1779295073.200373, -------------- SYN_REPORT ------------
Event: time 1779295076.076228, type 1 (EV_KEY), code 164 (KEY_PLAYPAUSE), value 1
Event: time 1779295076.076228, -------------- SYN_REPORT ------------
Event: time 1779295076.076250, type 1 (EV_KEY), code 164 (KEY_PLAYPAUSE), value 0
Event: time 1779295076.076250, -------------- SYN_REPORT ------------
Event: time 1779295078.961740, type 1 (EV_KEY), code 164 (KEY_PLAYPAUSE), value 1
Event: time 1779295078.961740, -------------- SYN_REPORT ------------
Event: time 1779295078.961754, type 1 (EV_KEY), code 164 (KEY_PLAYPAUSE), value 0
Event: time 1779295078.961754, -------------- SYN_REPORT ------------
Event: time 1779295081.850156, type 1 (EV_KEY), code 164 (KEY_PLAYPAUSE), value 1
Event: time 1779295081.850156, -------------- SYN_REPORT ------------
Event: time 1779295081.850175, type 1 (EV_KEY), code 164 (KEY_PLAYPAUSE), value 0
Event: time 1779295081.850175, -------------- SYN_REPORT ------------
Event: time 1779295083.306612, type 5 (EV_SW), code 2 (SW_HEADPHONE_INSERT), value 0
Event: time 1779295083.306612, -------------- SYN_REPORT ------------
So when I plug in my headphone (see the SW_HEADPHONE_INSERT event), the unexpected behavior starts, unplugging stops the problem.
Good! But what was totally unexpected for me: my headphone, being a Beyerdynamic DT-990 Pro, does not have any keys. 8-) As it turned out, the headphone jack seemed to have been not entirely clean. The analog side of the jack triggers a behavior within the audio codec, where it seems to interpret the fluctuating impedance as a play button of the headset, being pressed, again and again. I cleaned the jack of my headphone and my XF86AudioPlay problem is gone, case closed.

18 May 2026

Sergio Durigan Junior: Fixing a 20+ year old bug in Debian curl

I have been helping co-maintain the Debian curl package for a few years now, and even though Samuel and Charles do most of the work, I'm happy to jump in and help when needed. This is one of those cases. Nowadays the package is maintained by 3 people (with help from others occasionally), but it hasn't always been like this. Samuel adopted the package back in 2021, and since then it has received a lot of love and care to make sure it lives up to Debian's standards. Again, kudos to both him and Charles who have been doing great work on this front. But a little more than 20 years ago, the situation in Debian (and curl!) was "a bit" different.

Once upon a time... According to d/changelog, the Debian curl maintainer in 2005 introduced changes to the packaging that allowed it to generate a version of libcurl for each TLS backend available: OpenSSL and GnuTLS. This meant that curl would have two binary library packages:
  • libcurl3-openssl and its respective -dev variant, for libcurl linked against OpenSSL; and
  • libcurl3-gnutls and its respective -dev variant, for libcurl linked against GnuTLS.
But then, around 2006/2007 or so, upstream curl decided to bump the SONAME version of libcurl from 3 to 4. At the time, they apparently did not version their library symbols like they do now, which was... less than ideal. I don't judge them: curl and a lot of other important projects have come a long way when we consider best practices to write shared libraries. Meanwhile, on Debian land, the release team was having trouble with other transitions going on at the time. For those who are not versed in Debian's vocabulary, a transition happens when a shared library gets its SONAME version bumped: when this happens, we have to make sure that all reverse dependencies of that library still build with the new version, and fix things that fail. The more reverse dependencies the library has, the harder this work gets. When upstream curl bumping the SONAME version of libcurl, the Debian curl maintainer at the time correctly renamed the binary packages from libcurl3- openssl,gnutls (and their -dev variants) to libcurl4- openssl,gnutls (and their -dev variants), which obviously triggered a transition. And a big one, because libcurl is used by several projects. Long story short, the Debian release team found themselves between a rock and a hard place. According to the late Steve Langasek at the time:
We talked a while back about the curl transition, and about how upstream's change from libcurl.so.3 to libcurl.so.4 is gratuitously painful for us in light of the large number of reverse dependencies. The libcurl transition has at this point gotten tangled with soname transitions in jasper, exiv2, kexiv2, and God only knows what else. So I'd like to revisit this question, because tracking this transition is costing the release team a lot of time that would be better spent elsewhere, and removing the need for a libcurl transition promises to reduce the complexity of the other components by an order of magnitude. On looking at the curl package, I've come to understand that the symbol versioning in place in this library is the result of a Debian-local patch. That's great news, because it suggests a solution to this quandary that doesn't require an unreasonable amount of developer time.
Yeah, it wasn't pretty. Here's what was proposed:
I am proposing the following:
  • Keep the library soname the same as it currently is upstream. Because upstream uses unversioned symbols, our package will be binary-compatible with applications built against the upstream libcurl regardless of what we do with symbol versioning, so leaving the soname alone minimizes the amount of patching to be done against upstream code here.
  • Revert the Debian symbol versioning to the libcurl3 version, and make libcurl.so.3 a symlink to libcurl.so.4. We have already established that libcurl.so.4 is still API-compatible with libcurl.so.3, in spite of the soname change upstream; reverting the symbol versioning will make it fully ABI-compatible with libcurl.so.3, and adding the symlink lets previously-built binaries find it.
  • Revert the Debian package names to the curl 7.15.5 versions. Because compatibility has been restored with libcurl3 and libcurl3-gnutls, restoring the package names provides the best upgrade path from etch to lenny; and because the symbol versions have been reverted, the libraries are not binary-compatible with the Debian packages currently named libcurl4/libcurl4-gnutls/libcurl4-openssl (in spite of being binary-compatible with upstream), so it would be wrong to keep the current names regardless.
  • Drop the SSL-less variant of the library, which was not present in curl 7.15.5; AFAICS, there is no use case where a user of curl needs to not have SSL support, so this split seems to be unnecessary overhead. Please correct me if I'm mistaken.
  • Leave the -dev package names alone otherwise, to simplify binNMUing of the reverse-dependencies (some packages have already added versioned build-deps on libcurl4.*-dev -- I have no idea why -- so reverting the names would mean more work to chase down those packages). Drop libcurl4-dev as a binary package, though, in favor of being Provided by libcurl4-gnutls-dev. Many of the packages currently build-depending on libcurl4-dev -- including some that wrongly used libcurl3-dev before -- are GPL, and these are apparently all packages where having SSL support missing in libcurl4 wasn't hurting them, so libcurl4-gnutls-dev seems to be the reasonable "default" here.
  • Schedule binNMUs for all reverse-dependencies.
Again, no judgement here: this was what needed to be done at the time, and I believe it was a good solution given the circumstances. In the end, the binary library packages got renamed again: from libcurl4- openssl,gnutls back to libcurl3- openssl,gnutls (but not their -dev variants!), but they continued shipping libcurl libraries whose SONAME version was 4. This solved the immediate problem of untangling the transitions mentioned by Steve, but introduced a technical debt that would stick with the package literally for decades. The situation at the end of 2007 was:
  • libcurl3-openssl with libcurl4-openssl-dev; and
  • libcurl3-gnutls with libcurl4-gnutls-dev.

More discrepancy is added Eventually the libcurl3-openssl package got renamed to libcurl3, but aside from that the situation with mismatched library names vs. SONAME versions stayed relatively unchanged until around 2018, when the Debian curl maintainer at the time (a different person) renamed libcurl3 to libcurl4 to fix a bug. This was the right thing to do for libcurl3, and at the time upstream curl was already properly versioning their symbols, but for some reason libcurl3-gnutls got left behind. So now we had:
  • libcurl4 with libcurl4-dev; and
  • libcurl3-gnutls with libcurl4-gnutls-dev.
In other words, we now have a discrepancy between the OpenSSL and GnuTLS variants' names. Yeah, confusing. And this is the situation right now, on May 2026, while I write this post. To make matters worse, the Debian curl package has been carrying a patch to facilitate the split of OpenSSL and GnuTLS flavours for decades now, and, for some reason I didn't bother to investigate, the patch pins the SONAME version of libcurl3-gnutls to CURL_GNUTLS_3, effectively overriding upstream's decision to version the symbols as CURL_GNUTLS_4.

A call to make things right Back in 2022, Simon McVittie filed a Debian bug to try and call our attention to the fact that we were shipping this messy set of curl packages. I had just started to get involved in the package maintenance and Samuel asked me to take a look at the bug. I noticed it was going to take more time than I had available, so I decided to put it in my TODO list (TM). Simon was generous enough to lay out a possible plan to tackle the problem, but I had a feeling that this was going to be harder than it looked. I kept postponing working on the bug, but also kept thinking about it now and then because it's an interesting thing to solve. Then, a month or so ago the Debian Brasil community got together for MiniDebConf Campinas 2026 and we decided to do a bug squashing party there. I started working on a few FTBFS bugs with GCC 16, but then got remembered about the curl bug and thought that that was the perfect time and place to start working on it, for a few reasons:
  • Samuel and Charles were also attending the conference, so I could talk to them about my plans and show them a PoC.
  • I was going to give a presentation about symbols (in pt_BR), so I could use this bug as an example of symbol versioning.
  • I wanted to have fun.

The initial plan The plan I had in mind was a variant of Simon's proposed plan:
  • I would have to adjust our GnuTLS-specific patch so that it did not override the SONAME version for libcurl-gnutls. Then,
  • For each symbol from libcurl3-gnutls I would have to:
    • Explicitly version it as curl_symbol_name@@CURL_GNUTLS_4.
    • Create an alias for the symbol (let's call it __curl_compat_symbol_name).
    • Explicitly version this alias as __curl_compat_symbol_name@CURL_GNUTLS_3.
  • Have a separate version of curl's linker script to make it possible to create a hierarchy between CURL_GNUTLS_3 and CURL_GNUTLS_4 symbols.
Note that this whole dance is needed because it is a hard requirement that programs linked against libcurl3-gnutls keep working when we ship libcurl4-gnutls, without needing to recompile them. Due to the fact that we will not really bump the SONAME of libcurl-gnutls (but instead fix the symbol versions shipped by it), we cannot expect programs to break given that they are actually using the exact same ABI as before. Unfortunately (as it is common with low level tools) the documentation for ld's versioning syntax is quite incomplete and hard to find. One of the best sources I found was this blog post. For this reason, let me quickly explain the different notations for symbol versioning used above.

curl_symbol_name@@CURL_GNUTLS_4 When we use curl_symbol_name@@CURL_GNUTLS_4 (note the @@) we are telling the linker that this should be considered the default version of curl_symbol_name. In other words, when a binary that links against libcurl-gnutls calls curl_symbol_name, the linker should use curl_symbol_name@@CURL_GNUTLS_4 to resolve the symbol. There are a few ways to specify a symbol version in C/C++:
__attribute__((__symver__("curl_symbol_name@@CURL_GNUTLS_4")))
void curl_symbol_name()
 
  /* ... */
 

/* or... */
void curl_symbol_name()
 
  /* ... */
 
__asm__(".symver curl_symbol_name, curl_symbol_name@@CURL_GNUTLS_4");

Function alias Creating an alias for a function is basically saying that a function can be called by another name. You can do that in C/C++ like:
void curl_symbol_name()
 
  /* ... */
 

void __curl_compat_symbol_name()
  __attribute__((alias("curl_symbol_name")));

__curl_compat_symbol_name@CURL_GNUTLS_3 Finally, when we use __curl_compat_symbol_name@CURL_GNUTL_3 (note the single @) we are telling the linker that this symbol exists, but it should not be used as the default symbol. In fact, this notation will basically hide the symbol and make it only available for those programs that have already been linked against it. It's a way of saying "don't offer this symbol when linking, but it's here in case a program needs it to run" (it's a bit more complicated than that, but you get the point). The reason I had to create an alias to the function before versioning the symbol with @CURL_GNUTLS_3 is because, once I've versioned the main symbol as @@CURL_GNUTLS_4, I can't create another version of it. It's also important to mention that to be able to create a version for the alias I also had to change its visibility to default. In the end, the alias ended up being defined as:
extern void __curl_compat_symbol_name()
  __attribute__((alias("curl_symbol_name"), visibility("default")));

First attempt and lessons learned For my PoC I decided to tackle a small subset of the problem. The symbols file for libcurl3-gnutls contains around 100 symbols that need to be fixed, so I chose two of them and started trying to write a patch to see if I could make things work. And after some time struggling with GCC's syntax and inspecting nm -D's output I finally got something that looked like it was going to work. The two symbols I had chosen to work with got correctly versioned (both as @@CURL_GNUTLS_4 and @CURL_GNUTLS_3), and a quick-and-dirty C program that used those symbols correctly compiled and ran with the expected symbols. I showed the results to Samuel and Charles, we got excited about what we saw, and then the conference ended.

Second attempt and some adjustments After getting back home I resumed the work on my branch and wrote an Emacs function that semi-automatically adjusted all 100+ symbols listed in the symbols file so that they all looked like:
__attribute__((__symver__("curl_symbol_name@@CURL_GNUTLS_4")))
void curl_symbol_name()
 
  /* ... */
 

extern void __curl_compat_symbol_name()
  __attribute__((alias("curl_symbol_name"), visibility("default"),
                 symver("__curl_compat_symbol_name@CURL_GNUTLS_3")));
The patch was big but mostly repetitive, and I was happy to have come up with a solution that looked clean. Until I tried to build the package, that is. I started seeing some strange errors that happened when ld was trying to link the final libcurl4-gnutls object (yes, at that point I had already renamed the binary package). This is one of the errors I was getting from ld (I got variants of this error as I was trying to fix the approach):
/usr/bin/x86_64-linux-gnu-ld.bfd: .libs/libcurl_gnutls_la-easy.o: in function  dupeasy_meta_freeentry':
./debian/build-gnutls/lib/./debian/build-gnutls/lib/easy.c:1024: multiple definition of  curl_easy_cleanup'; .libs/libcurl_gnutls_la-easy.o:./debian/build-gnutls/lib/./debian/build-gnutls/lib/easy.c:908: first defined here
/usr/bin/x86_64-linux-gnu-ld.bfd: .libs/libcurl-gnutls.so.4.8.0: version node not found for symbol curl_easy_duphandle@CURL_GNUTLS3
/usr/bin/x86_64-linux-gnu-ld.bfd: failed to set dynamic section sizes: bad value
This was strange. I did some tests with very simple versions of a shared library using the versioning mechanism I had implemented and it all worked. I could not reproduce the problem, and that's not a great feeling to have. Then, after reading a lot of documentation and blog posts throughout the internet I found something interesting. Apparently ld has a limitation when it comes to dealing with symbols versioned with @@. If there is a single symbol versioned like that in a source file (the actual term is TU, which means Translation Unit, but let's simplify), then ld is happy and generates the expected version without issues. But when we're dealing with multiple definitions of @@ symbols in a source file (which is exactly what happens in curl), then ld can get confused and start giving errors during the link stage. To solve that limitation, we have to resort to yet another symbol versioning notation: @@@. Yes, three at signs. For example:
void curl_symbol_name()
 
  /* ... */
 
__asm__(".symver curl_symbol_name, curl_symbol_name@@@CURL_GNUTLS_4");
Note that we have to use __asm__ because GCC's __attribute__ doesn't support the triple-at notation. What this does is tell the linker to create a versioned symbol for curl_symbol_name, set it as the default symbol when linking, but also remove the unversioned curl_symbol_name symbol. This makes ld happy and allows it to successfully link libcurl-gnutls. As usual, you won't find any mention of the @@@ notation inside ld's documentation. With libcurl-gnutls compiling again, I had to adjust libcurl's linker script to create a hierarchy between CURL_GNUTLS_3 and CURL_GNUTLS_4 symbols. Here's the final version of the file:
CURL_GNUTLS_3
 
  global:
    curl_easy_cleanup;
    /* lots of other symbols here */
  local: *;
 ;

CURL_GNUTLS_4
 
  global: curl_*;
  local: *;
  CURL_GNUTLS_3;

Debian package adjustments After getting the hard part out of the way, the rest was easy. It was time to finally rename libcurl3-gnutls to libcurl4-gnutls. Initially I was thinking that I'd need to ask the release team for a transition to happen, but as it turns out that won't be necessary. Because we are effectively shipping the same exact library/ABI and the only difference is the inclusion of the extra CURL_GNUTLS_4 versioned symbols, and given that we will be shipping CURL_GNUTLS_3 versioned symbols to guarantee backwards compatibility, packages won't need to get rebuild just to pick up the new dependency. Instead, we can safely turn libcurl3-gnutls into a transitional package that depends on libcurl4-gnutls.

Merge request and next steps This is the merge request where I am working on the fix. As of this writing it is in a draft state, but I expect to merge in the next couple of days. Once the fixed curl package is uploaded, we should keep an eye on the archive to make sure no unexpected bugs happen. I would like to carry this patch downstream at least until forky is released. It doesn't make sense to propose it upstream because this problem is Debian-specific and should be fixed there. We will need to make sure that all reverse dependencies of libcurl3-gnutls are recompiled before we can get rid of the transitional package, too. This was a fun bug to investigate and fix, and I am happy that we will finally have sensible names (and symbol versions!) for both of our libcurl variants. Stay tuned for the next challenge!

Sergio Durigan Junior: Fixing a 20+ year old bug in Debian curl

I have been helping co-maintain the Debian curl package for a few years now, and even though Samuel and Charles do most of the work, I'm happy to jump in and help when needed. This is one of those cases. Nowadays the package is maintained by 3 people (with help from others occasionally), but it hasn't always been like this. Samuel adopted the package back in 2021, and since then it has received a lot of love and care to make sure it lives up to Debian's standards. Again, kudos to both him and Charles who have been doing great work on this front. But a little more than 20 years ago, the situation in Debian (and curl!) was "a bit" different.

Once upon a time... According to d/changelog, the Debian curl maintainer in 2005 introduced changes to the packaging that allowed it to generate a version of libcurl for each TLS backend available: OpenSSL and GnuTLS. This meant that curl would have two binary library packages:
  • libcurl3-openssl and its respective -dev variant, for libcurl linked against OpenSSL; and
  • libcurl3-gnutls and its respective -dev variant, for libcurl linked against GnuTLS.
But then, around 2006/2007 or so, upstream curl decided to bump the SONAME version of libcurl from 3 to 4. At the time, they apparently did not version their library symbols like they do now, which was... less than ideal. I don't judge them: curl and a lot of other important projects have come a long way when we consider best practices to write shared libraries. Meanwhile, on Debian land, the release team was having trouble with other transitions going on at the time. For those who are not versed in Debian's vocabulary, a transition happens when a shared library gets its SONAME version bumped: when this happens, we have to make sure that all reverse dependencies of that library still build with the new version, and fix things that fail. The more reverse dependencies the library has, the harder this work gets. When upstream curl bumping the SONAME version of libcurl, the Debian curl maintainer at the time correctly renamed the binary packages from libcurl3- openssl,gnutls (and their -dev variants) to libcurl4- openssl,gnutls (and their -dev variants), which obviously triggered a transition. And a big one, because libcurl is used by several projects. Long story short, the Debian release team found themselves between a rock and a hard place. According to the late Steve Langasek at the time:
We talked a while back about the curl transition, and about how upstream's change from libcurl.so.3 to libcurl.so.4 is gratuitously painful for us in light of the large number of reverse dependencies. The libcurl transition has at this point gotten tangled with soname transitions in jasper, exiv2, kexiv2, and God only knows what else. So I'd like to revisit this question, because tracking this transition is costing the release team a lot of time that would be better spent elsewhere, and removing the need for a libcurl transition promises to reduce the complexity of the other components by an order of magnitude. On looking at the curl package, I've come to understand that the symbol versioning in place in this library is the result of a Debian-local patch. That's great news, because it suggests a solution to this quandary that doesn't require an unreasonable amount of developer time.
Yeah, it wasn't pretty. Here's what was proposed:
I am proposing the following:
  • Keep the library soname the same as it currently is upstream. Because upstream uses unversioned symbols, our package will be binary-compatible with applications built against the upstream libcurl regardless of what we do with symbol versioning, so leaving the soname alone minimizes the amount of patching to be done against upstream code here.
  • Revert the Debian symbol versioning to the libcurl3 version, and make libcurl.so.3 a symlink to libcurl.so.4. We have already established that libcurl.so.4 is still API-compatible with libcurl.so.3, in spite of the soname change upstream; reverting the symbol versioning will make it fully ABI-compatible with libcurl.so.3, and adding the symlink lets previously-built binaries find it.
  • Revert the Debian package names to the curl 7.15.5 versions. Because compatibility has been restored with libcurl3 and libcurl3-gnutls, restoring the package names provides the best upgrade path from etch to lenny; and because the symbol versions have been reverted, the libraries are not binary-compatible with the Debian packages currently named libcurl4/libcurl4-gnutls/libcurl4-openssl (in spite of being binary-compatible with upstream), so it would be wrong to keep the current names regardless.
  • Drop the SSL-less variant of the library, which was not present in curl 7.15.5; AFAICS, there is no use case where a user of curl needs to not have SSL support, so this split seems to be unnecessary overhead. Please correct me if I'm mistaken.
  • Leave the -dev package names alone otherwise, to simplify binNMUing of the reverse-dependencies (some packages have already added versioned build-deps on libcurl4.*-dev -- I have no idea why -- so reverting the names would mean more work to chase down those packages). Drop libcurl4-dev as a binary package, though, in favor of being Provided by libcurl4-gnutls-dev. Many of the packages currently build-depending on libcurl4-dev -- including some that wrongly used libcurl3-dev before -- are GPL, and these are apparently all packages where having SSL support missing in libcurl4 wasn't hurting them, so libcurl4-gnutls-dev seems to be the reasonable "default" here.
  • Schedule binNMUs for all reverse-dependencies.
Again, no judgement here: this was what needed to be done at the time, and I believe it was a good solution given the circumstances. In the end, the binary library packages got renamed again: from libcurl4- openssl,gnutls back to libcurl3- openssl,gnutls (but not their -dev variants!), but they continued shipping libcurl libraries whose SONAME version was 4. This solved the immediate problem of untangling the transitions mentioned by Steve, but introduced a technical debt that would stick with the package literally for decades. The situation at the end of 2007 was:
  • libcurl3-openssl with libcurl4-openssl-dev; and
  • libcurl3-gnutls with libcurl4-gnutls-dev.

More discrepancy is added Eventually the libcurl3-openssl package got renamed to libcurl3, but aside from that the situation with mismatched library names vs. SONAME versions stayed relatively unchanged until around 2018, when the Debian curl maintainer at the time (a different person) renamed libcurl3 to libcurl4 to fix a bug. This was the right thing to do for libcurl3, and at the time upstream curl was already properly versioning their symbols, but for some reason libcurl3-gnutls got left behind. So now we had:
  • libcurl4 with libcurl4-dev; and
  • libcurl3-gnutls with libcurl4-gnutls-dev.
In other words, we now have a discrepancy between the OpenSSL and GnuTLS variants' names. Yeah, confusing. And this is the situation right now, on May 2026, while I write this post. To make matters worse, the Debian curl package has been carrying a patch to facilitate the split of OpenSSL and GnuTLS flavours for decades now, and, for some reason I didn't bother to investigate, the patch pins the SONAME version of libcurl3-gnutls to CURL_GNUTLS_3, effectively overriding upstream's decision to version the symbols as CURL_GNUTLS_4.

A call to make things right Back in 2022, Simon McVittie filed a Debian bug to try and call our attention to the fact that we were shipping this messy set of curl packages. I had just started to get involved in the package maintenance and Samuel asked me to take a look at the bug. I noticed it was going to take more time than I had available, so I decided to put it in my TODO list (TM). Simon was generous enough to lay out a possible plan to tackle the problem, but I had a feeling that this was going to be harder than it looked. I kept postponing working on the bug, but also kept thinking about it now and then because it's an interesting thing to solve. Then, a month or so ago the Debian Brasil community got together for MiniDebConf Campinas 2026 and we decided to do a bug squashing party there. I started working on a few FTBFS bugs with GCC 16, but then got remembered about the curl bug and thought that that was the perfect time and place to start working on it, for a few reasons:
  • Samuel and Charles were also attending the conference, so I could talk to them about my plans and show them a PoC.
  • I was going to give a presentation about symbols (in pt_BR), so I could use this bug as an example of symbol versioning.
  • I wanted to have fun.

The initial plan The plan I had in mind was a variant of Simon's proposed plan:
  • I would have to adjust our GnuTLS-specific patch so that it did not override the SONAME version for libcurl-gnutls. Then,
  • For each symbol from libcurl3-gnutls I would have to:
    • Explicitly version it as curl_symbol_name@@CURL_GNUTLS_4.
    • Create an alias for the symbol (let's call it __curl_compat_symbol_name).
    • Explicitly version this alias as __curl_compat_symbol_name@CURL_GNUTLS_3.
  • Have a separate version of curl's linker script to make it possible to create a hierarchy between CURL_GNUTLS_3 and CURL_GNUTLS_4 symbols.
Note that this whole dance is needed because it is a hard requirement that programs linked against libcurl3-gnutls keep working when we ship libcurl4-gnutls, without needing to recompile them. Due to the fact that we will not really bump the SONAME of libcurl-gnutls (but instead fix the symbol versions shipped by it), we cannot expect programs to break given that they are actually using the exact same ABI as before. Unfortunately (as it is common with low level tools) the documentation for ld's versioning syntax is quite incomplete and hard to find. One of the best sources I found was this blog post. For this reason, let me quickly explain the different notations for symbol versioning used above.

curl_symbol_name@@CURL_GNUTLS_4 When we use curl_symbol_name@@CURL_GNUTLS_4 (note the @@) we are telling the linker that this should be considered the default version of curl_symbol_name. In other words, when a binary that links against libcurl-gnutls calls curl_symbol_name, the linker should use curl_symbol_name@@CURL_GNUTLS_4 to resolve the symbol. There are a few ways to specify a symbol version in C/C++:
__attribute__((__symver__("curl_symbol_name@@CURL_GNUTLS_4")))
void curl_symbol_name()
 
  /* ... */
 

/* or... */
void curl_symbol_name()
 
  /* ... */
 
__asm__(".symver curl_symbol_name, curl_symbol_name@@CURL_GNUTLS_4");

Function alias Creating an alias for a function is basically saying that a function can be called by another name. You can do that in C/C++ like:
void curl_symbol_name()
 
  /* ... */
 

void __curl_compat_symbol_name()
  __attribute__((alias("curl_symbol_name")));

__curl_compat_symbol_name@CURL_GNUTLS_3 Finally, when we use __curl_compat_symbol_name@CURL_GNUTL_3 (note the single @) we are telling the linker that this symbol exists, but it should not be used as the default symbol. In fact, this notation will basically hide the symbol and make it only available for those programs that have already been linked against it. It's a way of saying "don't offer this symbol when linking, but it's here in case a program needs it to run" (it's a bit more complicated than that, but you get the point). The reason I had to create an alias to the function before versioning the symbol with @CURL_GNUTLS_3 is because, once I've versioned the main symbol as @@CURL_GNUTLS_4, I can't create another version of it. It's also important to mention that to be able to create a version for the alias I also had to change its visibility to default. In the end, the alias ended up being defined as:
extern void __curl_compat_symbol_name()
  __attribute__((alias("curl_symbol_name"), visibility("default")));

First attempt and lessons learned For my PoC I decided to tackle a small subset of the problem. The symbols file for libcurl3-gnutls contains around 100 symbols that need to be fixed, so I chose two of them and started trying to write a patch to see if I could make things work. And after some time struggling with GCC's syntax and inspecting nm -D's output I finally got something that looked like it was going to work. The two symbols I had chosen to work with got correctly versioned (both as @@CURL_GNUTLS_4 and @CURL_GNUTLS_3), and a quick-and-dirty C program that used those symbols correctly compiled and ran with the expected symbols. I showed the results to Samuel and Charles, we got excited about what we saw, and then the conference ended.

Second attempt and some adjustments After getting back home I resumed the work on my branch and wrote an Emacs function that semi-automatically adjusted all 100+ symbols listed in the symbols file so that they all looked like:
__attribute__((__symver__("curl_symbol_name@@CURL_GNUTLS_4")))
void curl_symbol_name()
 
  /* ... */
 

extern void __curl_compat_symbol_name()
  __attribute__((alias("curl_symbol_name"), visibility("default"),
                 symver("__curl_compat_symbol_name@CURL_GNUTLS_3")));
The patch was big but mostly repetitive, and I was happy to have come up with a solution that looked clean. Until I tried to build the package, that is. I started seeing some strange errors that happened when ld was trying to link the final libcurl4-gnutls object (yes, at that point I had already renamed the binary package). This is one of the errors I was getting from ld (I got variants of this error as I was trying to fix the approach):
/usr/bin/x86_64-linux-gnu-ld.bfd: .libs/libcurl_gnutls_la-easy.o: in function  dupeasy_meta_freeentry':
./debian/build-gnutls/lib/./debian/build-gnutls/lib/easy.c:1024: multiple definition of  curl_easy_cleanup'; .libs/libcurl_gnutls_la-easy.o:./debian/build-gnutls/lib/./debian/build-gnutls/lib/easy.c:908: first defined here
/usr/bin/x86_64-linux-gnu-ld.bfd: .libs/libcurl-gnutls.so.4.8.0: version node not found for symbol curl_easy_duphandle@CURL_GNUTLS3
/usr/bin/x86_64-linux-gnu-ld.bfd: failed to set dynamic section sizes: bad value
This was strange. I did some tests with very simple versions of a shared library using the versioning mechanism I had implemented and it all worked. I could not reproduce the problem, and that's not a great feeling to have. Then, after reading a lot of documentation and blog posts throughout the internet I found something interesting. Apparently ld has a limitation when it comes to dealing with symbols versioned with @@. If there is a single symbol versioned like that in a source file (the actual term is TU, which means Translation Unit, but let's simplify), then ld is happy and generates the expected version without issues. But when we're dealing with multiple definitions of @@ symbols in a source file (which is exactly what happens in curl), then ld can get confused and start giving errors during the link stage. To solve that limitation, we have to resort to yet another symbol versioning notation: @@@. Yes, three at signs. For example:
void curl_symbol_name()
 
  /* ... */
 
__asm__(".symver curl_symbol_name, curl_symbol_name@@@CURL_GNUTLS_4");
Note that we have to use __asm__ because GCC's __attribute__ doesn't support the triple-at notation. What this does is tell the linker to create a versioned symbol for curl_symbol_name, set it as the default symbol when linking, but also remove the unversioned curl_symbol_name symbol. This makes ld happy and allows it to successfully link libcurl-gnutls. As usual, you won't find any mention of the @@@ notation inside ld's documentation. With libcurl-gnutls compiling again, I had to adjust libcurl's linker script to create a hierarchy between CURL_GNUTLS_3 and CURL_GNUTLS_4 symbols. Here's the final version of the file:
CURL_GNUTLS_3
 
  global:
    curl_easy_cleanup;
    /* lots of other symbols here */
  local: *;
 ;

CURL_GNUTLS_4
 
  global: curl_*;
  local: *;
  CURL_GNUTLS_3;

Debian package adjustments After getting the hard part out of the way, the rest was easy. It was time to finally rename libcurl3-gnutls to libcurl4-gnutls. Initially I was thinking that I'd need to ask the release team for a transition to happen, but as it turns out that won't be necessary. Because we are effectively shipping the same exact library/ABI and the only difference is the inclusion of the extra CURL_GNUTLS_4 versioned symbols, and given that we will be shipping CURL_GNUTLS_3 versioned symbols to guarantee backwards compatibility, packages won't need to get rebuild just to pick up the new dependency. Instead, we can safely turn libcurl3-gnutls into a transitional package that depends on libcurl4-gnutls.

Merge request and next steps This is the merge request where I am working on the fix. As of this writing it is in a draft state, but I expect to merge in the next couple of days. Once the fixed curl package is uploaded, we should keep an eye on the archive to make sure no unexpected bugs happen. I would like to carry this patch downstream at least until forky is released. It doesn't make sense to propose it upstream because this problem is Debian-specific and should be fixed there. We will need to make sure that all reverse dependencies of libcurl3-gnutls are recompiled before we can get rid of the transitional package, too. This was a fun bug to investigate and fix, and I am happy that we will finally have sensible names (and symbol versions!) for both of our libcurl variants. Stay tuned for the next challenge!

17 May 2026

Russ Allbery: Review: Unwinding Anxiety

Review: Unwinding Anxiety, by Judson Brewer
Publisher: Avery
Copyright: 2021
ISBN: 0-593-33045-5
Format: Kindle
Pages: 268
Unwinding Anxiety is a non-fiction self-help book about how to reduce anxiety. The author is a board-certified psychiatrist specializing in addiction and substance abuse, who has subsequently done clinical and research (and commercial, more on that later) work in anxiety. His previous book, The Craving Mind, was a pop science treatment of addiction research. This book is more deliberately structured as a self-help guide. (The cover will assure you that he has an M.D. and a Ph.D. I don't include honorifics and degrees in author listings as a small protest against the weird social rules about which degrees count and which don't.) There are a lot of self-help books out there about anxiety. There are a lot fewer that say something relatively original. I think this is one of the latter, but I certainly have not done a survey of the subgenre, and it's possible the ideas here are only new to me. Brewer makes three basic claims in this book, all of which I found personally useful:
  1. Anxiety can be usefully analyzed as a habit. The rumination loop and other related anxiety behaviors such as excessive analysis, reassurance-seeking, and negative anticipation take the form of deeply ingrained habits triggered by stimuli.
  2. Raw willpower is not a useful way to break habits in general and anxiety habits in particular. In order to displace the habit, you have to retrain the part of your brain that runs habits on autopilot. Attempting to override it with willful effort is exhausting and likely to fail.
  3. Habit loops in general, and anxiety loops in particular, can be defused and replaced using mindfulness techniques.
This is not the way Brewer lays out the book. He goes to some effort to lead the reader slowly through three techniques for handling anxiety (for which he uses the metaphor of "gears," like for a bicycle or car) by introducing them one at a time and encouraging the reader to become thoroughly familiar with each one before moving on to the next. Since this is a book review, I'm going to give you the whole argument at once so that you know where this book is going. This may be less helpful in practice; if you're trying to use this technique on your own anxiety, you may want to read the book instead and not jump ahead. Brewer's three gears are:
  1. Identify your habit loops and recognize when they're happening. (This part felt the most similar to traditional cognitive behavioral therapy to me.)
  2. Focus on how those habit loops make you feel. Rather than trying to force the habit loop to stop, let it happen but pay very close attention to the outcome and its effects on you.
  3. Find and focus on a different reaction that provides better rewards than the anxiety habit loop. Brewer suggests curiosity.
For me, the point where I thought "okay, you have my attention" is when Brewer described the way many people, particularly people without anxiety, tell people with anxiety to "just stop thinking about it" or "just do the thing you're anxious about anyway and you'll see it will be fine" and then described in detail why he believes that doesn't work. This is one of the few discussions of anxiety I've read where the author goes out of his way to stress that you cannot simply think your way out of anxiety and that repeatedly trying to do so and failing is exhausting and demoralizing. Everyone is different and I know some people find cognitive behavioral therapy very helpful, but I find the constant effort to challenge cognitive distortions more draining and demoralizing than useful. His second gear, of not directly confronting the habit loop but instead watching its effect and thinking about its outcome, feels so much more approachable to me. Assuming, of course, it works. Brewer's approach is essentially just mindfulness, although he mostly avoids the (to me at least) somewhat off-putting typical introduction to mindfulness via religious practice or general well-being and instead ties it to a theorized model of how habits work in the human brain. His contention is that habits, including anxiety, exist because at some point they provided a reward that was sufficiently compelling to make the habit-following part of your brain seek that reward. You were getting some benefit (a sense of control, a sense of being prepared, temporary reassurance, etc.) out of the anxiety reaction, which is why the anxiety habit formed in the first place. Once that habit is in place, it can continue without the reward. (Although in my experience there is probably still some short-term reward.) Rather than trying to force yourself to stop following the habit, Brewer instead suggests letting the habit happen but then focusing (via mindfulness) on how following the habit makes you feel, whether it improves your sense of well-being or worsens it, and whether other actions produce different feelings. The goal, in other words, is to undermine the assumption of reward and to challenge any short-term reward with the long-term discomfort that made you want to stop being anxious. This avoids using your conscious brain to exert direct willpower, which is exhausting and usually unsuccessful since the habit-following part of your brain is stronger (for various evolutionary psychology reasons he explains and that I found at least partly credible). Instead, you are using its strengths of observation and classification. You pay close attention to the ways in which the habit loop makes you feel bad, which in theory provides feedback to the habit-following part of your brain that can dislodge the habit. If the habit is recognized as no longer rewarding, it will weaken. Brewer's background is in addiction treatment, so he is predisposed to see addiction in everything and one should probably be a bit cautious about his enthusiasm. He claims a great deal of success with this approach in clinical settings, mostly with addiction but also with anxiety, but this is always hard to verify. (Few doctors who write self-help books rigorously document their failures.) He apparently also has a company that produces various phone apps that assist with this technique. I'm rather cynical about anyone who talks about products their company has produced in self-help books of this type, and I'm also rather cynical about anyone who calls himself "Dr. Jud," but the book doesn't seem to be a sales pitch and there's no direct information in it about how to get the apps. For me, the first two parts of the book were the most useful and the conception of anxiety reactions as habits made a surprising amount of intuitive sense. I thought the third part of the book, where he tries to describe a better in-the-moment reaction that you can try to build into a more beneficial habit, to be the weakest. It's mostly stock mindfulness advice that I've seen in other places, and you will be entirely unsurprised to learn that Brewer meditates and has studied meditation. I think it's clear that, for him, a feeling of curiosity works as an anxiety replacement; I'm not sure that's universal and I'm not sure it works for me. That core idea that anxiety reactions are a type of addictive habit that have outlived their useful rewards but continue because habits are hard to change felt both useful and at least a little bit true, though. Your mileage may, of course, vary, but I've been trying out various ideas from this book since I first started reading it, and I think it's helping. If any of this clicks with you and you're also prone to anxiety, it might be worth a read. One warning, though: Brewer's previous work on addiction includes binge eating, and while it's not a primary focus, he uses several weight loss and disordered eating examples and has a very traditional medical attitude towards weight. I'm somewhat dubious of the addiction model of weight gain in general, but more to the point, it's rather off-putting in a book supposedly about anxiety. It's something I was able to skim over, but be aware going in if you're likely to find this obnoxious. I do think this book is a case of an addiction researcher seeing everything through the lens of addiction, and I'm a little dubious this is the right model for everyone's anxiety. But this is one of the good reasons why there are a lot of books about anxiety: Different approaches suit different people. This one made more sense to me than most; maybe you are similar. I can't really recommend or not recommend a book like this, since I think so much will depend on whether you are one of the people for whom this specific explanation will click, but I'm glad that I read it and I think it's good to know that this model of anxiety exists. Rating: 8 out of 10

Otto Kek l inen: Balancing persistence vs pivoting is grit a virtue or wasteful?

Featured image of post Balancing persistence vs pivoting   is grit a virtue or wasteful?Being persistent, sticking to a plan and showing up to work every day is generally valued highly across all cultures as virtuous behavior. It is obvious that anything of value and worth achieving is also not easy, but requires significant and recurring effort. Learning a new language, winning a sports competition or building a successful business are all typical scenarios where grit plays a central role above everything else. However, sometimes the virtue of tenacity can result in just a waste of energy. The question is then: how does one recognize that true progress is being blocked by stubbornness and a pivot would be the correct decision, as opposed to being close to breakthrough where doing more of the same would actually be the right choice?

What is persistence actually? To think clearly about this topic, one must first grasp the concept of grit and what it looks like in practice. Research by psychologist Angela Duckworth on grit shows that sustained effort in the face of setbacks separates high achievers from those who quit too soon. Entrepreneurs who iterated through dozens of failed prototypes or writers who revised manuscripts for years understand this truth. Persistence builds resilience, deep expertise, and the kind of compounding results that shortcuts cannot deliver. It also protects against the distraction of shiny new ideas that pull focus from what actually works. Persistence is about:
  1. Believing in an outcome and working towards it despite people around you not sharing the belief, and despite your own work and experiments not being successful.
  2. Continuing to hold the belief and sticking to the decision despite other ideas, solutions and competing alternatives surfacing.
  3. The more time passes, the firmer the conviction becomes. Time, money, and emotional energy invested in a failing direction create psychological pressure to continue (sunk-cost fallacy).
Simply following through on a plan or upholding a contract is not true persistence. Grit is a personal trait one can cultivate to actually become more energized to do something precisely because it turns out to be harder than expected.

Pivoting: a calculated choice The opposite of being persistent is giving up. Pivoting is not about giving up, but about redirecting the energy and momentum towards a new goal. Pivoting requires coming to the realization that you were wrong, and going through the painful process of discovering a new truth. Ideas tend to be abundant, and doing something new isn t hard as such. The hard part is to abandon a previously held belief and adopt a new one with equal conviction. To have that conviction you need to have data and metrics. This is also the key to how to decide between persisting vs pivoting at any moment in time.

Key metrics of success Any decision is only as good as the information available at the time it was made. To be set up for success one needs to start by deciding on what the actual goal is, what one values and how progress is measured. Key metrics are usually easiest to discover by working backwards from the goal. If you want to build an electric car, you might decide that the goal is to have a car that costs 30,000 euros and can drive 300 km on one charge. From that goal you can break down what the cost structure should be, what volume of production is needed to break even, what raw materials are needed and what the battery chemistry needs to achieve to meet the goal. That can further be broken down into a rate of progress. Suppose the plan requires battery energy density to reach 150 Wh/kg to be viable. If the state of the art starts at 100 Wh/kg and funding lasts a maximum of five years, the team needs at least an 8% improvement every year (1.08^5 100 Wh/kg 150 Wh/kg). This can then be used as a guideline. Sometimes progress is not steady, but happens in jumps. Even in those cases there should be a trajectory to benchmark the jumps against. In an online business, the key metric could, for example, be one of these:
  • 7- or 30-day retention rate: Do new users who try the service actually like it?
  • Weekly or monthly active users: Is usage trending up?
  • Feature adoption rate: In an existing service, how many users are using the new feature?
  • Product-Market Fit Score (from Sean Ellis test): Percentage of users who say they would be very disappointed if the product disappeared. Above 40% is a strong early indicator. A number below that (after multiple iterations) is a good data point to pivot.
  • Revenue run rate or burn rate: The most generic metric everything eventually boils down to. Healthy markets reward good products.
Weekly metrics are better than monthly, as they make the feedback loop faster and allow you to get validation quickly and do minor course corrections along the way. A complete pivot should, however, be based on long-term data, driven by the key metric and supported by additional data points. Metrics are also needed because they can t be bribed or convinced to be anything other than what they are. Listening to other people is good, but just relying on the opinion of others is extremely dangerous because people are biased either for you or against you depending on whether they see you as a trusted leader or an outcast. Key metrics are of course domain-specific and everyone needs to come up with their own. However, you must have some key metric. You can t have the excuse that what you are doing can t be measured. If you are part of a larger organization and you need to advocate for a difficult decision for example, to kill your darlings when facing a pivot you need to have the metrics to back up your views, and those metrics need to have been established way before as something the organization values, and not cherry-picked just for this one decision. It does not matter if you are on a personal improvement journey, running a political campaign, inventing a new product, or growing a business you need to have some metric you can check at any given time to see if things are improving fast enough to predict success. Metrics can and should also be used in daily work to validate that you are on the correct path, and to optimize execution.

Famous examples of persistence and pivoting that led to breakthroughs In all of the cases below it is of course in hindsight easy to say they made the right decision. However, take a minute to try to imagine yourself in their shoes at the time of the decision. What metrics might they have had available to support their decision? What would you have wanted to measure or find out if you were in the same situation?
  • Frustrated that his vacuum lost suction, James Dyson spent five years and built thousands of failed prototypes in a backyard shed. He remortgaged his home, lived on savings, and faced rejection from every major manufacturer who wanted to protect their bag-replacement business. The 5,127th prototype based on an idea from a sawmill with a cyclone finally worked. Launched in 1993, the Dyson DC01 became Britain s best-selling vacuum within two years.
  • As a single mother on welfare in the mid-1990s, J.K. Rowling finished her manuscript for Harry Potter and the Philosopher s Stone while battling depression and poverty. She hand-typed copies and mailed them to publishers. Twelve rejected it outright, with comments like children s books about magic don t sell. She nearly quit multiple times but kept revising and submitting. Bloomsbury finally accepted it after the CEO s eight-year-old daughter read the first chapter and demanded the rest. The series has since sold hundreds of millions of copies worldwide.
  • Founded in 1997 as a mail-order DVD rental service, Netflix added unlimited subscriptions in 1999 to compete with Blockbuster. By 2007, broadband growth and declining DVD sales signaled a shift. CEO Reed Hastings pivoted aggressively toward streaming, investing in bandwidth deals and original content while de-emphasizing physical media. The move faced skepticism, but eventually changed the whole culture of how entertainment is consumed.
  • YouTube launched in 2005 as a video-dating site. Founders offered money to women who uploaded dating videos, but almost no one did. Meanwhile, users uploaded random clips. The team recognized the mismatch and pivoted within months to a general-purpose video-sharing platform with easy uploading. Google bought it just 18 months later.
  • Instagram began in 2010 as Burbn, a location-based check-in app that let users post plans, earn points, and share photos. Co-founders Kevin Systrom and Mike Krieger quickly noticed users ignored most features and mainly used it for photo-sharing. They made the tough call: scrap everything else. Within weeks, they rebuilt the app around clean, simple photography with filters. The pivot launched as Instagram in October 2010. It gained 1 million users in two months and was acquired by Facebook just 18 months later.

Insanity or conviction? English has several proverbs that warn against excessive persistence, such as banging your head against the wall . Insanity is commonly defined as Doing the same thing over and over again and expecting different results. In Finland, the national identity is practically built on the concept of sisu . It means much more than just grit . The word is derived from the word for inside or guts and represents an unexplained, almost superhuman force that makes one stoically take action despite seemingly impossible odds and somehow succeed anyway. It became a defining national mythos during the Winter War (1939 1940), where a force 10 times larger than the Finnish army tried to invade the country but was stopped and Finland just barely managed to keep its independence. The word sisu transitioned from a character trait to a pillar of national survival. I think Finns survived because the more you believe in persistence, the more likely you are to persist. I view persistence as a religion that requires faith, while pivoting is a science where you derive the truth from the numbers. When in doubt, I would always choose persistence over pivoting. Perhaps it is because of my genetic tendency towards having sisu , but I would also rather keep on going a bit more and try one more time before giving up and pivoting in order to get more data, so that when I pivot, I know it is absolutely the right thing to do at that point. Depending on the situation, the costs of postponing the pivot vary. Of course, if the main metric is the burn rate and a company is running out of money, a pivot must be done early enough that the remaining runway is enough to execute the pivot, and then some more. In some situations a business idea might simply be ahead of its time. If that is the conviction and the key metrics support it, the best way to navigate the situation is to cut down on costs and wait for competitors to appear, help build general awareness, and then ramp up again to ride the wave. Remember that success does not come from grit alone there is always an element of timing and luck as well. But if you are not persistent and stop showing up every day, you won t be able to seize the opportunities if and when they arise.

Failure is the likely outcome you have to avoid it at any cost One must also realize that most attempts end in failure. Failure is the baseline, and success is the exception. To reach a breakthrough, one must be stubbornly persistent. In particular, if you are a leader, you need to be so high in conviction that it almost becomes an aura that radiates to those around you. Postponing the decision to pivot allows you to get a bit more data for the decision, so that once you pivot, you have full belief in the new direction. Once you pivot, there is no looking back, otherwise you will undermine morale and most certainly fail with the new thing as people will execute it with hesitation. Failure is statistically always the more likely outcome. Most things end in failure and we never hear about them. If someone on your team does not believe in what you are doing, it is very easy for them to prove that something is a failure by spreading negativity, putting in less effort (perhaps unconsciously due to lack of conviction) and thus actually contributing to a self-fulfilling failure. In most areas of life, ideas are cheap and the only thing that matters is execution. To be good at executing, you need to be good at making decisions. When drafting plans it is good to have alternatives and a lot of consideration. However, when execution starts, there is no room for doubt, otherwise the chances of success decrease. Therefore, the best way of balancing persistence vs pivoting is to
  1. plan well ahead,
  2. establish the key metrics,
  3. have thresholds established for what would trigger a pivot, and
  4. do everything you can to move the metrics in the direction you want them to go.
Finally, if you decide to pivot, you must do so only with very high conviction, as you can t undo a pivot, and you should not be doing multiple pivots in a row either. If you are fully convinced yourself about the pivot, you will also be able to convince others about it, and carry the momentum.

15 May 2026

Antoine Beaupr : The Four Horsemen of the LLM Apocalypse

I have been battling Large Language Models (LLM1) for the past couple of weeks and have struggled to think about what it means and how to deal with its fallout. Because the fight has come from many fronts, I've come to articulate this in terms of the Four Horsemen of the Apocalypse.
Sound track: Metallica's The Four Horsemen, preferably downloaded from Napster around 2000, but now I guess you get it on YouTube.

War: bot armies Let's start with War. We've been battling bot armies for control of our GitLab server for a while. Bots crawl virtually infinite endpoints on our Git repositories (as opposed to downloading an archive or shallow clone), including our fork of Firefox, Tor Browser, a massive repository. At first, we've tried various methods: robots.txt, blocking user agents, and finally blocking entire networks. I wrote asncounter. It worked for a while. But now, blocking entire networks doesn't work: they come back some other way, typically through shady proxy networks, which is kind of ironic considering we're essentially running the largest proxy network of the world. Out of desperation, we've forced users to use cookies when visiting our site. We haven't deployed Anubis yet, as we worry that bots have broken Anubis anyways and that it does not really defend against a well-funded attacker, something which Pretix warned against in 2025 already. (We have a whole discussion regarding those tools here.) But even that, predictably, has failed. I suspect what we consider bots are now really agents. They run full web browsers, JavaScript included, so a feeble cookie is no match for the massive bot armies.

Side note on LLM "order of battle" We often underestimate the size of that army. The cloud was huge even before LLMs, serving about two thirds of the web. Even larger swaths of clients like government and corporate databases have all moved to the cloud, in shared, but private infrastructure with massive spare capacity that is readily available to anyone who pays. LLMs have made the problem worse by dramatically expanding the capacity of the "cloud". We now have data centers that defy imagination with millions of cores, petabytes of memory, exabytes of storage. I thought that 25 gigabit residential internet in Switzerland could bring balance, but this is nothing compared to the scale of those data centers. Those companies can launch thousands, if not millions of fully functional web browsers at our servers. Computing power or bandwidth are not a limitation for them, our primitive infrastructure is. No one but hyperscalers can deal with this kind of load, and I suspect that they are also struggling, as even Google is deploying extreme mechanisms in reCAPTCHA. This is the largest attack on the internet since the Morris worm but while Robert Tappan Morris went to jail on a felony, LLM companies are celebrated as innovators and will soon be too big to fail.2 Which brings us to the second horsemen, famine.

Famine: shortages All that computing power doesn't come out of thin air: it needs massive amounts of hardware, power, and cooling. Earlier this year, I've heard from a colleague that their Dell supplier refused to even provide a quote before August. Dell! In February, Western Digital's hard drive production for 2026 was already sold out. Hard drives essentially doubled in price within a year, and some have now tripled. A server quote we had in November has now quadrupled, going from 10 thousand to FORTY thousand dollars for a single server. But regular folks are facing real-life shortages as well, as city-size data centers are being built at neck-breaking speed, stealing fresh water and energy from human beings to feed the war machine. We've been scared of losing our jobs, but it seems that Apocalypse has yet to fully materialize. Regardless for engineers, the market feels tighter than it was a couple years ago, and everyone feels on edge that they will just have to learn to operate LLMs to keep their jobs. Update: it turns out I was clearly too optimistic. Cisco is laying off 4,000 or 5% of its staff in a jolly announcement celebrating a record $15.8 billion revenue, and Meta will lay off 8,000 or 10% of its workforce, in horrifying conditions. See also the jobloss.ai tracker which counts 125,000 jobs lost since January 2025, as of May 2026. Which brings us, of course, to Death.

Death: security and copyright Our third horseman is one I did not expect a couple of months ago. Back at FOSDEM, curl's maintainer Daniel Stenberg famously complained about the poor quality of LLM-generated reports but then, a few months later, everyone is scrambling to deal with floods of good reports. In the past two weeks, this culminated in a significant number of critical security issues across multiple projects. Chained together, remote code execution vulnerabilities in Nginx and Apache and two local privilege escalations in the Linux kernel (dirtyfrag and fragnesia) essentially gave anyone root access to any unpatched server to the web. As I write this, another vulnerability dropped, which gives read access to any file to a local user, compromising TLS and SSH private keys. All those vulnerabilities were released without any significant coordination while people scrambled to mitigate. Many people including Linus Torvalds are now considering issues discovered through LLMs to be essentially public. This puts some debates about disclosure processes in perspective, to say the least. But this is not merely the death of the traditional coordinated disclosure process, the C programming language, or the Linux kernel: remember that those bots are trained on a large corpus of copyrighted material. Facebook has trained their models on pirated books and Nvidia has done deals with Anna's Archive to secure access to large swaths of copyrighted material. The US Congress seems to think LLM outputs are not copyrightable, like any other machine outputs. With many people now vibe coding their way out of learning or remembering how computers work, is this the Death of Copyright? And that, of course, brings us to the final horseman: Pestilence.

Pestilence: slop There is a growing meme that programming is essentially over as we know it. That you can simply vibe-code applications from scratch and it's pretty good. Maybe that's true. So far, most of my attempts at resolving any complex problem with a LLM have often failed with bizarre failures. Some worked surprisingly well. Maybe, of course, I am holding it wrong. I personally don't believe LLMs will ever be good enough to produce and maintain software at scale. They're surprisingly good at finding security flaws right now. But what I see is also a lot of Bullshit, with a capital B. It's not lying: it does not "know" anything, so it can't lie. It's misleadingly cohesive and deliberate, but it lacks meaning, intent, will. I have not been confronted with much slop, apart from the lobster Jesus or the yellow man atrocities, and particularly not in my work. But I see what it is doing to my profession: beyond vibe-coding, people are now token-maxxing, and land-grabbing their colleagues. I don't like what LLMs do to our communities, or the fabric of software we live with. Software does not evolve in a void. It is a team effort, be it free software or a corporate product. Generations of humans have carefully built the scaffolding of technology required for modern networks and software to operate, in a convoluted contraption that no single human fully understands anymore. The idea of simply giving up on that understanding entirely and delegating it to an unproven model is not only chilling, it feels just plain stupid. Not stupid as in Skynet, stupid as in "I can't get inside the data center because the authentication system is down". Except we're in a "the power plant doesn't reboot" or "their LLM found an 0day in our slop" kind of stupid.

The fifth horsemen Researching for this article, I looked up the four horsemen and found out they original seems to have been:
  • Famine
  • War
  • Death
  • Conquest (??)
I was surprised. I grew up thinking about the horsemen being Famine, War, Pestilence, and Death. So I went back to my original source which actually claims the horsemen are:
Time has taken its toll on you, the lines that crack your face.
Famine, your body, it has torn through, withered in every place.
Pestilence for what you've had to endure, and what you have put others through
Death, deliverance for you, for sure, now there's nothing you can do
So I guess that makes no sense either, which, fair enough, I shouldn't rely on Metallica for theological references. Especially since that song was originally called Mechanix and was "about having sex at a gas station". Anyways. The point is, there are actually five horsemen, and the fifth one is, in my opinion, Conquest. Those companies (and not "AI", mind you) are taking over the world. I sense a strong connection with the "post-truth" world imposed on us by fascists like Trump and Putin. It's not an accident, it's a power grab part of the Californian Ideology3. Just like Airbnb broke housing, Uber destroyed the transportation and Amazon is taking over retail and server hosting, LLM companies are essentially trying to take over if not everything, at least Cognition as a whole. But the capitalization of those companies (OpenAI and Nvidia in particular) are so far beyond reason that their inevitable collapse will likely lead to a global financial collapse of biblical proportions. Because they will inevitably fail like previous bubbles they are built on. And when they fail, I hope it zips all the way back through the blockchain scam, the ad surveillance system, and the dot com then git me back my internet.

The Tower of Babel While I'm off in the woods hallucinating (ha!) on biblical allegories, I feel there's another sign that the apocalypse is coming. The Tower of Babel myth says that humans tried to create a big tower up to heaven and become god. God confounds their speech and scatters the human race. End of utopia. This is what is happening to our human translators now. LLMs being, after all, Language Models, they are excellent at translation work. So much that the only translators not replaced by LLMs right now are interpreters, who translate vocally in real time. But interpreters are worried about their jobs as well. This concretely means we will lose the human capacity, as a civilization, to translate between each other. It is still an open question whether the remaining revision work will be enough for translators to avoid deskilling, but other research has shown that LLM use leads to cognitive decline, impacts critical thinking, and generally, that deskilling is a common outcome. Ultimately, I think this is where LLMs bring us. Towards collapse. So this is a call to arms. Fight back! Poison bots. Build local real-world communities. Go low tech. Moore's law is dead, make use of it. Patch your shit. Go weird. Refuse slop. Train your brain. Refuse distillation. The horsemen will collapse, but let's not go down with them. Butlerian Jihad!
This article was written without the use of a large language model and should not be used to train one.

Updates
  • A paragraph was added about the job apocalypse, which was of course under-estimate.
  • Why Timnit Gebru was fired is extremely important and interesting. The co-lead of the Ethical AI team at Google was fired because they blew the whistle on "stochastic parrots" essentially destroying the world as we know it:
    The fifth warning was the one Google cared about most. [...] The internet would become a place where the dominant voice was a statistical average of dominant voices, presented as a neutral assistant.
    The warnings from the paper are eerily similar to my horsemen:
    1. predicted the hallucination (pestilence)
    2. bias amplification (war?)
    3. environmental cost (famine)
    4. un-auditable training corpus (death?)
    5. "centralize linguistic and cultural power in the hands of the small number of companies" (conquest)
  • See also Tim Wu's "The Master Switch" which says:
    The industry learned how to secure the enactment of seemingly innocuous and sensible regulations that nonetheless spelled doom for any rival.
    People claim the same about Anthropic.

  1. I prefer "LLM" to Artificial Intelligence, as I don't consider models to have "Intelligence" which goes far beyond the analytical traits we train models for. Intelligence requires embodiment and social interaction; machines lack the innate human skills of empathy, feeling and care, which explains a lot of the evils behind the current trends.
  2. It should be noted that Morris also happened to be one of the founder of Y Combinator where he is in good company with other techno-fascists like Peter Thiel, Sam Altman, and so on. Crime, after all, pays.
  3. Probably a good time to watch All Watched Over by Machines of Loving Grace.

11 May 2026

Freexian Collaborators: Monthly report about Debian Long Term Support, April 2026 (by Thorsten Alteholz)

The Debian LTS Team, funded by Freexian s Debian LTS offering, is pleased to report its activities for April.

Activity summary During the month of April, 21 contributors have been paid to work on Debian LTS (links to individual contributor reports are located below). The team released 37 DLAs fixing 145 CVEs. The team continued preparing security updates in its usual rhythm. Beyond the updates targeting Debian 11 ( bullseye ), which is the current release under LTS, the team also proposed updates for more recent releases (Debian 12 ( bookworm ) and Debian 13 ( trixie )), including Debian unstable. We highlight several notable security updates here below.
  • Andrej Shadura prepared DLA 4525-1 for libyaml-syck-perl to fix a vulnerability related to a memory leak.
  • Andrej also prepared DLA 4551-1 for mbedtls to fix a leak of secrets.
  • Arnaud Rebillout prepared DLA 4532-1 for python3.9 to fix a use-after-free issue in several decompressors.
  • Arnaud also prepared DLA 4533-1 for systemd to fix multiple vulnerabilities, which might be also used to execute arbitrary code.
  • Bastien Roucari s prepared DLA 4529-1 for bind9 to fix a DNSSEC issues, which can cause the resolver to consume excessive CPU.
  • Bastien also prepared DLA 4539-1 for imagemagick to fix 21 vulnerabilities.
  • Emilio Pozuelo Monfort prepared DLA 4535-1 for openssh to fix a potentially execution of arbitrary code.
  • Emilio also Monfort prepared DLA 4526-1, DLA 4546-1 and DLA 4555-1 for firefox-esr to fix 31 vulnerabilities.
  • Jochen Sprickerhof prepared DLA 4524-1 for postgresql-13 to fix multiple vulnerabilities, which might be also used to execute arbitrary code.
  • Sylvain Beucler prepared DLA 4538-1 for perl to fix unauthorized access to data or arbitrary code execution.
  • Thorsten Alteholz prepared DLA 4545-1 for packagekit to fix a local privilege escalation.
  • Thorsten also prepared DLA 4544-1 for ntfs-3g to fix a local privilege escalation.
  • Tobias Frost prepared DLA 4521-1 for libpng1 to fix multiple vulnerabilities, which might be also used to execute arbitrary code.
Contributions from outside the LTS Team:
  • As usual, the thunderbird updates, released as DLA 4534-1 and DLA 4549-1, were prepared by its maintainer Christoph Goehre. This month 28 CVEs has been fixed. Thanks a lot for his continuous contributions. The DLAs have been sent by Emilio.
  • Thanks alot as well to Mathias Behrle for providing DLA 4543-1 for package simpleeval. The DLA has been sent by Santiago.
The LTS Team has also contributed with updates to the latest Debian releases:
  • Andreas Henriksson completed the upload of gvfs for trixie and bookworm
  • Ben Hutchings did uploads of several kernel packages to unstable and the corresponding backports repositories.
  • Sylvain took care of uploads of awstats to trixie and bookworm. He also did the same for 7zip-rar with an upload to bookworm-backports).
Some milestones in the lifecycle of two Debian releases are just around the corner. The support of Debian 12 will be handed over to the LTS team on June 11th 2026. After August 31st, support for Debian 11 will move from Debian LTS to ELTS managed by Freexian.

Individual Debian LTS contributor reports

Thanks to our sponsors Sponsors that joined recently are in bold.

Freexian Collaborators: Debusine workflow performance issues (by Colin Watson)

During March and April, we had a number of performance issues that made Debusine s core functions of running work requests and reflecting their results in workflows quite unreliable. Investigating and fixing this took up a lot of time from both the Debusine development team and Freexian s sysadmins. The central problems involved a series of database concurrency and worker communication issues that interacted in complex ways. On bad days, this caused between 10% and 25% of processed work requests to fail unnecessarily. We communicated some of the problems to users on IRC, but not consistently since we didn t entirely understand the scope of the problems at the time. Most of the problems are fixed now, but we had a retrospective meeting to make sure we understood what happened and that we learn from it. Here s a summary.

Data model Debusine s workflows consist of many individual work requests. Each work request has a database row representing its state, which means that the overall state of a workflow is distributed across many rows. Changes to one work request (for example, when it is completed) can cause changes to other work requests (perhaps unblocking it so that it can be scheduled to an idle worker). Those changes may happen concurrently, and in practice often do. Workers typically need to create artifacts containing the output of tasks: these include things like packages, build logs, and test output. Debusine records task history so that it can make better decisions about how to schedule work requests. Since this might otherwise grow without bound, the server expires older parts of that history after a while. The same is true for many other kinds of data.

Causes
  • Because workflows involve changes that propagate between work requests, there were historically some cases where different parts of the system could deadlock due to trying to take update locks on overlapping sets of work request rows in different orders. We mitigated that somewhere around 2025-11-05 by locking entire workflows in one go before making any change that might need to propagate between work requests like this; that dealt with the deadlocks, but it s quite a heavyweight locking strategy that sometimes caused significant delays.
  • We ve been working for some time to make Debusine useful to Debian developers, and regression tracking is an important part of that: it lets developers test uploads without being too badly misled by tests in related packages that were already failing before they started. On 2026-03-11 we enabled this by default on debusine.debian.net, after testing it for a while. Although this is useful, it put more load on the system as a whole, often approximately doubling the number of work requests in a given workflow with many additional dependencies between them.
  • Like much of the world, we re in an arms race with unethical scrapers desperately trying to feed everyone else s data into LLMs before they run out of money. We saw a substantial uptick here towards the end of March, which meant that we had to temporarily disable regression tracking and to put some other mitigations in front of our web interface.
  • We historically haven t had systematic internal timeouts. Prompted by ruff, a Google Summer of Code applicant went through and added timeouts in many places, including some calls between the worker and the server. This was fiddly work and the student did a solid job, so I m not putting them on blast or anything! However, it did mean that some things that came in under load balancer timeouts now timed out earlier on the client side of the request (and hence in Debusine workers), which made some problems show up in different ways and be more obvious. This was deployed on 2026-04-03.

Fixes

Workflow orchestration Figuring out what individual work requests need to be run as part of a workflow - the process we call orchestration - can be challenging. Unlike typical CI pipelines, these workflows often span substantial chunks of a distribution: a glibc update can involve retesting nearly everything! Nevertheless, it s not particularly helpful for it to take hours just to build the workflow graph. Fixing this involved many classic database optimizations such as adding indexes and CTEs, but probably the most effective fix was adding a cache for lookups within each orchestrator run or work request. Profiling showed that resolving lookups was a hot spot, and the way that task data is often passed down through a workflow meant that the same lookup could be resolved hundreds or thousands of times in a large workflow.

Expiry We knew for quite some time that our expiry job took very aggressive locks, effectively blocking most of the rest of the system. This was an early decision to make the expiry logic simpler by allowing it to follow graphs without worrying about concurrent activity, but it clearly couldn t stay that way forever. Row locks in PostgreSQL was very helpful in figuring out the correct approach here. Since we re mainly concerned about the possibility of new foreign key references being created to artifacts we re considering for expiry, and since that would involve taking FOR KEY SHARE locks on those rows, we can explicitly take FOR UPDATE locks (which conflict with FOR KEY SHARE), and then recompute the set of artifacts to expire with any locked artifacts marked to keep. This was delicate work, but it saved minutes of downtime every day.

Whole-workflow locking I mentioned earlier that we avoided some deadlock issues by taking locks on entire workflows. To ensure that these locks are effective even against code that isn t specifically aware of them, this is implemented by using SELECT FOR UPDATE on all the work request rows in the workflow. In some cases the search for which rows to lock itself tripped up the PostgreSQL planner.

Scheduling We run multiple Celery workers for various purposes. Some of them can do many things in parallel, but in some specific cases (notably the task scheduler) we only ever want a single instance to run at once. Unfortunately a bug in the systemd service meant that the scheduler often ran concurrently anyway! Once we fixed that, the scheduler logs became a lot less confusing. When Debusine was small, it was reasonable for it to perform scheduling very aggressively, typically as soon as any change occurred to a work request or a worker that might possibly influence it. This doesn t scale very well, though, and even though we tried to batch multiple scheduling triggers that occurred within a single transaction, it could still make debugging very confusing. We reduced the number of changes that would result in immediate scheduling, and deferred everything else to a regular tick . The scheduler may not be able to assign a work request to an idle worker due to the workflow being locked. That isn t a major problem in itself; it can just try again later. However, in very large workflows, we found that it often worked its way down all the pending work requests one by one finding that each of them was locked, which was slow and also produced a huge amount of log noise. It now assumes that if a work request is locked, then it might as well skip other work requests in the same workflow until the next scheduler run. Between them, these changes reduced the number of locks typically being held on debusine.debian.net by about 80%: Lock graph

Worker refactoring The Debusine worker has always been partially asynchronous, but while it was actually executing a task - in other words, most of the time, at least in busy periods - it didn t respond to inbound websocket messages, causing spurious disconnections. We restructured the whole worker to be fully event-based. We also had to put quite a bit of effort into improving the path by which workers report work request completion, because if that hits a timeout then it can mean throwing away hours of work. We have some further improvements in mind, but for now we defer most of this work to a Celery task so that whole-workflow locks aren t on the critical path.

Database write volume One of our sysadmins observed that our database write volume was consistently very high. This was a puzzle, but for a long time we left that unexplored. Eventually we thought to ask PostgreSQL s own statistics, and we found a surprise:
debusine=> SELECT relname AS table_name,
debusine->        n_tup_ins AS inserts,
debusine->        n_tup_upd AS updates,
debusine->        n_tup_del AS deletes,
debusine->        (n_tup_ins + n_tup_upd + n_tup_del) AS total_dml
debusine-> FROM pg_stat_user_tables
debusine-> WHERE (n_tup_ins + n_tup_upd + n_tup_del) > 0
debusine-> ORDER BY total_dml DESC
debusine-> LIMIT 20;
              table_name                inserts    updates     deletes   total_dml
--------------------------------------+---------+------------+---------+------------
 db_collectionitem                      1418251   3578202388   3630143   3583250782
 db_token                                 15143     11212106     11389     11238638
 db_workrequest                          386196      6399071   1820500      8605767
 db_fileinartifact                      2783021      1837929   1663887      6284837
 django_celery_results_taskresult       1819301      1501623   1791656      5112580
 db_artifact                             960077      3340859    663890      4964826
 db_collectionitemmatchconstraint       1550457            0   2207486      3757943
 db_artifactrelation                    2229382            0   1363825      3593207
 db_fileupload                          1023400      1057036   1023346      3103782
 db_file                                1673194            0    970252      2643446
 db_fileinstore                         1411995            0    970259      2382254
 db_filestore                                 0      2381578         0      2381578
 django_session                          645423      1519880       531      2165834
 db_workrequest_dependencies             365877            0    936537      1302414
 db_worker                                18317       949280      9487       977084
 db_collection                            10061           85    177741       187887
 db_workerpooltaskexecutionstatistics     28721            0         0        28721
 db_workerpoolstatistics                   1640            0         0         1640
 db_workflowtemplate                        130          158       649          937
 db_identity                                 76          661         0          737
(20 rows)
Oh my - that s a lot of db_collectionitem updates and must surely be out of proportion with what we really need. Can we narrow that down by asking about the most recently-updated tuples?
debusine=> SELECT DISTINCT category
debusine-> FROM db_collectionitem
debusine-> WHERE id IN (
debusine->     SELECT id FROM db_collectionitem
debusine->     ORDER BY xmin::text::integer DESC LIMIT 10000
debusine-> );
           category
------------------------------
 debusine:historical-task-run
(1 row)
That might not be absolutely reliable, but it was certainly a hint. As per PostgreSQL s documentation, by default UPDATE always performs physical updates to every matching row regardless of whether the data has changed, and our code to expire old task history entries wasn t doing that properly. Once we knew where to look, it was easy to add some extra constraints. This reduced our mean write volume on debusine.debian.net from about 23 MB/s to about 3 MB/s, which had an immediate knock-on effect on our request failure rate: Disk write graph HTTP errors

Current state Our metrics indicate that things are a lot better now. We still have a few things to deal with, such as:
  • Some more performance fixes are on their way to fix some remaining cases where views are very slow or where file uploads from workers fail due to locks.
  • We have some changes in the works to revamp how work request changes propagate through workflows in a way that doesn t require so many heavyweight locks.
  • We have a number of monitoring and alerting improvements we d like to make, both for outcomes (things like slow Celery tasks) and possible root causes (database performance). We d also like to deploy some more modern observability tools; hunting for things using journalctl isn t terrible, but it s not really the state of the art.
  • We need to improve how we communicate to users when we re having operational problems, both informally (IRC, etc.) and on the site.
  • Retries don t always behave the way you d expect in workflows.
I hope this has been an interesting tour through the sorts of things that can go wrong in this kind of distributed system!

9 May 2026

Russell Coker: Bad Criticism of LLMs (not AI)

Discussion of AI systems seems to be dominated by fears of uncommon and unlikely threats. I think that we should be focusing more on real issues with LLMs and with society in general and put the most effort towards the biggest problems. It s Not AI True Artificial Intelligence [1] (IE a computer that has the mental capacity of a household pet) is something that I think can be developed, but it hasn t been developed and we don t have good plans for developing it. We seem to be a lot further away from achieving that goal than we were from landing on the moon in 1962 when JFK gave his historic speech. What we have is a variety of pattern recognition systems that can predict what fits into a pattern. The most well known type of Machine Learning (ML) is the Large Language Model (LLM) which means ChatGPT and similar systems which predict which text would be likely to come next and can make an essay from it. They can give interesting and useful output, but there is no thought behind it, it s just a better form of Eliza (the famous program from 1964 that simulates conversation by pattern matching) [2]. By analysing billions of documents, storing the data in a condensed mathematical way, and then using computation to extract from that record LLMs can produce output that is unfortunately considered by some people to be good enough to include in legal documents submitted to courts, university assignments, and many other documents. But they do so without even having the thinking ability of a mouse. To call current systems AIs without any significant qualifiers when criticising them is to concede the debate about the worth of such things. If we develop AIs that can actually think we will have to deal with the issues in the SciFi horror short story Lena by qntm [3]. The Bad Arguments Here is a list of some of the most unreasonable arguments I ve seen against AI which distract attention from real problems both related to AI and other problems in society. Suicide and Homicide Wikipedia has a page listing Deaths Linked to Chatbots [4] which right now has 16 entries from 2023 to Feb 2026. They are all tragedies and as a society we should try to prevent such things. But what I would like to see from the media is some analysis of overall trends, yes it gets people s attention when someone dies in an unusual way but we need to have attention paid to the more numerous deaths which are preventable. It has become a standard practice to give information on Lifeline in media referencing suicide, it would be good if they also developed a practice of mentioning the relative incidence of a problem when publishing an article about it. One of the many factors that cause more suicides than chatbots is school, Scientific American has an informative article from 2022 about the correlation between child suicide and school [5]. It is based on US statistics and shows that the lowest suicide rate is in July (a no-school month in the US) which has a rate of 2.3 per 100,000 person years. So if kids had a quality of life equivalent to July all year around then there would be 2.3 suicides per 100,000 kids every year while if they had a quality of life equivalent to a Monday in January or November it would be 3.9 suicides per 100,000 kids every year. The article states Any time I present these data to teachers, parents, principals or school administrators, they are shocked. This should be common knowledge. It is common knowledge to anyone who takes any notice of what happens in schools, but paying attention to serious problems is unpleasant, it s more fun to pretend that school is good for everyone. No parent wants to think that they sent their child to a place that was horrible, no teacher wants to think that they are part of a system that harms kids. The US CDC has an informative article about youth suicide [6] which documents it as the 3rd largest cause of death in the 14-18 age range fro 2021. This article was published in 2024 and based on statistics from 2023 and earlier. It notes significant differences in suicides, attempts, and persistent feelings of sadness or hopelessness which had girls at more than twice the rate of boys and LGBQ+ kids at more than twice the rate of heterosexual students. It seems obvious that misogyny and homophobia is correlated with suicide and that s something that could and should be addressed in schools. My state has a Safer Schools program [7] to try and alleviate the problems related to homophobia, but I expect that things are getting worse in the US in that regard. 39.7% of kids in US high schools had persistent feelings of sadness or hopelessness before LLMs became popular, school could and should be a happy time for the vast majority of kids but instead almost half of the kids don t enjoy it and a majority of girls and LGBQ+ kids don t. Having no mention of trans kids is a significant omission from that article, based on everything I ve heard from trans people I expect that their statistics would be even worse. One could argue that the small number of deaths inspired by use or misuse of LLMs is an indication of a larger number of people suffering in ways that don t result in death and don t get noticed. But I don t think that can compare to the fact that the majority of girls and LGBQ+ kids have persistent feelings of sadness or hopelessness in the current school system. Regarding homicide, the Australian Institute of Criminology has an article showing that in the 2003-2004 time period 49% of women who were killed were listed as a domestic argument [8], that s something that could and should be addressed. That article claimed 308 homicide victims in that time period which is larger than the world-wide death toll from LLMs but also less than 1/3 the death toll from car accidents in Australia. Australia has less than 0.4% of the world population, a fairly low homicide rate, and a number of homicides that vastly outnumbers all world homicides related to LLMs. I think it s great to address any cause of suicide or homicide, but devoting government resources and legislation towards very uncommon causes instead of things that happen every day is not a good strategy. It would be fine to address all factors leading to suicide, but problems with the school system have been a major factor for decades with little effort applied to fix it. Fraud and Other Crime There is evidence of criminals using LLMs to help prepare for crimes, the ability to generate large amounts of text quickly can be used for fraud and extortion. This is going to be a serious problem and we need structural changes to society to deal with it. There is an ongoing issue of scammers convincing older people that their child or other young relative is in trouble and a large amount of cash is required to address it. This sort of scam as well as the more well known Nigerian scams will probably become more common as the cost of running them decreases. This may be more of a problem for people in developing countries as currently a common scam business model is to have people in regions where wages are low (such as Pakistan for one who I spoke to) scamming people in relatively wealthy countries like Australia so an attack with a low probability of success is financially viable. Cheaper attacks will make less affluent victims financially viable to the scammers. While writing this post I received a financial scam phone call trying to get me to invest in SpaceX that was run by an AI chat system, I expect to receive more of them and this is something that needs to be dealt with via both technical measures and legislation. Do we have to accept less freedom and less anonymity in finances as a cost of reducing financial crime? Greater restrictions on the use of cash would make some crimes more difficult or less profitable for criminals. As a society I think we need to have a discussion about a balance between financial freedom and freedom from criminal exploitation, failing to have such a discussion is likely to lead to policies which don t work well. Also one thing that ML systems are good at is recognising patterns in data. Banks could scan all their transactions and look for patterns that correlate with fraud. They currently do this badly and do things like locking credit cards when someone goes to another country and spends money. They could do a better job of that and involve the police in cases of obvious fraud even when the customer doesn t realise that they are a victim. This isn t a reason to criticise AIs , it s a reason to plan defensive technology that matches the capabilities of attackers. As an aside I used to work for a company that was developing AI software to scan bank phone calls and allow banks to recognise employees who acted illegally. Unfortunately the Royal Commission into banking misconduct [9] didn t impose any penalties that gave the banks a financial reason to avoid criminal activity. Unemployment and Inequality There are many claims about AI systems making large numbers of jobs obsolete, some of them are outlandish such as the claims that all white-collar jobs will be obsolete in the near future. There are some reasonable claims like the ability to replace some mundane jobs. Replacing jobs that suck with computers, robots, and other machinery is a good thing! Very few people wish that they were working on a farm without a tractor. In 1900 it s estimated that between 60% and 70% of the world labour force worked in agriculture and 40% of the US labour force did so. Now it s something like 27% globally and between 1% and 3% in developed countries. Automated factories are also a good thing, it s best to avoid boring and dangerous work. The most plausible claims about job replacement from AI is jobs that involve analysing and summarising documents. One example that comes to mind is the worst kind of journalism where press releases from companies are massaged into the format of a feature article. I don t think anyone wants that sort of job and doing it with AI hopefully means no human has to sign their name to it. For work like programming few people will be directly replaced by AI but if people can do their work more efficiently while using it then less people are required. I don t think that any programmer likes the part of their job where they have to skim read long documents looking for a clue about how to solve a problem with a library or protocol. A LLM processing the document and finding the potentially useful things will take away the drudgery from the work and allow greater productivity. The trend in replacing people has been making people work longer. If you force all employees to work 60 hour weeks then that can theoretically allow hiring fewer people than having 40 hour weeks. For some work that applies but for skilled work it mostly doesn t as productivity and work quality on average drops when people work more than 40 hours in a week. Another trend for exploiting people is having a low minimum wage and making accommodation expensive so that many people need to work two jobs. What we need is legislation to restore the situation in the 70s where a single full time job was sufficient to provide for a family. The low minimum wage and high expenses for many things is a problem that s been slowly developing over the course of decades while being mostly ignored by journalists. If they could concentrate on the real issues that are hurting workers today they could incite political action to fix these problems. Academic Cheating There is no shortage of ways of cheating in school and university. There are people who are paid to write essays, mobile phones are used for cheating in exams, etc. Getting an AI to write essays makes it easier to cheat for the essay writing part but does so with lower quality and in a less stealthy way. What s the worst case scenario? That we have to change to oral exams for all university subjects? In the US the average annual price for tuition at a university is apparently $25,000, if each student had individually supervised assessment for their exams at a cost of $100 per hour it would make the degree cost 4% more. The cost of university in the US is unreasonably high and that s a problem that needs to be fixed, but a hypothetical case of increasing the price by 4% isn t going to be a major part of it. Weak Arguments Against AI Computer Security Attacks There have been many claims made that AI will break the security of all systems and cause the type of disruption that was previously predicted for year 2000. Bruce Schneier has written a good analysis of the issues including how AI can be used by both attackers and defenders [10], he doesn t have a strong conclusion on whether the net result will be good or bad but his article does make it clear that the result is not going to be a total disaster. While I was working on this post I read another post by Bruce Schneier that was significantly more negative about this issue [11]. While I still don t think this will destroy civilisation I found his other post convincing enough to move computer security from the bad argument section to the weak argument section. Spidering the Web to Death There are issues of bots from AI companies doing a bad job of trying to download all the Internet s content and using a lot of resources. When it was just the major search engines and the Wayback Machine doing it the load was small due to having a small number of organisations that were very good at the way they did it having evolved practices over many years. Now we have a lot of idiots doing it badly and repeatedly hitting generated content. This is really annoying but is something that we can deal with. Currently my blog and many other sites are hosted on a Hetzner server with a E3-1271 v3 CPU with 32G of RAM and there are occasions where more than half the CPU power is being used to service web requests from such systems. Even on the server bidding (renting servers previously used by other customers) Hetzner isn t offering systems so slow nowadays, the slowest they offer is about 20% faster than that. This is something that can be dealt with by spending a little more on hosting until the companies doing that go bankrupt. I m sure this is a serious problem for some people, but for most people it s not a big deal. Also hostile traffic on the Internet is something we have all had to deal with as a part of life since the mid to late 90s. RAM Prices The unreasonably high prices for RAM are annoying and hurt the development of useful computer projects. Big companies can afford it, even with current high prices and large quantities of RAM used for some servers it s still not significant. But it is a major issue for hobbyists and small projects. Things like setting up a dozen test VMs for FOSS development are now too expensive for many people who develop software in their spare time. But this is a temporary thing, if AI companies were to keep buying RAM at high rates for a few years companies would just manufacture more of it to meet demand. In some situations capitalism can work. Environmental Damage There are many people claiming that power used by data centers for AI will lead to environmental damage, using power and water when there isn t enough. The trend of computer hardware is to get smaller and faster. It hasn t been going as fast as it used to in many areas but it hasn t stopped either and it s an exponential trend. There has been an increase in data centers (DCs) for AI use as the use has been increasing faster than the hardware gets smaller. Eventually they will stop increasing faster than advances in hardware and software can match and the size of DCs will decrease. As the production of renewable energy is increases the environmental cost of energy hungry industries decreases. In a few years this won t be an issue anyone is bothered about. False Claims About Danger as PR Jamie McClelland makes an interesting claim that the AI companies are pushing dangers of AI as a method of PR [12]. That seems plausible and combined with the tendency of many journalists to just massage press releases from companies into articles could be the reason for a lot of the bad arguments against AI. Good Arguments Against AI Spam Everywhere I ve previously written about Communication and Hostile AIs [13]. I think that filling all communication channels with rubbish is a denial of service attack against society. In the past communication took some effort, even the simplest email that was directly targeted at the recipient took some human effort and that reduced it s frequency. I get a lot of spam saying something like I see your web site doesn t rank in the top for Google searches while my web site in fact rates well and the actor named Russell Coker is ranking below me, so I know that such spam hasn t had the minimum of human involvement. Now a spammer who wanted to do a better job could get an LLM written spam for every target so the message was specifically aimed at them and would take much longer to be recognised by a human as spam and would also avoid most anti-spam software. Searching for businesses used to be easy, the phone book had listings for them and there was a real cost to being in the book as well as humans actively trying to stop fraud. Creating fake web sites to get business isn t too difficult but it s also not trivial at the moment and such fake sites won t look complete. Now with LLMs it s possible to create hundreds of sites that have content and look reasonable without human involvement. Instead of the small number of suicides and homicides inspired by AI chat systems we should probably be concerned about people who need psychological or medical advice being misled by bogus web sites created as part of fraud campaigns. Imagine people searching for mental health assistance finding web sites run by cults who oppose psychology as a profession. Imagine people searching for basic medical advice such as how to cook a healthy meal getting sucked in to web sites that start sane and then lead people to Ivermectin as a universal medicine. LLMs have the potential to take spam from quick and simple attacks to large scale targeted fraud aimed at people and organisations that don t have the resources to defend against it. There have been many reports of CEO impersonation fraud against major corporations aiming to steal hundreds of thousands of dollars and fraud against individuals who are persuaded to get amounts like $50,000 to help a relative who is allegedly in a difficult situation. But if every corner store experienced the same type of attack that CEOs experience and if every child had someone trying to steal the pocket money in the same way that relatively wealthy people are being targeted now it would really change things. David Brin wrote an insightful and informative blog post about this focusing on how AI generated content is being allowed to destroy YouTube [14]. Deep Fakes There is some overlap between filling all communications channels with rubbish (fake news etc) and deep fake. Making a fake photo of a politician or celebrity to lobby for legislative changes is a real issue but it s not what most people think of when the term deep fake is used. Using photo and video faking targeting non-consenting people is a serious issue. It s not just fake porn (which is a major issue and will cause some suicides) as there are many other possibilities. Fake videos showing behaviour that justifies sacking people from their jobs is going to become an issue, for people in public facing positions even proof that the videos are fake won t necessarily help them. Will we find ourselves in a situation where every politician gets deep-fake porn made of them and the only people who run for public office are ones who are cool with that? Will positions of leadership in the technology industry be restricted to people who aren t bothered by having the most depraved fake porn made of them? The Justice System We have seen a lot of evidence of law enforcement and the court system based on bias leading to bad results. The Innocence Project attempts to correct that and it s web site documents some of the things that have gone wrong [15]. Using AI systems to do some of the work of law enforcement by training computers on the flawed results of current systems can entrench bias and also make it harder to spot. When determining whether someone should be considered a suspect or whether a prisoner should be eligible for parole the number of factors that a human can use is limited. But a computer can take many more factors into account so the issues of whether inappropriate factors are being used can be masked. Computers are also unable to explain decisions that they made and are also able to come up with better fake reasons. In the past there have been racist policies in the US about banks not lending to people living in suburbs where most houses were owned by non-white people, these policies were documented and the documents have become part of the historical record showing racist policies. If a LLM decides not to lend money to people based on mathematical correlations it determined based on historical banking practices it could assign negative weights to factors such as non-English names and implement the racism in a large array of numbers with no proof. The current cases of lawyers getting LLM systems to do some of their work and having their incompetence revealed when the computer generated work is shown to be ridiculously bad are amusing. But that is not the real problem. The real problems will start when the computers in police cars start flagging every car owned by a non-write person as having a probable cause for a drug stop. Technically Not Financial Fraud The majority of the ecosystem around AI is a financial scam [16]. There are companies and individuals doing good things with machine learning some of which is based on hardware and software developed as part of this ecosystem. But the majority of it has no plausible path to profits and a the future of it inevitably ends with some bankruptcies. There are circular flows of money that have the major cloud providers and NVidia looped in, when the values of these companies correct it will become apparent that they have all burned a lot of money keeping this running and all the senior people have got a share of it (the entire purpose of stock options is to allow senior people to suck money out of the company). Then every cloud provider will increase costs while under chapter 11 and all the companies that depend on them will pay whatever it takes. That includes all major companies and most governments. Unlike the dot-com boom and crash and the housing crash the coming financial crash will impact every company that we deal with and most governments. So the people in first-world countries will effectively be taxed to pay for this scam while the executives go party in Monaco. This may seem like an extreme claim but it all happened before with the dot com crash and the housing market crash. The CEO class has an ongoing practice of doing things that aren t crimes because they lobby (bribe) politicians to make them legal. So the current stock market shenanigans around AI don t seem to involve things that governments consider to be crimes. But any normal person might be surprised to learn that such things are legal and most people would vote for such things to be crimes if they had the opportunity. A global financial crisis is the least of the problems that seem likely to afflict society from AI systems. But it will be more immediately obvious when it happens which could be this year! Propaganda Creating art requires skills that the type of people who want to create propaganda tend to lack. AI technologies allow creating art that is based on mathematical models of actual art to the requirements of the person running the program. I have seen the term AI Fascism used to describe the use of AI to help authoritarian governments. I am dubious about whether it deserves that term and while every article I ve read about the topic has had some good points I thought that they were all weak points. But there are lots of ways that governments can abuse their populations without going full fascist. In the last century there were lots of truly terrible governments that didn t even make the top 10 of fascism. AI Sycophants Bruce Schneier wrote an informative blog post about AI Chatbots and Trust which focused on sycophantic chatbots [17]. We have seen a lot of evidence of terrible behaviour and stupid decisions from rich people due to having no negative consequences for bad choices. The vast majority of the history of kings concerns bad decisions made by such people. A future where middle class and poor people can make the same bad decisions as rich people wouldn t be good. Good Things About ML Machine Learning (abbreviated as ML) can do useful things. It s not just Large Language Models (LLMs) such as ChatGPT etc. There are also ML systems that can analyse images and other data sets. I have found ChatGPT to be very useful for making suggestions for improving blog posts. I don t get it to write anything just ask for suggestions. It has pointed out things that I missed such as when I didn t include the price when reviewing a car because the car in question was much more expensive than I will ever pay, the price wasn t relevant to me but would be to some readers. It has also made useful suggestions about structure of blog posts, repeating points, and having a good conclusion. It has some downsides which include trying to erase my voice from my writing, suggesting that the rhetorical question does email suck? is unprofessional. I have worked for a company that used ML systems to analyse driver performance and alert people if a driver is falling asleep, using a phone, or otherwise seems unable to drive safely. Their business model involved a human reviewing the images from the drivers the computer flagged and then determining who is actually doing the wrong thing. This seems a good use of the technology. I have also worked for a company that used ML systems to analyse the performance of bank employees and detect potentially fraudulent behaviour. Preventing crime seems to be clearly a good thing and in this case the manager of the employee in question would review the evidence to make sure that they weren t being falsely accused. Conclusion I don t think that the problems with managing the changes that so called AI is introducing are particularly new. An example of how society handles change that s worth considering is car safety. The seat belt first became mandatory for aeroplanes in some jurisdictions in 1928. The Model T Ford is widely regarded as the first vehicle to start a mass market for cars and it was released in 1925. So if society acted in a reasonable way then for the majority of mass market cars seat belts would have been a standard feature. However seat belts were first made compulsory in 1970 in Victoria Australia and there are still people who think that they are safer without seat belts! The delay in adoption of car seat belts is only one example of needless deaths caused by not taking reasonable measures for car safety but it s one that s easy to demonstrate and measure. The difference between past problems like car safety and the current problems of AI is that the AI problems will be more pervasive. Most of my history as a car driver and car passenger was in cars that are much less safe than cars made in the last 10 years. But partly through luck I ve never been in a serious crash so being in cars that wouldn t have given me a low probability of surviving a freeway speed crash didn t affect me. There is no possibility that through any combination of luck and skill someone could avoid the downsides of AI . If nothing else the results of elections will be affected and no-one can avoid that. As a society we really need to address the real issues related to AI which in some cases requires legislation.

7 May 2026

Reproducible Builds: Reproducible Builds in April 2026

Welcome to our April 2026 report from the Reproducible Builds project! Our reports outline what we ve been up to over the past month, highlighting items of news from elsewhere in the increasingly-important area of software supply-chain security. As ever, if you are interested in contributing to the Reproducible Builds project, please see the Contribute page on our website. In this month s report, we cover:

  1. Tor stateless relays and Reproducible Builds
  2. Civil Infrastructure Platform celebrates 10 years of supporting industrial grade Linux
  3. Reproducible Builds at LinuxFest NorthWest
  4. Reproducibility issues in Rust binaries that embed random bytes
  5. Distribution work
  6. Patches
  7. diffoscope development
  8. Documentation updates
  9. Misc news


Tor stateless relays and Reproducible Builds An interesting post was published on Tor Project blog by Osservatorio Nessuno OdV this month on stateless relays . These are stateless, diskless operating systems that are designed to be used as Tor exit relays. According to the post, which is titled A Server That Forgets: Exploring Stateless Relays:
For relay operators, this approach raises the security bar by enforcing better behaviors by design: [ ]
  1. Reproducibility. A system that doesn t change between reboots is easier to verify and, eventually, to reproduce and audit.
Furthermore, using a Trusted Platform Module (TPM), could allow for greater integrity in the future:
Transparency logs. Once you have a measured boot chain, you can publish it. A relay operator provides a recipe for a reproducible build; anyone can recompute the expected hash and verify it matches what the TPM reports. An append-only transparency log can make these attestations publicly auditable. The Tor community could run an independent monitor to track this across the relay fleet.

Civil Infrastructure Platform celebrates 10 years of supporting industrial grade Linux Congratulations to the Civil Infrastructure Platform (CIP) for reaching their 10-year anniversary last month. CIP has been a supporter of Reproducible Builds for many years, and we have collaborated on a number of technical issues that overlap. As Chris Lamb mentions in CIP s press release:
The collaboration between the Reproducible Builds project and CIP highlights a critical shift in how we approach industrial software. Through verifiability, CIP ensures that the open source foundation of our critical infrastructure is not only sustainable but also demonstrably secure. This commitment to transparency is vital for the trust and resilience required by critical systems over decades of operation.

Reproducible Builds at LinuxFest NorthWest Vagrant Cascadian and Chris Lamb hosted a table in the exposition hall at LinuxFest NorthWest 2026 this month in Bellingham, WA, USA, introducing many people to Reproducible Builds and answering questions both days of the conference. In addition, Vagrant presented Beyond Trusting Open Source Software on Sunday afternoon, exploring the intersection of Free/Open Source Software, Reproducible Builds and Bootstrappable builds, and how they all reinforce each other. Vagrant s slides are available online, including source code to build them reproducibly.

Reproducibility issues in Rust binaries that embed random bytes Reproducible Builds developer kpcyrd opened a ticket on the Rustsec issue tracker regarding binaries that deliberately inject random bytes into their binaries as a secret seed for a Hash Collision DoS mitigation. As kpcyrd notes in his message, this causes issues for reproducibility, and because the relevant end-user binaries are mostly distributed pre-compiled through package managers, those binaries (and by extension the secret seed) are public knowledge . kpcyrd goes on to note:
This is somewhat unique to Rust because Python/JavaScript doesn t compile binaries, and Go (to my knowledge) is too restrictive during build for any library to pull something like this.

Distribution work In Arch Linux this month, Robin Candau and Mark Hegreberg worked at adding a new repro tag/version to the Arch Linux Docker images providing a bit-for-bit reproducible image. Robin also shared a related announcement and implementation details on our mailing list. Arch Linux developer Robin Candau posted a blog post announcing that Arch Linux Now Has a Bit-for-Bit Reproducible Docker Image . Robin mentions one interesting caveat:
to ensure reproducibility, the pacman [package manager] keys have to be stripped from the image, meaning that pacman is not usable out of the box in this image. While waiting to find a suitable solution to this technical constraint, we are therefore providing this reproducible image under a dedicated tag as a first milestone. [ ]
The blog post was also discussed on Hacker News.
In Debian this month, 24 reviews of Debian packages were added, 7 were updated and 16 were removed this month adding to our knowledge about identified issues. Vagrant Cascadian performed Non-Maintainer Uploads (NMUs) in Debian for several packages with outstanding patches over a year old jakarta-jmeter, wxmplot, critcl, vcsh and magic-wormhole-transit-relay. In addition, Reproducible Builds developer Jochen Sprickerhof filed a bug against the APT package manager to request that APT should ignore [a] 0 epoch when downloading or installing with a version specifier . This is related to the special-case handling of the optional epoch prefix in Debian package version numbers.
In NixOS, Julien Malka presented Lila: Decentralized Build Reproducibility Monitoring for the Functional Package Management Model, a paper written together with Arnout Engelen at the Mining Software Repositories (MSR) ACM conference, where it was awarded the MSR 2026 FOSS Impact Award. Congratulations!
Lastly, in openSUSE, Michael Schroeder added reproducibility verification support in the Open Build Service [ ] and Bernhard M. Wiedemann posted another openSUSE monthly update for their reproducibility work there.

Patches The Reproducible Builds project detects, dissects and attempts to fix as many currently-unreproducible packages as possible. We endeavour to send all of our patches upstream where applicable or possible. This month, we wrote a large number of such patches, including:

diffoscope development diffoscope is our in-depth and content-aware diff utility that can locate and diagnose reproducibility issues. This month, Chris Lamb made a number of changes, including preparing and uploading versions, 316, 317 and 318 to Debian.
  • Chris Lamb:
    • Bump Standards-Version to 4.7.4. [ ]
    • Correct ordering of python3-guestfs architecture restrictions. [ ]
    • Limit python3-guestfs Build-Dependency to architectures that are not i386. [ ]
    • Try to fix PYPI_ID_TOKEN debugging. [ ]
  • Holger Levsen:
    • Add ppc64el to the list of python3-guestfs architecture whitelist. (Closes: #1132974). [ ]
In addition, Vagrant Cascadian updated diffoscope in GNU Guix to version 317.

Documentation updates Yet again, there were a number of improvements made to our website this month including:
  • Manuel Jacob:
    • Fix some minor wording issues on the Stable inputs page, and update information about the sorting behavior of GNU Make [ ].
    • On the Archives page, remove information about deterministic archives in historical Fedora versions [ ], add a note about .tar file portability [ ] and correct a section about .tar PAX headers [ ].
  • Mattia Rizzolo:
  • kpcyrd:
  • ktecho:

Misc news On our mailing list this month:
  • Timo Pohl posted our list inviting people to online group discussions with 4-6 participants each to talk about your perception of terms and requirements for reproducibility. As Timo notes:
    During our research of the existing literature, as well as my experience at the Reproducible Builds Summit 2025 in Vienna, we noticed that some of the terminology in the field is not used consistently across different groups of people, and that the precise meaning of some core terms like reproducibility of an artifact in itself is not uniform.
    As Timo mentions, the sessions will last roughly 90 minutes and will be rewarded with 50 per participant.
  • kpcyrd posted to the list asking for assistance with fixing an issue after updating the flake.lock file for their repro-env project.
  • Aman Sharma of the KTH Royal Institute of Technology, Sweden, posted to our list in order to share that Eric Cornelissen, a PhD student in KTH s CHAINS group, is maintaining an open-source project to monitor the reproducibility of GitHub Actions:
    The goal of the project is to assess whether GitHub Actions can be reproduced. Currently, it focuses on two types of Actions: JavaScript-based actions and Docker-based actions (composite actions are not considered). For JavaScript actions, the project rebuilds the distributed files and compares them bit-by-bit with the repository contents. For Docker actions, it rebuilds images from the Dockerfile and checks for semantic equivalence, using diffoci, across builds.


Finally, if you are interested in contributing to the Reproducible Builds project, please visit our Contribute page on our website. However, you can get in touch with us via:

23 April 2026

Sergio Talens-Oliag: Developing a Git Worktree Helper with Copilot

Over the past few weeks I ve been developing and using a personal command-line tool called gwt (Git Worktree) to manage Git repositories using worktrees. This article explains what the tool does, how it evolved, and how I used GitHub Copilot CLI to develop it (in fact the idea of building the script was also to test the tool).

The Problem: Managing Multiple BranchesI was working on a project with multiple active branches, including orphans; the regular branches are for fixes or features, while the orphans are used to keep copies of remote documents or store processed versions of those documents. The project also uses a special orphan branch that contains the scripts and the CI/CD configuration to store and process the external documents (it is on a separate branch to avoid mixing its operation with the main project code). The plan is trigger a pipeline against the special branch from remote projects to create or update the doc branch for it in our git repository, retrieving artifacts from the remote projects to get the files and put them on an orphan branch (initially I added new commits after each update, but I changed the system to use force pushes and keep only one commit, as the history is not really needed). The original documents have to be changed, so, after ingesting them, we run a script that modifies them and adds or updates another branch with the processed version; the contents of that branch are used by the main branch build process (there we use git fetch and git archive to retrieve its contents). When working on the scripts to manage the orphan branches I discovered the worktree feature of git, a functionality that allows me to keep multiple branches checked out in parallel using a single .git folder, removing the need to use git switch and git stash when changing between branches (until now I ve been a heavy user of those commands). Reading about it I found that a lot of people use worktrees with the help of a wrapper script to simplify the management. After looking at one or two posts and the related scripts I decided to create my own using a specific directory structure to simplify things. That s how I started to work on the gwt script; as I also wanted to test copilot I decided to build it using its help (I have a pro license at work and wanted to play with the cli version instead of integrated into an editor, as I didn t want to learn a lot of new keyboard shortcuts).

The gwt Philosophy: Opinionated and Transparentgwt enforces a simple, filesystem-visible model:
  • Exactly one bare repository named bare.git (treated as an implementation detail)
  • One worktree directory per branch where the directory name matches the branch name
  • Single responsibility: gwt doesn t try to be a general git wrapper; it only handles operations that map cleanly to this layout
The repository structure looks like this:
my-repo/
+-- bare.git/           # the Git repository (internal)
+-- main/               # worktree for branch "main"
+-- feature/api/        # worktree for branch "feature/api"
+-- fix/docs/           # worktree for branch "fix/docs"
+-- orphan-history/     # worktree for the "orphan-history" branch
The tool follows five core design principles:
  1. Explicit over clever: Git commands are not hidden or reinterpreted
  2. Transparent execution: Every operation is printed before it happens
  3. Safe, preview-first operations: Destructive commands default to preview, confirmation, then apply
  4. Shell-agnostic core: The script never changes the caller s working directory (shell wrappers handle that)
  5. Opinionated but minimal: Only commands that fit the layout model are included

Core CommandsThe script provides these essential commands:
  • gwt init <url> Clone a repository and set up the gwt layout
  • gwt convert <dir> Convert an existing Git checkout to the gwt layout
  • gwt add [--orphan] <branch> [<base>] Create a new worktree (optionally orphaned)
  • gwt remove <branch> Remove a worktree and unregister it (asks the user to remove the local branch too, useful when removing already merged branches)
  • gwt rename <old> <new> Rename a branch AND its worktree directory
  • gwt list List all worktrees
  • gwt default [<branch>] Get or set the default branch
  • gwt current Print the current worktree or branch name
Except init and convert all of the commands work inside a directory structure that follows the gwt layout, which looks for the bare.git folder to find the root folder of the structure. As I don t want to hide which commands are really used by the wrapper, all git and filesystem operations pass through a single run shell function that prints each command before executing it. This gives complete visibility into what the tool is doing. Also, destructive operations (remove, rename) default to preview mode:
$ gwt remove feature-old --dry-run
+ git -C bare.git branch -d feature-old
+ git -C bare.git worktree remove feature-old/
Apply these changes? [y/N]:
The user sees exactly what will happen, can verify it s correct, and only then confirm execution.

Incremental Development with CopilotThe gwt script has grown from 597 lines in its original version (git-wt) to 1,111 lines when writing the first draft of this post. This growth happened through incremental, test-driven development, with each feature being refined based on real usage patterns. What follows is a little history of the script evolution written with the help of git log.

Initial versionFirst I wrote a design document and asked copilot to create the initial version of the git-wt script with the original core commands. I started to use the tool with a remote repostory (I made copies of the branches in some cases to avoid missing work) and fixed bugs (trivial ones with neovim, larger ones asking copilot to fix the issues for me, so I had less typing to do).

First command updateOne of the first commands I had to enhance was rename:
  • as I normally use branches with / on their name and my tool checks out the worktrees using the branch name as the path inside the gwt root folder (i.e. a fix/rename branch creates the fix directory and checks the branch inside the fix/rename folder) the rename command had to clean up the empty parent directories
  • when renaming a worktree we move the folders and fix the references using the worktree repair command to make things work locally, but the rename also affects the remote branch reference, to avoid surprises the command unsets the remote branch reference so it can be pushed again using the new name (of course, the user is responsible of managing the old remote branch, as the gwt can t guess what it should do with it).

Integration with the shellAs I use zsh with the Powerlevel10k theme I asked copilot to help me add visual elements to the prompt when working with gwt folders, something that I would have never tried without help, as it would have required a lot of digging on my part on how to do it, as I never looked into it. The initial version of the code was on an independent file that I sourced from my .zshrc file and it prints on the right part of the prompt when we are inside a gwt folder (note that if the folder is a worktree we see the existing git integration text right before it, so we have the previous behavior and we see that it is a gwt friendly repo) and if we are on the root folder or the bare.git folder we see gwt or bare (I added the text because there are no git promts on those folders). I also asked copilot to create zsh autocompletion functions (I only use zsh, so I didn t add autocompletion for other shells). The good thing here is that I wouldn t have done that manually, as it would have required some reading to get it right, but the output of copilot worked and I can update things using it or manually if I need to. One thing I was missing from the script was the possibility of changing the working directory easily, so I wrote a gwt wrapper function for zsh that intercepts commands that require shell cooperation (changing the working directory) and delegates everything else to the core script. Currently the function supports the following enhanced commands:
  • cd [<branch>]: change into a worktree or the default one if missing
  • convert <dir>: convert a checkout, then cd into the initial worktree
  • add [--orphan] <branch> [<base>]: create a worktree, then cd into it on success
  • rename <old> <new>: rename a worktree, then cd into it if we were inside it
Note that the cd command will not work on other shells or if the user does not load my wrapper, but the rest will still work without the working directory changes.

Renaming the commandAs I felt that git-wt was a long name I renamed the tool to gwt, I could have done it by hand, but using copilot I didn t have to review all files by myself and it did it right (note that I have it configured to always ask me before doing changes, as it sometimes tries to do something I don t want and I like to check its changes as I have the files in git repos, I manually add the files when I like the status and if the cli output is not clear I allow it to apply it and check the effects with git diff so I can validate or revert what was done).

The convert commandAfter playing with one repo I added the convert subcommand for migrating existing checkouts, it seemed a simple task at first, but it took multiple iterations to get it right, as I found multiple issues while testing (in fact I did copies of the existing checkouts to be able to re-test each update, as some of the iterations broke them). The version of the function when this post was first edited had the following comment explaining what it does:
# ---------------------------------------------------------------------------
# convert - convert an existing checkout into the gwt layout
# ---------------------------------------------------------------------------
#
# Must be run from the parent directory of <dir>.
#
# Steps:
#   1. Read branch from the checkout's HEAD
#   2. Rename <dir> to <dir>.wt.tmp (sibling, same filesystem)
#   3. Create <dir>/ as the new gwt root
#   4. Move <dir>.wt.tmp/.git to <dir>/bare.git; set core.bare = true
#   5. Fix fetch refspec (bare clone default maps refs directly, no remotes/)
#   6. Add a --no-checkout worktree so git wires up the metadata and
#      creates <dir>/<branch>/.git (the only file in that dir)
#   7. Move that .git file into the real working tree (<dir>.wt.tmp)
#   8. Remove the now-empty placeholder directory
#   9. Move the real working tree into place as <dir>/<branch>
#  10. Reset the index to HEAD so git status is clean
#      (--no-checkout leaves the index empty)
#  11. Create <dir>/.git -> bare.git symlink so plain git commands work
#      from the root without --git-dir
#
# The .git file ends up at the same absolute path git recorded in step 5,
# so no worktree repair is needed. Working tree files are never modified.
The .git link was added when I noticed that I could run commands that don t need the checked out files on the root of the gwt structure, which is handy sometimes (i.e. a git fetch or a git log, that shows the log of the branch marked as default). After playing with commands that used the bare.git folder I updated the init and convert commands to keep the origin refs, ensuring that the remote tracking works correctly.

Improving the add commandWhile playing with the tool on more repos I noticed that I also had to enhance the add command to better handle worktree creation, depending on my needs. Right now the tool supports the following use cases:
  • if the branch exists locally or on origin, it just checks it out.
  • if the branch does not exist, we create it using the given base branch or, if no base is given, the current worktree (if we are in the root folder or bare.git the command fails).
  • as I needed it for my project, I added a --orphan option to be able to create orphan branches directly.

Moving to a single fileEventually I decided to make the tool self contained; I removed the design document (I moved the content to comments on the top of the script and details to comments on each function definition) and added a pair of commands to print the code to source for the p10k and zsh integration (autocompletion & functions), leaving everything in a single file. Now my .zshrc file adds the following to source both things:
# After loading the p10k configuration
if type gwt >/dev/null 2>&1; then
  source <(gwt p10k)
fi
[...]
# After loading autocompletion
if type gwt >/dev/null 2>&1; then
  source <(gwt zsh)
fi

VersioningAs I modified the script I found interesting to use CalVer-based versioning (the version variable has the format YYYY.mm.dd-r#) so I added a subcommand to show its value or bump it using the current date and computing the right revision number.

About the use of copilotAlthough I ve never been a fan of AI tools I have to admit that the copilot CLI has been very useful for building the tool:
  • Rapid prototyping: Each commit represented a small feature or fix that I could implement, test immediately in my actual workflow, and iterate on based on the result
  • Edge case handling: Rather than trying to anticipate every scenario upfront, I could ask Copilot how to handle edge cases as they appeared in real usage
  • Script refinement: Questions like "how do I clean up empty directories after a rename" or "how do I detect if I m inside a specific worktree" were quickly answered with working code
  • Shell integration: The Zsh wrapper and completion system grew from simple prototypes to sophisticated features, with each iteration informed by how I actually used the tool
For example, the convert command started as a simple rename operation, but evolved to also create a .git symlink and intelligently handle various migration scenarios all because I used it repeatedly and refined the implementation each time.

Self-Contained and Opinionatedgwt is deliberately opinionated:
  • Zsh & Powerlevel10k Integration: The tool includes built-in Zsh shell integration, accessed via source <(gwt zsh) and supports adding a prompt segment when using p10k, as described earlier.
  • Directory Structure: The bare.git directory name is non-negotiable. This is how gwt discovers the repository root from any subdirectory, and how the tool knows whether a directory is a gwt repository. The simplicity of this marker means the discovery mechanism is foolproof and requires no configuration.
  • No Configuration Files: gwt deliberately has no configuration. There are no .gwtrc files or config directories. This makes it portable; the tool works the same way everywhere, and repositories can be shared across systems without synchronizing configuration.

From Script to SystemWhat started as a small helper script for managing worktrees has become a complete system:
  1. Core script (gwt): 1,111 lines of pure shell, no external dependencies
  2. Shell integration: Zsh functions and completions
  3. Prompt integration: Powerlevel10k segment
  4. Documentation: Built-in help and design philosophy documentation
The script is self-contained, everything needed for the tool to work is in a single file. This makes it trivial to update (just replace the script) or audit (no hidden dependencies).

Development with AI supportDeveloping gwt with copilot taught me some things:
  • Incremental refinement works well for small tools: Each iteration informed the next, resulting in a tool that handles real use cases elegantly
  • Transparency is a feature: Making operations visible builds confidence and is easier to debug
  • Opinionated tools can be powerful: By constraining the problem space (one bare repo, one worktree per branch), the solution becomes simpler and more robust
  • Shell integration matters: The same core commands are easier to use when they can automatically change directories and provide completions
  • Real-world testing is essential: I wouldn t have discovered the need for automatic directory cleanup or context-aware cd behavior without actually using the tool daily

What was next?The tool is stable and handles my daily workflow well, so my guess is that I would keep using it and fixing issues if or when I found them, but I do not plan to include additional features unless I find a use case that justifies it (i.e. I never added support for some of the worktree subcommands, as it is easier to use the git versions if I ever needed them).

What really happenedWhile editing this post I discovered that I needed to add another command to it and fixed a bug (see below). With those changes and the inclusion of a license and copyright notice (just in case I distribute it at some point) now the script is 1,217 lines long instead of the 1,111 it had when I started to write this entry.

Submodule SupportWhen I converted this blog repository to the gwt format and tried to preview the post using docker compose, it failed because the worktree I was on didn t have the Git submodule initialized. My blog theme is included on the repository as a submodule, and when I used gwt to check out different branches in worktrees, the submodule was not initialized in the new worktrees. This led me to add new internal function and a gwt submodule command to handle submodule initialization; the internal function is called from convert and add (when converting a repo or adding a worktree) and the public command is useful to update the submodules on existing branches.

Path Handling with Branch Names Containing SlashesThe second discovery was a bug in how the tool handled branch names containing slashes (e.g., feature/new-api, docs/user-guide), the worktree directories are created with the branch name as the path, so a branch like feature/new-api would create two nested folders (feature and new-api inside it). However, there was a mismatch in how the zsh wrapper function resolved worktree paths (initially it used shell parameter expansion, i.e. rel="$ cwd#"$REPO_ROOT"/ "), versus how the core script calculated them, causing the cd command to fail or navigate to the wrong location when branch names contained slashes. The fix involved ensuring consistent path resolution throughout the script and wrapper (now it uses a function that processes the git worktree list output), so that gwt cd feature/new-api correctly navigates to the worktree directory regardless of path depth.

Conclusiongwt is a tool that solves a real problem: managing multiple Git branches simultaneously without context-switching overhead. I m sure I m going to keep using it for my projects, as it simplifies some workflows, although I ll also use switch and stash in some cases, but I like the use of multiple worktrees in parallel. In fact I converted this blog repository checkout to the gwt format to work on a separate branch as it felt the right approach even if I m the only one using the repo now, and it helped me improve the tool, as explained before. Also, it was a good example of how to use AI tools like copilot to develop a simple tool and keep it evolving while using it. In any case, although I find the copilot useful and has saved me time, I don t trust it to work without supervision, it worked well, but got stuck some times and didn t do the things as I wanted in multiple occasions. I also have an additional problem now I ve been reading about it, but I don t really know which models to use or how the premium requests are computed (I ve only been playing with it since last month and I ran out of requests the last day of the month on purpose, just to see what happened it stops working ;). On my work machine I ve been using a specific user account with a GitHub Copilot Business subscription and I only used the Anthropic Claude Sonnet 4.6 model and with my personal account I configured the Anthropic Claude Haiku 4.5 model, but I ve only used that to create the initial draft of this post (I ended up rewriting most of it manually anyway) and to review the final version (I m not a native speaker and it was useful for finding typos and improving the style in some parts). I guess I ll try other models with copilot in the future and check other command line tools like aider or claude-code, but probably only using free accounts unless I get a payed account at work, as I have with GitHub Copilot. To be fair, what I will love to be able to do is to use local models (aider can do it), but the machines I have are not powerful enough. I tried to run a simple test and it felt really slow, but when I have the time or the need I ll try again, just in case.

22 April 2026

Dirk Eddelbuettel: nanotime 0.3.14 on CRAN: Upstream Maintenance

Another minor update 0.3.14 for our nanotime package is now on CRAN, and has compiled for r2u (and will have to wait to be uploaded to Debian until dependency bit64 has been updated there). nanotime relies on the RcppCCTZ package (as well as the RcppDate package for additional C++ operations) and offers efficient high(er) resolution time parsing and formatting up to nanosecond resolution, using the bit64 package for the actual integer64 arithmetic. Initially implemented using the S3 system, it has benefitted greatly from a rigorous refactoring by Leonardo who not only rejigged nanotime internals in S4 but also added new S4 types for periods, intervals and durations. This release has been driven almost entirely by Michael, who took over as bit64 maintainer and has been making changes there that have an effect on us downstream . He reached out with a number of PRs which (following occassional refinement and smoothing) have all been integrated. There are no user-facing changes, or behavioural changes or enhancements, in this release. The NEWS snippet below has the fuller details.

Changes in version 0.3.14 (2026-04-22)
  • Tests were refactored to use NA_integer64_ (Michael Chirico in #149 and Dirk in #156)
  • nanoduration was updated for changes in nanotime 4.8.0 (Michael Chirico in #152 fixing #151)
  • Use of as.integer64(keep.names=TRUE) has been refactored (Michael Chirico in #154 fixing #153)
  • In tests, nanotime is attached after bit64; this still needs a better fix (Michael Chirico in #155)
  • The package now has a hard dependency on the just released bit64 version 4.8.0 (or later)

Thanks to my CRANberries, there is a diffstat report for this release. More details and examples are at the nanotime page; code, issue tickets etc at the GitHub repository and all documentation is provided at the nanotime documentation site.

This post by Dirk Eddelbuettel originated on his Thinking inside the box blog. If you like this or other open-source work I do, you can now sponsor me at GitHub. You can also sponsor my Tour de Shore 2026 ride in support of the Maywood Fine Arts Center.

Next.

Previous.