Latest Posts (20 found)
Evan Hahn Yesterday

Notes from May 2026

My blog turned 16 this month! I did nothing to celebrate, but made some little tools and clicked some links about tech ethics. I published four little tools this month: I also did some work on Helmet, my open source project: And like every month, I wrote a few articles at Zelda Dungeon . I don’t feel I wrote anything special this month, but my colleagues put together a feature about Zelda and mental health which was very affecting! “The vast majority of tech workers, at least those who I have encountered in my many years of reporting, are not vampiric Silicon Valley tech bro caricatures [… They] both like working with tech and ultimately want to see it serve the public good.” From “They just formed the biggest tech worker union in the US. They plan to rein in AI and curb layoffs” . This “love letter to Gnutella” is both an introduction to a P2P protocol and a celebration of the culture around it. From “Affordances for me, but not for thee” : “One of the oddest parts of the AI shift is that people are much more willing to do things for LLMs that they should have been doing for human beings all along.” Accessibility, specifications, documentation, and policies are better codified now. The author calls this “dystopian”, and I agree: our motivation to do this stuff is AI or productivity, not helping our fellow human. “More importantly, whereas accessibility affordances provide new abilities for vulnerable people, an AI affordance provides new abilities for people with power. And that’s probably the heart of it.” Looking forward to being surveilled because I’m an “anti-tech extremist” . I can’t tell you how exciting it was to watch Jira add 2 + 3 . “What can I do to resist AI?” asks the AI Resist List . “Tech companies like Google, Facebook and Microsoft are ignoring data controls mandated under California law, researchers say.” “Your AI Slop Bores Me” presents an interface that looks like an LLM chatbot, but it’s entirely powered by humans. A very cute idea. I’m a very bad “image generator”, at least according to the ratings I received. I continue to be amazed by “Lest We Forget the Horrors: An Unending Catalog of Trump’s Cruelties, Collusions, Corruptions, and Crimes” . It’s so thorough. RIP to a real one: Wikinews is shutting down after 21 years . Hope you had a good May. ZIP Shrinker , a web app that shrinks ZIP files with higher compression ratios A command line tool to do (completely offline) translation Open Link in Unloaded Tab , a Firefox extension to open links without loading them png-cmp , a command line tool to compare PNG pixel data After over a year of quiet maintenance, I released version 8.2.0 with some small new features and documentation updates. In a step toward dropping GitHub, I moved the docs from a GitHub URL to helmet.js.org . “The vast majority of tech workers, at least those who I have encountered in my many years of reporting, are not vampiric Silicon Valley tech bro caricatures [… They] both like working with tech and ultimately want to see it serve the public good.” From “They just formed the biggest tech worker union in the US. They plan to rein in AI and curb layoffs” . This “love letter to Gnutella” is both an introduction to a P2P protocol and a celebration of the culture around it. From “Affordances for me, but not for thee” : “One of the oddest parts of the AI shift is that people are much more willing to do things for LLMs that they should have been doing for human beings all along.” Accessibility, specifications, documentation, and policies are better codified now. The author calls this “dystopian”, and I agree: our motivation to do this stuff is AI or productivity, not helping our fellow human. “More importantly, whereas accessibility affordances provide new abilities for vulnerable people, an AI affordance provides new abilities for people with power. And that’s probably the heart of it.” Looking forward to being surveilled because I’m an “anti-tech extremist” . I can’t tell you how exciting it was to watch Jira add 2 + 3 . “What can I do to resist AI?” asks the AI Resist List . “Tech companies like Google, Facebook and Microsoft are ignoring data controls mandated under California law, researchers say.” “Your AI Slop Bores Me” presents an interface that looks like an LLM chatbot, but it’s entirely powered by humans. A very cute idea. I’m a very bad “image generator”, at least according to the ratings I received. I continue to be amazed by “Lest We Forget the Horrors: An Unending Catalog of Trump’s Cruelties, Collusions, Corruptions, and Crimes” . It’s so thorough. RIP to a real one: Wikinews is shutting down after 21 years .

0 views
Evan Hahn 2 weeks ago

Make ZIP files smaller with ZIP Shrinker

I built ZIP Shrinker, a little browser tool to shrink ZIP files. It also works with formats that are secretly ZIPs underneath, like APK, EPUB, JAR, and many more. Try it out! At a high level, this tool (1) re-compresses every file in the ZIP archive with higher compression (2) removes all metadata (3) removes entries for directories. ZIP files are typically compressed with an algorithm called Deflate . There are a few tools that can re-compress Deflate data and make it smaller, usually by spending more time on the computation. I took one of these tools, libdeflate , and applied it to each compressed entry in the ZIP. I chose libdeflate because of its performance; alternatives like Zopfli can achieve marginally smaller results but take much longer. I created libdeflate.js , a WebAssembly wrapper for libdeflate, as part of this work. (I always relish my time working with WASM!) Each entry in a ZIP file can contain additional metadata like comments. These aren’t typically used, and if they’re there, my shrinker removes them. This usually doesn’t save too many bytes, but it doesn’t hurt. Removing directories is a slightly spicier decision. Usually, the existence of a file entry implies the existence of the directory it’s inside. For example, implies the existence of the directory. Some ZIPs include separate entries for directories, but because most extractors don’t need them, I remove those. This has the side effect of removing empty directories— let me know if that’s a problem for you. If you want to see how the whole project works, check out the full source code . I tested several ZIPs to see what this tool could do. Some anecdotal results: Not particularly scientific, but useful to see. This proof-of-concept shows that you can make ZIP files smaller without sacrificing backwards compatibility. It could be useful for sending an archive to someone, but could also be useful to reduce bandwidth and server costs. For example, if Project Gutenberg re-compressed all their EPUB books with this method, they might be able to save some money. Of course, ZIP isn’t always the most efficient format. Typically, other archives like can be smaller. But those aren’t backwards-compatible! ZIP also supports compression methods other than Deflate. They’re atypical, but you could use them to achieve a smaller result, too. Give my tool a try if you want a smaller ZIP.

0 views
Evan Hahn 2 weeks ago

Open Link in Unloaded Tab, a little Firefox extension

In short: I just published Open Link in Unloaded Tab , a little Firefox extension that adds “Open Link in Unloaded Tab” to the right-click context menu. In Firefox, you can unload tabs to save system resources. But there’s no way to open a new tab in the unloaded state…until now! I built a very simple extension that adds a new option to do this. (It even has a cute icon which I paid ~$15 for.) I’ve built one-off extensions before, but this is the first one I’ve submitted to the Firefox Add-ons directory. Download the extension here or check out the source code .

0 views
Evan Hahn 1 months ago

Offline command line translation with TranslateGemma + Ollama

I wrote a simple script that translates text at the command line, completely offline. Here’s an example of how it works on my computer: It combines a few tools: Here’s the pseudocode of how it works: I built this because I couldn’t find anyone else who had done it. It’s written in Deno for my specific needs—for example, it only translates text into your system’s language—but could easily be adapted if you need something else. I like that I can do offline, private, automatic translation. It’s imperfect, but useful for me! Here’s the source code. TranslateGemma , a special-purpose language model for translation Ollama , a tool for running language models locally Efficient Language Detector , a library that detects the language for a piece of text

0 views
Evan Hahn 1 months ago

Notes from April 2026

After a busy March , April was a little quieter. But don’t worry, I still have a bunch of little links for you to click on. GitHub’s uptime hasn’t been great recently. Even though I dislike the Microsoft subsidiary, I wrote “In defense of GitHub’s poor uptime” , which argues that it’s not as bad as folks seems to be saying. See this Lobsters thread for some discussion. Published version 0.2.0 of , my JavaScript library for waiting a very long time . Like every month, I published a few articles over at Zelda Dungeon . We had a slew of shitposts for April Fool’s this year. I think my favorite (not written by me) was “Daily Debate: Do You Think Old Man Was Once Young Man?” . Ghost, my employer, was recognized as a digital public good . “You can’t advertise people out of reacting to their own experiences. This is a fundamental disconnect between how tech people with software brains see the world and how regular people are living their lives.” From “The people do not yearn for automation” . I subscribed to the RSS feed for internet RFCs . Most of this is over my head but it’s neat! This post argues that “any gains we do realize [from LLM coding] are likely going to be incremental and evolutionary, rather than the world-changing revolution many people seem to be expecting.” Matches my experience. Loved this piece about LLM-assisted development, talking about the real pros and cons for a production project. Bookmarked this list of books critical of AI , from 1976 to today. (I’ve only read 1.5 of the books on this list.) This pair of podcast episodes about driverless cars ends with a poignant thought: the haters need to “imagine visions of the future more vivid than the word ’no’.” Same applies to other technologies. “For the last 20 years, employees could accept the intense external criticism and awkward conversations with family and friends about working for a company named after J.R.R. Tolkien’s corrupting all-seeing orb. But a year into Trump’s second term, as Palantir deepens its relationship with an administration many workers fear is wreaking havoc at home, employees are finally raising these concerns internally, as the US’s war on immigrants, war in Iran, and even company-released manifestos has forced them to rethink the role they play in it all.” From “Palantir Employees Are Starting to Wonder if They’re the Bad Guys” . Spotted the Fuck Capitalism game jam . This is the kind of nerdy punk rock stuff I like to see!! Hope you had a good April. GitHub’s uptime hasn’t been great recently. Even though I dislike the Microsoft subsidiary, I wrote “In defense of GitHub’s poor uptime” , which argues that it’s not as bad as folks seems to be saying. See this Lobsters thread for some discussion. Published version 0.2.0 of , my JavaScript library for waiting a very long time . Like every month, I published a few articles over at Zelda Dungeon . We had a slew of shitposts for April Fool’s this year. I think my favorite (not written by me) was “Daily Debate: Do You Think Old Man Was Once Young Man?” . Ghost, my employer, was recognized as a digital public good . “You can’t advertise people out of reacting to their own experiences. This is a fundamental disconnect between how tech people with software brains see the world and how regular people are living their lives.” From “The people do not yearn for automation” . I subscribed to the RSS feed for internet RFCs . Most of this is over my head but it’s neat! This post argues that “any gains we do realize [from LLM coding] are likely going to be incremental and evolutionary, rather than the world-changing revolution many people seem to be expecting.” Matches my experience. Loved this piece about LLM-assisted development, talking about the real pros and cons for a production project. Bookmarked this list of books critical of AI , from 1976 to today. (I’ve only read 1.5 of the books on this list.) This pair of podcast episodes about driverless cars ends with a poignant thought: the haters need to “imagine visions of the future more vivid than the word ’no’.” Same applies to other technologies. “For the last 20 years, employees could accept the intense external criticism and awkward conversations with family and friends about working for a company named after J.R.R. Tolkien’s corrupting all-seeing orb. But a year into Trump’s second term, as Palantir deepens its relationship with an administration many workers fear is wreaking havoc at home, employees are finally raising these concerns internally, as the US’s war on immigrants, war in Iran, and even company-released manifestos has forced them to rethink the role they play in it all.” From “Palantir Employees Are Starting to Wonder if They’re the Bad Guys” . Spotted the Fuck Capitalism game jam . This is the kind of nerdy punk rock stuff I like to see!!

0 views
Evan Hahn 1 months ago

In defense of GitHub's poor uptime

In short: GitHub’s downtime is bad, but uptime numbers can be misleading. It’s not as bad as it looks; more like a D than an F. 99.99% uptime, or “four nines”, is a common industry standard. Four nines of uptime is equivalent to 1.008 minutes of downtime per week. GitHub is not meeting that, and it’s frustrating. Even though they’re owned by Microsoft’s, one of the richest companies on earth, they aren’t clearing this bar. Here are some things people are saying: According to “The Missing GitHub Status Page” , which reports historical uptime better than GitHub’s official source, they’ve had 89.43% uptime over the last 90 days. That’s zero nines of uptime. That implies more than 2.5 hours of downtime every day ! I dislike GitHub and Microsoft, so I shouldn’t be coming to their defense, but I think this characterization is unfair. I’m no mathematician, but let’s do a little math. Let’s say your enterprise has two services: Service A and Service B. Over the last 10 days: 3 of the last 10 days had outages. That’s 70% uptime total. (That’s how the Missing GitHub Status Page calculates it.) GitHub’s status page lists ten services: core Git operations, webhooks, Issues, and more. Sometimes they’re down simultaneously, but usually not. If all ten of those services have 99% uptime and outages don’t overlap, it’d look like GitHub had 90% uptime because some part of GitHub is out 10% of the time. That’s much worse! The numbers look better if outages happen at the same time. For example, if Service A and Service B go down on Saturday and Sunday, you’d have 80% uptime overall instead of 70%. Compared to the previous scenario, Service A is down twice as long, but the uptime number looks better. A downstream effect of this calculation is that your uptime numbers look worse if your services are well-isolated . I think it’s good that Service A doesn’t take down Service B! I think it’s good that a GitHub Packages outage doesn’t take down GitHub Issues! But if all you see is one aggregate uptime number, you might miss that. Things look rosier when you look at features individually. Over the last 90 days, core Git operations have had 98.98% uptime, or about 22 hours where things were broken. That’s still bad, but not as bad as some people are saying. D tier, not F tier. Also, an incident doesn’t mean everything is broken. For example, GitHub recently had an issue where things were slow for users on the west coast of the United States. Not good , but not “everything is broken for all users”. Again, the number doesn’t tell the whole story. I still think GitHub’s uptime is unacceptably low, especially because they’re owned by Microsoft, but I don’t think we’re being honest when we say that GitHub has “zero nines” of availability. To me, it’s more like: they have a bunch of unstable services which cumulatively have horrible uptime, but individually have not-very-good uptime. There are better reasons to dislike these companies. “GitHub appears to be struggling with measly three nines availability” “World’s First Enterprise Solution With Zero Nines Uptime” “Sure, they may have made the uptime worse, but remember what we got in exchange – when it’s up, the UI is slower and buggier.” Service A had one day of downtime. That means it has 90% uptime. Service B had two days of downtime on different days. That means it has 80% uptime.

0 views
Evan Hahn 2 months ago

Notes from March 2026

March always seems to be my life’s busiest month. “The two kinds of error” : in my mind, software errors are divided into two categories: expected and unexpected errors. I finally wrote up this idea I’ve had for a long time. “All tests pass” is a short story about a strange, and sorta sad, experience I had with a coding agent. Inspired by others, I published a disclaimer about how I use generative AI to write this blog . My main rule of thumb: the final product must be word-for-word what I would’ve written without AI, given enough time. And I have discomfort about its use. Built llm-eliza , a plugin for LLM that lets you use the ELIZA chatbot at the command line. I think this is my first satirical software project. (Also the first thing I’ve published to the Python package registry, PyPI.) Found the human.json standard , which is “a protocol for humans to assert authorship of their site content and vouch for the humanity of others.” I added it to my site this month. Scraped Rosetta Code and built a stupid little website that picks a random programming language . At work, I helped with a project to improve the editor for Ghost’s “welcome emails” feature . This month marked the one year anniversary of my first post on Zelda Dungeon . I celebrated by writing more articles, including a treatise the difference between 2D and 3D games and a personal piece about Ocarina of Time . I also wrote my first article that contained an interview , which was a skill I’m totally new to. It’s a small change, but I fixed a little bug in fzf . From a tale about vibe coding : “I’d be embarrassed to show it at a code review. I’d also be embarrassed to admit how many times I failed to ship the ‘clean’ version.” “Claude is the only AI model that has actually been deployed inside classified [American] military systems. So to the extent that AI is having an effect in Iran, it is probably Claude.” From a Hard Fork podcast episode . From “AI’s Enthusiasm Chasm” : “people—well, again, most people—don’t enjoy existing in a strict state of quantification. Pursuits and pastimes—joy—are underpinned by qualitative thought, and those considerations make people less likely to want to involve AI just to get something at a tenth of the cost or five times faster.” “The Cognitive Dark Forest” posits that AI forces us, socially, to close down the open web. “The sheer act of thinking outside the box makes the box bigger.” This post has a good—if incomplete—list of all the downsides of generative AI: perpetuation of bias, erosion of critical thinking, harm to artists, and more. Uber used to be inexpensive because it was subsidized by VC money. Now it’s more costly because they needed to stop losing money. “Don’t get used to cheap AI” posits that the same will happen with AI. Similar ideas are presented in “Is the Future of AI Local?” . From “It’s time to embrace climate conspiracy” : “the actual story of climate change—the one we’ve reported exhaustively—is one about coordinated power, deliberate deception, and a bought-off government that repeatedly acts to promote an industry that is poisoning humans and the environment for profit. It just so happens to be a real conspiracy.” Really liked this short piece about what’s lost when new technology becomes commonplace . Few people today remember what we lost when we switched from candles to lightbulbs. “we don’t need more ram, we need better software” had me whispering “hell yeah” to myself. I’ve long pondered a blog post called “Why I’m afraid of YAML”. This post from a former colleague says it better than I ever could. “Costs of War” highlights the costs, financial and otherwise, of the United States’s wars. The US FBI is buying location data for surveillance , as is our Secret Service . This review of the new Marathon shooter game was surprisingly poignant. “It’s just thoughts and if I don’t get them out, my tummy hurts.” As a Legend of Zelda fan and programmer, I was happy to discover YouTuber Skawo . Their videos explain Zelda quirks by delving into real source code. I especially liked this explanation of why some players were experiencing rumble in a game that shouldn’t have it . The US effectively bans foreign-made routers. Hope you had a good March. “The two kinds of error” : in my mind, software errors are divided into two categories: expected and unexpected errors. I finally wrote up this idea I’ve had for a long time. “All tests pass” is a short story about a strange, and sorta sad, experience I had with a coding agent. Inspired by others, I published a disclaimer about how I use generative AI to write this blog . My main rule of thumb: the final product must be word-for-word what I would’ve written without AI, given enough time. And I have discomfort about its use. Built llm-eliza , a plugin for LLM that lets you use the ELIZA chatbot at the command line. I think this is my first satirical software project. (Also the first thing I’ve published to the Python package registry, PyPI.) Found the human.json standard , which is “a protocol for humans to assert authorship of their site content and vouch for the humanity of others.” I added it to my site this month. Scraped Rosetta Code and built a stupid little website that picks a random programming language . At work, I helped with a project to improve the editor for Ghost’s “welcome emails” feature . This month marked the one year anniversary of my first post on Zelda Dungeon . I celebrated by writing more articles, including a treatise the difference between 2D and 3D games and a personal piece about Ocarina of Time . I also wrote my first article that contained an interview , which was a skill I’m totally new to. It’s a small change, but I fixed a little bug in fzf . From a tale about vibe coding : “I’d be embarrassed to show it at a code review. I’d also be embarrassed to admit how many times I failed to ship the ‘clean’ version.” “Claude is the only AI model that has actually been deployed inside classified [American] military systems. So to the extent that AI is having an effect in Iran, it is probably Claude.” From a Hard Fork podcast episode . From “AI’s Enthusiasm Chasm” : “people—well, again, most people—don’t enjoy existing in a strict state of quantification. Pursuits and pastimes—joy—are underpinned by qualitative thought, and those considerations make people less likely to want to involve AI just to get something at a tenth of the cost or five times faster.” “The Cognitive Dark Forest” posits that AI forces us, socially, to close down the open web. “The sheer act of thinking outside the box makes the box bigger.” This post has a good—if incomplete—list of all the downsides of generative AI: perpetuation of bias, erosion of critical thinking, harm to artists, and more. Uber used to be inexpensive because it was subsidized by VC money. Now it’s more costly because they needed to stop losing money. “Don’t get used to cheap AI” posits that the same will happen with AI. Similar ideas are presented in “Is the Future of AI Local?” . From “It’s time to embrace climate conspiracy” : “the actual story of climate change—the one we’ve reported exhaustively—is one about coordinated power, deliberate deception, and a bought-off government that repeatedly acts to promote an industry that is poisoning humans and the environment for profit. It just so happens to be a real conspiracy.” Really liked this short piece about what’s lost when new technology becomes commonplace . Few people today remember what we lost when we switched from candles to lightbulbs. “we don’t need more ram, we need better software” had me whispering “hell yeah” to myself. I’ve long pondered a blog post called “Why I’m afraid of YAML”. This post from a former colleague says it better than I ever could. “Costs of War” highlights the costs, financial and otherwise, of the United States’s wars. The US FBI is buying location data for surveillance , as is our Secret Service . This review of the new Marathon shooter game was surprisingly poignant. “It’s just thoughts and if I don’t get them out, my tummy hurts.” As a Legend of Zelda fan and programmer, I was happy to discover YouTuber Skawo . Their videos explain Zelda quirks by delving into real source code. I especially liked this explanation of why some players were experiencing rumble in a game that shouldn’t have it . The US effectively bans foreign-made routers.

0 views
Evan Hahn 2 months ago

How I use generative AI on this blog

Inspired by others, I’m publishing how I use generative AI to write this little blog. Generative AI, like any technology, has tradeoffs. I think the cons far outweigh the pros. In other words, the world would be better off without generative AI. Despite this belief, I use it. I’m effectively forced at work, but I also use LLMs to help write this personal blog. I think they can produce better writing if used correctly. Also: I want to be critical of this technology. Specifically, I want to change the minds of “AI maxxers”, not preach to those who already hate it. If I never used this stuff, AI lovers wouldn’t listen to me. These people are more likely to respect criticism from a daily user who’s sympathetic to the benefits. I think there’s space for critique from a user of a technology they wish didn’t exist. I feel discomfort and tension about this, which I hope comes through. With that, let’s get to the specifics. My main rule of thumb: the final product must be word-for-word what I would’ve written without AI , given enough time. I use it in two main ways: I prefer local models that run on my phone and laptop. I’ll keep this post updated. Like a thesaurus. For example, I recently asked, “What’s another way to say that a book was overly positive, not critical of its subject matter?” I used one of its suggestions, “flattering”, in my final draft. Quick brainstorming for specifics. For example, I was listing types of software error in a recent post and asked it for more examples. I plucked one of its many answers—null pointer exceptions—and discarded the rest.

0 views
Evan Hahn 3 months ago

Introducing gzpeek, a tool to parse gzip metadata

In short: gzip streams contain metadata, like the operating system that did the compression. I built a tool to read this metadata. I love reading specifications for file formats. They always have little surprises. I had assumed that the gzip format was strictly used for compression. My guess was: a few bytes of bookkeeping, the compressed data, and maybe a checksum. But then I read the spec . The gzip header holds more than I expected! In addition to two bytes identifying the data as gzip, there’s also: The operating system that did the compression. This was super surprising to me! There’s a single byte that identifies the compressor’s OS: for Windows, for the Amiga, for Unix, and many others I’d never heard of. Compressors can also set for an “unknown” OS. Different tools set this value differently. zlib, the most popular gzip library, changes the flag based on the operating system . (It even defines some OSes that aren’t in the spec, like for BeOS.) Many other libraries build atop zlib and inherit this behavior, such as .NET’s , Ruby’s , and PHP’s . Java’s , JavaScript’s , and Go’s set the OS to “unknown” regardless of operating system. Some, like Zopfli and Apache’s , hard-code it to “Unix” no matter what. All that to say: in practice, you can’t rely on this flag to determine the source OS, but it can give you a hint. Modification time for the data. This can be the time that compression started or the modification time of the file. It can also be set to if you don’t want to communicate a time. This is represented as an unsigned 32-bit integer in the Unix format. That means it can represent any moment between January 1, 1970 and February 7, 2106. I hope we devise a better compression format in the next ~80 years, because we can only represent dates in that range. In my testing, many implementations set this to . A few set it to the current time or the file’s modification time—the command is one of these. FTEXT , a boolean flag vaguely indicating that the data is “probably ASCII text”. When I say vaguely, I mean it: the spec “deliberately [does] not specify the algorithm used to set this”. This is apparently for systems which have different storage formats for ASCII and binary data. In all my testing, nobody sets this flag to anything but . An extra flag indicating how hard the compressor worked. signals that it was compressed with max compression (e.g., ), for the fastest algorithm, and for everything else. In practice, zlib and many others set this correctly per the spec, but some tools hard-code it to . And as far as I can tell, this byte is not used during decompression, so it doesn’t really matter. The original file name . For example, when I run , the name is set to . This field is optional, so many tools don’t set it, but the command line tool does. You can disable that with . A comment . This optional field is seldom used, and many decompressors ignore it. But you could add a little comment if you want. Extra arbitrary data . If the other metadata wasn’t enough, you can stuff whatever you want into arbitrary subfields. Each subfield has a two-byte identifier and then 0 or more bytes of additional info. That’s way more info than I expected! I was intrigued by this metadata and I’ve been wanting to learn Zig , so I wrote gzpeek . gzpeek is a command-line tool that lets you inspect the metadata of gzip streams. Here’s how to read metadata from a gzipped file: It extracts everything I listed above: the operating system, original file name, modification time, and more. I used it a bunch when surveying different gzip implementations. Give it a try, and let me know what gzip metadata you find. The operating system that did the compression. This was super surprising to me! There’s a single byte that identifies the compressor’s OS: for Windows, for the Amiga, for Unix, and many others I’d never heard of. Compressors can also set for an “unknown” OS. Different tools set this value differently. zlib, the most popular gzip library, changes the flag based on the operating system . (It even defines some OSes that aren’t in the spec, like for BeOS.) Many other libraries build atop zlib and inherit this behavior, such as .NET’s , Ruby’s , and PHP’s . Java’s , JavaScript’s , and Go’s set the OS to “unknown” regardless of operating system. Some, like Zopfli and Apache’s , hard-code it to “Unix” no matter what. All that to say: in practice, you can’t rely on this flag to determine the source OS, but it can give you a hint. Modification time for the data. This can be the time that compression started or the modification time of the file. It can also be set to if you don’t want to communicate a time. This is represented as an unsigned 32-bit integer in the Unix format. That means it can represent any moment between January 1, 1970 and February 7, 2106. I hope we devise a better compression format in the next ~80 years, because we can only represent dates in that range. In my testing, many implementations set this to . A few set it to the current time or the file’s modification time—the command is one of these. FTEXT , a boolean flag vaguely indicating that the data is “probably ASCII text”. When I say vaguely, I mean it: the spec “deliberately [does] not specify the algorithm used to set this”. This is apparently for systems which have different storage formats for ASCII and binary data. In all my testing, nobody sets this flag to anything but . An extra flag indicating how hard the compressor worked. signals that it was compressed with max compression (e.g., ), for the fastest algorithm, and for everything else. In practice, zlib and many others set this correctly per the spec, but some tools hard-code it to . And as far as I can tell, this byte is not used during decompression, so it doesn’t really matter. The original file name . For example, when I run , the name is set to . This field is optional, so many tools don’t set it, but the command line tool does. You can disable that with . A comment . This optional field is seldom used, and many decompressors ignore it. But you could add a little comment if you want. Extra arbitrary data . If the other metadata wasn’t enough, you can stuff whatever you want into arbitrary subfields. Each subfield has a two-byte identifier and then 0 or more bytes of additional info.

0 views
Evan Hahn 3 months ago

Track Zelda release anniversaries in your calendar

The original Legend of Zelda came out 40 years ago today. With other birthdays on the horizon, like Twilight Princess ’s 20th in November, I wanted a calendar that showed the anniversary of every Zelda game. So I made one. Subscribe to this URL in your calendar app: Once you do, you’ll get calendar events on the anniversary of each game’s release. For example, you’ll be able to see that the Oracle games turn 25 in less than a week…I feel old. If you want to build this file yourself, I wrote a little Python script that generates an ICS file from a CSV of release dates .

0 views
Evan Hahn 4 months ago

A mental math heuristic to convert between Fahrenheit and Celsius

I sometimes have to convert between Fahrenheit and Celsius. The actual formula is hard to do in my head, but someone once told me a useful approximation: For example, if it’s 12ºC, this heuristic would return 54ºF. (12 × 2) + 30 = 54. The actual amount is not far off: 53.6ºF. To convert the other way: 68ºF becomes 19ºC. (68 − 30) ÷ 2 = 19. Again, this is close to the actual answer of 18ºC. These are pretty close because the numbers we’re using (2 and 30) are pretty close to their counterparts in the real formula (1.8 and 32). This isn’t exact, of course. But it’s come in handy! Now if we could only get the US to use the metric system … To convert from Celsius to Fahrenheit, double it and add 30. To convert from Fahrenheit to Celsius, subtract 30 and halve it (the reverse).

1 views
Evan Hahn 4 months ago

I set all 376 Vim options and I'm still a fool

I set all of Vim’s configuration options. I still feel far from mastery. I first saw someone use Vim during an internship in 2012. I had been coding for many years and I fancied myself pretty good at shortcuts, but I was quickly humbled. I watched in awe as experienced users zipped around the code. A single keystroke could move the cursor halfway across the file to exactly the right spot. Code was ripped apart and reshaped like putty. “ Wow ,” I thought to myself, and probably said out loud. I vowed to master this editor but I was slow. When I wasn’t accidentally opening some unknown menu, I was taking an uneconomical path through the code. I pressed j twenty times instead of running 20j , or manually deleted code inside parenthesis instead of running di( . Sometimes I’d open another text editor to give my mind a break from all the key bindings! Fast-forward to 2025. After tons of practice, I felt much more capable. Code did feel more like putty. I was working closer to the speed of thought. I could get code where I wanted much more quickly. 13 years of practice paid off! But Vim still felt clumsy. I was still accidentally opening menus I didn’t recognize. I would do silly things like converting the whole file to lowercase, or trigger some scary error message. “Surely I shouldn’t be making these mistakes,” I thought. What could be done to finally master this editor? That desire for expertise led me on a quest to set all of Vim’s options . I would make an informed decision about all 376 of Vim’s settings and drop them in my . In other words, I wanted to 100% Vim. Surely, setting every Vim option would make me the fluent expert I wanted to be…right? I pored over every single Vim option and made a decision. What did the option do, and what did I want it to be set to? My goal was to be thorough; leave no stone unturned. I only set the option after I understood it. Eventually, after countless hours, I had done it. I had set every single Vim option. This exercise taught me plenty about day-to-day usage and the editor’s inner workings. I spent a lot of time with the documentation, Vim’s source code, and online forums. Here’s a grab bag of things I learned: How to use external commands. I didn’t realize I could send text from a Vim buffer to an external command, and vice-versa. Now I can write regular programs that operate on stdin. For example, I now frequently use a program to remove “smart quotes” , which I call without leaving the editor. See and for details. Vim’s docs are mixed. I frequently ran the command for something, read the docs, and came away clueless. Thanks to the great people of the internet for so many great explanations! One of the flags stumped me so much that I had to ask my own question on Vi Stack Exchange because nobody had really documented it before. How Vim writes files. When you run :w , it’s just saving the file—how hard could it be? Turns out, it’s complicated! If you’re overwriting a file, Vim (by default) creates a backup of the old version, then overwrites it with the new version. This can help avoid data loss in some situations. But there’s a lot of complexity here: in the fate of that backup file after a successful write , in how the backup switcharoo happens , and where the backup is saved . This is probably irrelevant for most people, but I had to understand the details in order to set every option. The command-line window—which I’d always open by accident—is super useful. The option taught me about the command-line window . I frequently opened this by running q: instead of :q , and didn’t know what I had done. Now I know: a useful way to search and edit the history of previous commands and searches. For example, if I run some long command and make a typo, I can open the command-line window and fix the error using regular Vim keybindings. Digraphs are an obscure feature for typing obscure characters. For example, you can enter “½” in Insert mode with CTRL-K 1 2 . There’s a big list in . I don’t use this much, except for typing fractions, but I use this more than I thought I would. How to set options conditionally. I knew about regular conditionals with things like . But I didn’t know that you could set a Vim option only if it’s supported with code like . Vim and Neovim have more differences than I thought. Among the many changes , I was pleased to learn that pasting is smoother in Neovim , that Q repeats the last recorded macro , it makes running arbitrary code safer , and has a much nicer default color scheme. Vim used to have a different name. Today, “Vim” stands for “Vi IMproved”, an old Unix editor from the 1970s. But it was originally called Vi IMitation ! This is just a sample of my many discoveries. For more, see my heavily-annotated . As of this writing, my configuration file is nearly 2900 lines long. This exercise was fun. It also taught me a lot about the editor I use every day. I definitely feel more skilled! Yet the feeling of awkwardness remains. Even after ~14 years, I still accidentally open the command-line window all the time. I mess up filtering commands, or get lost in the jumplist . I still sometimes press k ten times instead of running 10k . I discovered one thing about myself. The feeling of true Vim fluency—one where every keystroke is exact, I never make mistakes, and I’m exploiting every obscure feature—is a fantasy, at least for me. That’s a comforting constant in this topsy-turvy life: I’ll always have more to learn about Vim. Click here to see the finished product. How to use external commands. I didn’t realize I could send text from a Vim buffer to an external command, and vice-versa. Now I can write regular programs that operate on stdin. For example, I now frequently use a program to remove “smart quotes” , which I call without leaving the editor. See and for details. Vim’s docs are mixed. I frequently ran the command for something, read the docs, and came away clueless. Thanks to the great people of the internet for so many great explanations! One of the flags stumped me so much that I had to ask my own question on Vi Stack Exchange because nobody had really documented it before. How Vim writes files. When you run :w , it’s just saving the file—how hard could it be? Turns out, it’s complicated! If you’re overwriting a file, Vim (by default) creates a backup of the old version, then overwrites it with the new version. This can help avoid data loss in some situations. But there’s a lot of complexity here: in the fate of that backup file after a successful write , in how the backup switcharoo happens , and where the backup is saved . This is probably irrelevant for most people, but I had to understand the details in order to set every option. The command-line window—which I’d always open by accident—is super useful. The option taught me about the command-line window . I frequently opened this by running q: instead of :q , and didn’t know what I had done. Now I know: a useful way to search and edit the history of previous commands and searches. For example, if I run some long command and make a typo, I can open the command-line window and fix the error using regular Vim keybindings. Digraphs are an obscure feature for typing obscure characters. For example, you can enter “½” in Insert mode with CTRL-K 1 2 . There’s a big list in . I don’t use this much, except for typing fractions, but I use this more than I thought I would. How to set options conditionally. I knew about regular conditionals with things like . But I didn’t know that you could set a Vim option only if it’s supported with code like . Vim and Neovim have more differences than I thought. Among the many changes , I was pleased to learn that pasting is smoother in Neovim , that Q repeats the last recorded macro , it makes running arbitrary code safer , and has a much nicer default color scheme. Vim used to have a different name. Today, “Vim” stands for “Vi IMproved”, an old Unix editor from the 1970s. But it was originally called Vi IMitation !

0 views
Evan Hahn 4 months ago

Notes from "On Writing Well"

I’ve been trying to improve my writing so I read On Writing Well by William Zinsser. My main takeaways: Clear thinking is a prerequisite for clear writing. How do you avoid cluttered writing? “The answer is to clear our heads of clutter. Clear thinking becomes clear writing; one can’t exist without the other. It’s impossible for a muddy thinker to write good English.” Reduce scope. Zinsser hammers this point repeatedly. For instance: “Nobody can write a book or an article ‘about’ something. Tolstoy couldn’t write a book about war and peace, or Melville a book about whaling. They made certain reductive decisions about time and place and about individual characters in that time and place—one man pursuing one whale. Every writing project must be reduced before you start to write.” Keep the thesis in mind. “Writers must […] constantly ask: what am I trying to say? Surprisingly often they don’t know. Then they must look at what they have written and ask: have I said it?” I don’t want to write like this guy. I think Zinsser’s writing is dogmatic, verbose, outdated, and above all: not to my taste. But that helps me clarify my own style by showing me what I don’t want to do. And despite all that, I agree with a lot of his recommendations. Even though there were many parts I disliked, I think On Writing Well holds better advice than a writing guide I read last year . I hope my writing improves as a result of reading this book. Clear thinking is a prerequisite for clear writing. How do you avoid cluttered writing? “The answer is to clear our heads of clutter. Clear thinking becomes clear writing; one can’t exist without the other. It’s impossible for a muddy thinker to write good English.” Reduce scope. Zinsser hammers this point repeatedly. For instance: “Nobody can write a book or an article ‘about’ something. Tolstoy couldn’t write a book about war and peace, or Melville a book about whaling. They made certain reductive decisions about time and place and about individual characters in that time and place—one man pursuing one whale. Every writing project must be reduced before you start to write.” Keep the thesis in mind. “Writers must […] constantly ask: what am I trying to say? Surprisingly often they don’t know. Then they must look at what they have written and ask: have I said it?”

0 views
Evan Hahn 5 months ago

Notes from "Bad Company: Private Equity and the Death of the American Dream"

Bad Company: Private Equity and the Death of the American Dream is a book about private equity in the United States. My main takeaway: private equity is bad . I also learned a few other straightforward lessons: This book further cemented a belief I hold: it’s harmful to pursue profit above all else. Private equity has one goal: to maximize shareholder value. As you might imagine, this causes lots of problems. Private equity firms may acquire a business with no intention of keeping it running. It can be more profitable to shutter the business. Leveraged buyouts dramatically lower the risk to the private equity buyer. Government subsidies and laws often make it easier for private equity firms to operate.

0 views
Evan Hahn 5 months ago

How I implemented relative imports with Pyodide

I was recently playing with Pyodide , the WebAssembly Python runtime. I wanted to have my main code import a utility file. Something like this: This took me awhile to figure out! I’m not convinced I have the best solution , but here’s what I did: Here’s what my JavaScript loader code looked like: This worked for me, but I wish there were a cleaner solution. Maybe Pyodide has a way to hook into ? If you have a better solution, please reach out ! Fetch with . Save it to Pyodide’s virtual file system. Run the main code!

0 views
Evan Hahn 6 months ago

Experiment: making TypeScript immutable-by-default

I like programming languages where variables are immutable by default. For example, in Rust , declares an immutable variable and declares a mutable one. I’ve long wanted this in other languages, like TypeScript, which is mutable by default—the opposite of what I want! I wondered: is it possible to make TypeScript values immutable by default? My goal was to do this purely with TypeScript, without changing TypeScript itself. That meant no lint rules or other tools. I chose this because I wanted this solution to be as “pure” as possible…and it also sounded more fun. I spent an evening trying to do this. I failed but made progress! I made arrays and s immutable by default, but I couldn’t get it working for regular objects. If you figure out how to do this completely, please contact me —I must know! TypeScript has built-in type definitions for JavaScript APIs like and and . If you’ve ever changed the or options in your TSConfig, you’ve tweaked which of these definitions are included. For example, you might add the “ES2024” library if you’re targeting a newer runtime. My goal was to swap the built-in libraries with an immutable-by-default replacement. The first step was to stop using any of the built-in libraries. I set the flag in my TSConfig, like this: Then I wrote a very simple script and put it in : When I ran , it gave a bunch of errors: Progress! I had successfully obliterated any default TypeScript libraries, which I could tell because it couldn’t find core types like or . Time to write the replacement. This project was a prototype. Therefore, I started with a minimal solution that would type-check. I didn’t need it to be good! I created and put the following inside: Now, when I ran , I got no errors! I’d defined all the built-in types that TypeScript needs, and a dummy object. As you can see, this solution is impractical for production. For one, none of these interfaces have any properties! isn’t defined, for example. That’s okay because this is only a prototype. A production-ready version would need to define all of those things—tedious, but should be straightforward. I decided to tackle this with a test-driven development style. I’d write some code that I want to type-check, watch it fail to type-check, then fix it. I updated to contain the following: This tests three things: When I ran , I saw two errors: So I updated the type in with the following: The property accessor—the line—tells TypeScript that you can access array properties by numeric index, but they’re read-only. That should make possible but impossible. The method definition is copied from the TypeScript source code with no changes (other than some auto-formatting). That should make it possible to call . Notice that I did not define . We shouldn’t be calling that on an immutable array! I ran again and…success! No errors! We now have immutable arrays! At this stage, I’ve shown that it’s possible to configure TypeScript to make all arrays immutable with no extra annotations . No need for or ! In other words, we have some immutability by default. This code, like everything in this post, is simplistic. There are lots of other array methods , like and and ! If this were made production-ready, I’d make sure to define all the read-only array methods . But for now, I was ready to move on to mutable arrays. I prefer immutability, but I want to be able to define a mutable array sometimes. So I made another test case: Notice that this requires a little extra work to make the array mutable. In other words, it’s not the default. TypeScript complained that it can’t find , so I defined it: And again, type-checks passed! Now, I had mutable and immutable arrays, with immutability as the default. Again, this is simplistic, but good enough for this proof-of-concept! This was exciting to me. It was possible to configure TypeScript to be immutable by default, for arrays at least. I didn’t have to fork the language or use any other tools. Could I make more things immutable? I wanted to see if I could go beyond arrays. My next target was the type, which is a TypeScript utility type . So I defined another pair of test cases similar to the ones I made for arrays: TypeScript complained that it couldn’t find or . It also complained about an unused , which meant that mutation was allowed. I rolled up my sleeves and fixed those errors like this: Now, we have , which is an immutable key-value pair, and the mutable version too. Just like arrays! You can imagine extending this idea to other built-in types, like and . I think it’d be pretty easy to do this the same way I did arrays and records. I’ll leave that as an exercise to the reader. My final test was to make regular objects (not records or arrays) immutable. Unfortunately for me, I could not figure this out. Here’s the test case I wrote: This stumped me. No matter what I did, I could not write a type that would disallow this mutation. I tried modifying the type every way I could think of, but came up short! There are ways to annotate to make it immutable, but that’s not in the spirit of my goal. I want it to be immutable by default! Alas, this is where I gave up. I wanted to make TypeScript immutable by default. I was able to do this with arrays, s, and other types like and . Unfortunately, I couldn’t make it work for plain object definitions like . There’s probably a way to enforce this with lint rules, either by disallowing mutation operations or by requiring annotations everywhere. I’d like to see what that looks like. If you figure out how to make TypeScript immutable by default with no other tools , I would love to know, and I’ll update my post. I hope my failed attempt will lead someone else to something successful. Again, please contact me if you figure this out, or have any other thoughts. Creating arrays with array literals is possible. Non-mutating operations, like and , are allowed. Operations that mutate the array, like , are disallowed. is allowed. There’s an unused there. doesn’t exist.

0 views
Evan Hahn 6 months ago

Fizz Buzz without conditionals or booleans

I recently learned about the Feeling of Computing podcast and listened to the latest episode . One of the hosts challenged listeners to “write Fizz Buzz with no booleans, no conditionals, no pattern matching, or other things that are like disguised booleans .” Here’s my Python solution: This solution is basically three things put together: Create endless cycling sequences of and the same idea for . Combine those two sequences with the numbers 1 through 100 using , to get the following sequence: Convert the number to a string, then “mask” it with any “Fizz”, “Buzz”, or “FizzBuzz” string. For example, returns , and returns . Because of this, my code breaks once you reach 10,000 because the digits start “leaking out” the end of the string. You’ll start seeing results like and . I’m sure there are better ways to do this, but that was my quick solution. How would you solve this problem? Create endless cycling sequences of and the same idea for . Combine those two sequences with the numbers 1 through 100 using , to get the following sequence: Convert the number to a string, then “mask” it with any “Fizz”, “Buzz”, or “FizzBuzz” string. For example, returns , and returns . Because of this, my code breaks once you reach 10,000 because the digits start “leaking out” the end of the string. You’ll start seeing results like and .

0 views
Evan Hahn 6 months ago

Notes from "Tor: From the Dark Web to the Future of Privacy"

I just finished Tor: From the Dark Web to the Future of Privacy , a profile of the Tor Project . I thought it was an approachable overview of Tor and its history. If you’re interested, the book is free! Or you can read my chapter-by-chapter notes below. The book claims that Tor “recreates the utopianism of the early internet pioneers, in which many users felt the connective power of the whole internet at their disposal. They experienced a global public sphere that was much harder for governments to control (even if with much of the poison that we see today).” It also talks about how “we write our values into the technologies we build” and how imported technologies “often bring their own cultures with them”. “So if we want to understand Tor and how it has shaped the landscape of online privacy across its history, we might try to pick apart the privacy values of its designers to understand how those values have shaped it over the years.” I learned about the “Pentagon Pizza Channel” , which supposedly predicts conflicts based on the number of pizzas being ordered near the Pentagon. If a lot of pizzas are being ordered late at night, maybe people are scrambling over some crisis. It may not be real, but it serves to demonstrate the importance of protecting metadata: Following Operation Desert Storm, the US military’s ground invasion of Iraq in 1991, a (possibly real, possibly apocryphal) story began circulating on late-night talk shows. As the story went, a journalist had noticed a massive spike in pizza deliveries to the headquarters of the US Department of Defense in the Pentagon building the night before the unannounced invasion. From this, the journalist deduced that there were hundreds of Pentagon employees working late, and hence, that the invasion must be imminent. To the [US Naval Research Laboratory] researchers, this provided a compelling example of how revealing metadata alone can be. There were several quotes about how usability was important (a lesson I learned repeatedly at Signal ): […] an onion routing system had to be open to as wide a range of users and maintainers as possible, so that the mere fact that someone was using the system wouldn’t reveal anything about their identity or their affiliations. Onion routing, whose values and core design were increasingly based around usability and mass uptake, framed these issues in terms of greater adoption—a more usable system was a more secure system, as it would have a larger and more diverse “crowd” to get lost in. The book mentions that anonymity is “one way of recasting privacy politics in technical language.” I think this is a good lesson. The author highlights many people who believe that Tor is neutral infrastructure and a non-political technology. I think this is totally wrong (for all technology, not just Tor)! If I want to talk with someone who disagrees, I can find framing that makes it sound less political. Bookmarked “The Eternity Service” , a 1996 paper which imagined a robust file storage similar to BitTorrent. (At least, that’s what I think it’s about—I haven’t read it yet.) A note about how digital surveillance feels, which I both understand and disagree with: You can’t see or feel online state surveillance, so you can’t really see or feel its absence either. Three more quotes about approachability: Usability and anonymity exist in tension in onion routing. This is one reason that Tor survived-many of its competitors, developed as they were by cypherpunks for whom security was the ultimate goal, prioritized resistance to powerful adversaries over usability. Wherever more complexity, or the opportunity to confuse users with a less or more secure mode popped up, it was generally considered and abandoned. Tor requires upkeep and maintenance, both to keep the code running and to operate relays. Not everyone does this for the same ideological reasons: It struck me as quite an odd group—I met techno-libertarian tinkerers, unabashed fascists, card-carrying liberal democrats, and anarcho-socialists. What they shared was a general interest in technology and digital privacy […] I really liked this quote from a Tor relay operator: “I think [Tor works] probably because it’s easy to work together. We don’t actually have to work together! The Tor Project has made it so simple to start a relay and just run it, and not actually interact with anyone…they’ve made it so easy to, to act like a big community when actually, we’re not really, I think we might be a bunch of individuals…We don’t have to cooperate with each other, apart from running the same software.” — Relay operator Again, Tor faced issues about whether it was a social/political technology or a neutral tool: […] the people involved in the Tor community at this stage generally chafed at the idea of privacy as a social movement . The engineer world was distrustful of policy and legal debates, preferring to change the world through technical fixes […] I do not believe that technology can ever be neutral. This idea seems to have taken hold with Tor. For example: This public relations campaign began to quickly change Tor’s reputation as a high-security hacker technology—instead of arguments at crypto conferences, a search for Tor now returned endorsements from major human rights organizations. And a nice reminder that infighting is a real threat: The state security actors against which Tor was trying to defend had a long history of skillfully disrupting undesirable activist or resistance groups through stirring up internal conflict and stoking paranoia. This book argues that the so-called “Dark Web” is a “wildly misleading term”. I loved this little rebuttal: Claiming that all data stored in servers accessible via the web are part of a shadowy bulk of unindexed sites is misleading—the equivalent of saying that books indexed in your local library sit on top of a vast hidden “deep library” of books that live in people’s living rooms, or arguing that all the ketchup that isn’t stored in supermarkets exists in a shadowy “deep kitchen.” Despite this, the concept of the Dark Web dogged Tor. The media associated Tor with the drugs and crime and bad things. This hurt the project. The Silk Road, perhaps the most famous example of this phenomenon, was shut down by cops. This shook the Tor community: […] that law enforcement was able to pull off such an operation strongly implied either wholesale government compromise of the network or a serious unknown vulnerability. In fact, the reality is more prosaic. As shown since by Sarah Jamie Lewis’ OnionScan mapping project, hidden services are easy to misconfigure, and large proportions of the ecosystem were making rookie mistakes that allowed them to be discovered. Learned about Tor2Web , a less secure (but more convenient) way of accessing Tor onion services (sometimes called “hidden services”). Hey, can someone make something really popular using Tor? Tor is still searching for what one might have once called its killer app —a use case that breaks open a major market or route to mass adoption. It has maintained a solid core of between two and three million daily users around the world for the last decade, but has yet to enter the exponential growth phase that typifies a “success story” in the world of digital infrastructure. I was surprised to learn that Tor sold an NFT: In fact, the nascent Web3 has already been an important source of funding for Tor. In 2021, Tor auctioned an NFT, a piece of digital art called Dreaming at Dusk created by artist Itzel Yard, based on the private key of the first onion service released on the network.The proceeds from this sale—$1.7 million in the Ethereum cryptocurrency—covered a third of Tor’s operating budget for the year, and helped it partly recover following its financial issues faced during the height of the pandemic. The book also mentions “crypto panics” which seem common: As authoritarian political movements continue to become established across the word, crime will be cited as a justification to ban Tor in service of state-level power, particularly to undermine its anti-censorship properties. And finally, I was inspired by this: […] the history of the internet is defined above all by the efforts that hackers, engineers, and everyday users have made to take the apparently fixed properties of its infrastructures and break them, or to build new and diverse worlds out of them. I found myself using Tor for my regular internet browsing after reading this book. Not all the time—it’s unfortunately too slow for a lot of things—but it’s plenty fast for messaging, emailing, and reading my RSS feeds. It’s clear that they’ve done a lot to make Tor “just work”, which I appreciate as fascism takes hold where I live. I thought the book was clearly written. I really liked its heavy use of quotes from Tor maintainers and relay operators. The book is free to download , so if it interests you, give it a read!

0 views
Evan Hahn 7 months ago

Scripts I wrote that I use all the time

In my decade-plus of maintaining my dotfiles , I’ve written a lot of little shell scripts. Here’s a big list of my personal favorites. and are simple wrappers around system clipboard managers, like on macOS and on Linux. I use these all the time . prints the current state of your clipboard to stdout, and then whenever the clipboard changes, it prints the new version. I use this once a week or so. copies the current directory to the clipboard. Basically . I often use this when I’m in a directory and I want use that directory in another terminal tab; I copy it in one tab and to it in another. I use this once a day or so. makes a directory and s inside. It’s basically . I use this all the time —almost every time I make a directory, I want to go in there. changes to a temporary directory. It’s basically . I use this all the time to hop into a sandbox directory. It saves me from having to manually clean up my work. A couple of common examples: moves and to the trash. Supports macOS and Linux. I use this every day. I definitely run it more than , and it saves me from accidentally deleting files. makes it quick to create shell scripts. creates , makes it executable with , adds some nice Bash prefixes, and opens it with my editor (Vim in my case). I use this every few days. Many of the scripts in this post were made with this helper! starts a static file server on in the current directory. It’s basically but handles cases where Python isn’t installed, falling back to other programs. I use this a few times a week. Probably less useful if you’re not a web developer. uses to download songs, often from YouTube or SoundCloud, in the highest available quality. For example, downloads that video as a song. I use this a few times a week…typically to grab video game soundtracks… similarly uses to download something for a podcast player. There are a lot of videos that I’d rather listen to like a podcast. I use this a few times a month. downloads the English subtitles for a video. (There’s some fanciness to look for “official” subtitles, falling back to auto-generated subtitles.) Sometimes I read the subtitles manually, sometimes I run , sometimes I just want it as a backup of a video I don’t want to save on my computer. I use this every few days. , , and are useful for controlling my system’s wifi. is the one I use most often, when I’m having network trouble. I use this about once a month. parses a URL into its parts. I use this about once a month to pull data out of a URL, often because I don’t want to click a nasty tracking link. prints line 10 from stdin. For example, prints line 10 of a file. This feels like one of those things that should be built in, like and . I use this about once a month. opens a temporary Vim buffer. It’s basically an alias for . I use this about once a day for quick text manipulation tasks, or to take a little throwaway note. converts “smart quotes” to “straight quotes” (sometimes called “dumb quotes”). I don’t care much about these in general, but they sometimes weasel their way into code I’m working on. It can also make the file size smaller, which is occasionally useful. I use this at least once a week. adds before every line. I use it in Vim a lot; I select a region and then run to quote the selection. I use this about once a week. returns . (I should probably just use .) takes JSON at stdin and pretty-prints it to stdout. I use this a few times a year. and convert strings to upper and lowercase. For example, returns . I use these about once a week. returns . I use this most often when talking to customer service and need to read out a long alphanumeric string, which has only happened a couple of times in my whole life. But it’s sometimes useful! returns . A quick way to do a lookup of a Unicode string. I don’t use this one that often…probably about once a month. cats . I use for , for a quick “not interested” response to job recruiters, to print a “Lorem ipsum” block, and a few others. I probably use one or two of these a week. Inspired by Ruby’s built-in REPL, I’ve made: prints the current date in ISO format, like . I use this all the time because I like to prefix files with the current date. starts a timer for 10 minutes, then (1) plays an audible ring sound (2) sends an OS notification (see below). I often use to start a 5 minute timer in the background (see below). I use this almost every day as a useful way to keep on track of time. prints the current time and date using and . I probably use it once a week. It prints something like this: extracts text from an image and prints it to stdout. It only works on macOS, unfortunately, but I want to fix that. (I wrote a post about this script .) (an alias, not a shell script) makes a happy sound if the previous command succeeded and a sad sound otherwise. I do things like which will tell me, audibly, whether the tests succeed. It’s also helpful for long-running commands, because you get a little alert when they’re done. I use this all the time . basically just plays . Used in and above. uses to play audio from a file. I use this all the time , running . uses to show a picture. I use this a few times a week to look at photos. is a little wrapper around some of my favorite internet radio stations. and are two of my favorites. I use this a few times a month. reads from stdin, removes all Markdown formatting, and pipes it to a text-to-speech system ( on macOS and on Linux). I like using text-to-speech when I can’t proofread out loud. I use this a few times a month. is an wrapper that compresses a video a bit. I use this about once a month. removes EXIF data from JPEGs. I don’t use this much, in part because it doesn’t remove EXIF data from other file formats like PNGs…but I keep it around because I hope to expand this one day. is one I almost never use, but you can use it to watch videos in the terminal. It’s cursed and I love it, even if I never use it. is my answer to and , which I find hard to use. For example, runs on every file in a directory. I use this infrequently but I always mess up so this is a nice alternative. is like but much easier (for me) to read—just the PID (highlighted in purple) and the command. or is a wrapper around that sends , waits a little, then sends , waits and sends , waits before finally sending . If I want a program to stop, I want to ask it nicely before getting more aggressive. I use this a few times a month. waits for a PID to exit before continuing. It also keeps the system from going to sleep. I use this about once a month to do things like: is like but it really really runs it in the background. You’ll never hear from that program again. It’s useful when you want to start a daemon or long-running process you truly don’t care about. I use and most often. I use this about once a day. prints but with newlines separating entries, which makes it much easier to read. I use this pretty rarely—mostly just when I’m debugging a issue, which is unusual—but I’m glad I have it when I do. runs until it succeeds. runs until it fails. I don’t use this much, but it’s useful for various things. will keep trying to download something. will stop once my tests start failing. is my emoji lookup helper. For example, prints the following: prints all HTTP statuses. prints . As a web developer, I use this a few times a month, instead of looking it up online. just prints the English alphabet in upper and lowercase. I use this surprisingly often (probably about once a month). It literally just prints this: changes my whole system to dark mode. changes it to light mode. It doesn’t just change the OS theme—it also changes my Vim, Tmux, and terminal themes. I use this at least once a day. puts my system to sleep, and works on macOS and Linux. I use this a few times a week. recursively deletes all files in a directory. I hate that macOS clutters directories with these files! I don’t use this often, but I’m glad I have it when I need it. is basically . Useful for seeing the source code of a file in your path (used it for writing up this post, for example!). I use this a few times a month. sends an OS notification. It’s used in several of my other scripts (see above). I also do something like this about once a month: prints a v4 UUID. I use this about once a month. These are just scripts I use a lot. I hope some of them are useful to you! If you liked this post, you might like “Why ‘alias’ is my last resort for aliases” and “A decade of dotfiles” . Oh, and contact me if you have any scripts you think I’d like. to start a Clojure REPL to start a Deno REPL (or a Node REPL when Deno is missing) to start a PHP REPL to start a Python REPL to start a SQLite shell (an alias for )

0 views
Evan Hahn 8 months ago

Notes from September 2025

Things I did and saw this September. See also: my notes from last month . I asked Ben Werdmuller for advice on “the best way for technologists to apply their skills to positive change”, and he gave a great answer . (I didn’t really do much here…all I did was ask the question.) “People read your blog in many different ways” was an attempt to capture the huge number of different types of readers you might have. I don’t know if this one is useful, but this kind of thinking is helpful for me. Following NetBSD , QEMU , and Gentoo , I updated Helmet.js’s guidelines to discourage AI contributions . I’ve long disliked in TypeScript, so I published " is almost always the worst option" . In my effort to fill in the internet’s missing pieces , I posted a bit about JavaScript ’s character encoding . Hopefully I’ve helped the next person with this question. And as usual, I wrote a few articles for Zelda Dungeon this month. I’m happy the ZD editors let me get a little deranged. Advice for software developers: “Everything I know about good system design” was great. Best tech post I read all month. “Every wart we see today is a testament to the care the maintainers put into backward compatibility. If we choose a technology today, we want one that saves us from future maintenance by keeping our wartful code running – even if we don’t yet know it is wartful. The best indicator of this is whether the technology has warts today.” From “You Want Technology With Warts” . On tech/AI ethics: “Google deletes net-zero pledge from sustainability website” seemingly because of AI. @pseudonymjones.bsky.social : “technology used to be cool, but now it’s owned by the worst, most moneysick humans on the planet. but there are transsexual furry hackers out there still fighting the good fight” @[email protected] : “maybe the hairless ape that is hardwired to see faces in the clouds is not the best judge of whether or not the machine has a soul” From “Is AI the New Frontier of Women’s Oppression?” : “…we’re on the edge of a precipice where these new forms of technology which are so untried and untested are being embedded and encoded in the very foundations of our future society. Even in the time since [I finished writing the book] we’ve seen an explosion of stories that are very clearly demonstrating the harms linked to these technologies.” “We’re entering a new age of AI powered coding, where creating a competing product only involves typing ‘Create a fork of this repo and change its name to something cool and deploy it on an EC2 instance’.” From a decision to change a project’s license . Quantum computing is trying to come to my Chicago backyard, but activists are against it. “The quantum facility is not the investment we need in this community, period.” Miscellaneous: If you like the first Halo game as much as I do, I’d highly recommend the Ruby’s Rebalanced mod , which I played this month. It feels like a Halo 1.1. It maintains the spirit of the classic, but improves it in nearly every way. The series is a bit of a guilty pleasure for me—I don’t love supporting Microsoft or ra-ra-ra military fiction—but if you already own the game, this mod is easy to recommend. Learned about, and donated to, the Chicagoland Pig Rescue from a WBEZ story last month . Shoutout to pigs. Hope you had a good September. I asked Ben Werdmuller for advice on “the best way for technologists to apply their skills to positive change”, and he gave a great answer . (I didn’t really do much here…all I did was ask the question.) “People read your blog in many different ways” was an attempt to capture the huge number of different types of readers you might have. I don’t know if this one is useful, but this kind of thinking is helpful for me. Following NetBSD , QEMU , and Gentoo , I updated Helmet.js’s guidelines to discourage AI contributions . I’ve long disliked in TypeScript, so I published " is almost always the worst option" . In my effort to fill in the internet’s missing pieces , I posted a bit about JavaScript ’s character encoding . Hopefully I’ve helped the next person with this question. And as usual, I wrote a few articles for Zelda Dungeon this month. I’m happy the ZD editors let me get a little deranged. “Everything I know about good system design” was great. Best tech post I read all month. “Every wart we see today is a testament to the care the maintainers put into backward compatibility. If we choose a technology today, we want one that saves us from future maintenance by keeping our wartful code running – even if we don’t yet know it is wartful. The best indicator of this is whether the technology has warts today.” From “You Want Technology With Warts” . “Google deletes net-zero pledge from sustainability website” seemingly because of AI. @pseudonymjones.bsky.social : “technology used to be cool, but now it’s owned by the worst, most moneysick humans on the planet. but there are transsexual furry hackers out there still fighting the good fight” @[email protected] : “maybe the hairless ape that is hardwired to see faces in the clouds is not the best judge of whether or not the machine has a soul” From “Is AI the New Frontier of Women’s Oppression?” : “…we’re on the edge of a precipice where these new forms of technology which are so untried and untested are being embedded and encoded in the very foundations of our future society. Even in the time since [I finished writing the book] we’ve seen an explosion of stories that are very clearly demonstrating the harms linked to these technologies.” “We’re entering a new age of AI powered coding, where creating a competing product only involves typing ‘Create a fork of this repo and change its name to something cool and deploy it on an EC2 instance’.” From a decision to change a project’s license . Quantum computing is trying to come to my Chicago backyard, but activists are against it. “The quantum facility is not the investment we need in this community, period.” If you like the first Halo game as much as I do, I’d highly recommend the Ruby’s Rebalanced mod , which I played this month. It feels like a Halo 1.1. It maintains the spirit of the classic, but improves it in nearly every way. The series is a bit of a guilty pleasure for me—I don’t love supporting Microsoft or ra-ra-ra military fiction—but if you already own the game, this mod is easy to recommend. Learned about, and donated to, the Chicagoland Pig Rescue from a WBEZ story last month . Shoutout to pigs.

0 views