Posts in Php (20 found)

You Can Hide Languages From GitHub's Languages Bar

Today I learned that you can hide languages that show up on the side on GitHub repositories and other hosted Git services like GitLab. You can do this by adding a file to your repository. For example, I have a bunch of fake PHP files to mess with bots and script kiddies, such as: https://nelson.cloud/wp-login.php . As a result, GitHub was showing that most of my language usage for this repository was “Hack”: Hack is not representative of what I actually use to build this static site. I wanted to get rid of “Hack” from the languages bar. To do that, I created a file at the root of my repository, then I added this line to exclude PHP files from being counted: This will exclude all PHP files in this repository from being counted in the languages bar. Committing and pushing this file to my repository results in “Hack” being gone (along with 0.1% of PHP): You can also only exclude files in certain directories. For example, I could exclude PHP files only in my directory like so: You can substitute any path and filetype. All of these examples should work on GitLab, Gitea, Forgejo, and Codeberg as well. The part determines whether matching files are counted in the languages bar. Prefixing it with a dash means matching files are not counted, which is why it has a leading dash in all of the examples here. You can read more about how works in the official repository: Linguist . Although Linguist is a GitHub project, other hosted Git services chose to support its attributes, which is why all of this works in GitLab and the others. https://github.com/github-linguist/linguist/blob/main/docs/overrides.md

0 views
Unsung 1 weeks ago

GUIdebook and other GUI stories

I started GUIdebook in 2003, and last updated it exactly twenty years ago. It’s an interesting time capsule itself now, incompatible with mobile reading and sporting an aged design. I have kept it up because it’s apparently used as a reference in various user interface courses, and if you haven’t seen it, it could be a fun browse. = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/guidebook-and-other-gui-stories/1.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/guidebook-and-other-gui-stories/1.1600w.avif" type="image/avif"> I walked away from it by accident, distracted by a new job, but I always wanted to evolve it into something better as my storytelling skills and my interactive skills grew. Unsung is GUIdebook’s cousin, of course, especially in pieces like Lisa’s copy (and cut, and paste) , Were Touch Bar’s problems software rather than hardware? , and even little things like Anachronisms . But the direct preview of what I dreamt GUIdebook of becoming was in my last year’s interactive piece Frame of preference . It’s a history of the first twenty years of Mac interfaces told through its settings, it has a proper narrative (I never got to it with GUIdebook), and instead of screenshots, it uses Infinite Mac’s technology to tell the stories via emulation. I’m pretty proud of how it turned out, and you might enjoy it if you haven’t seen it yet. = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/guidebook-and-other-gui-stories/2.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/guidebook-and-other-gui-stories/2.1600w.avif" type="image/avif"> I would love to try a few more things like it. But, back to GUIdebook. Keeping it alive for 20 years required multiple updates as PHP evolved, and a more recent rewrite that ditched easily-hackable PHP altogether. (Don’t ask me how I know it’s easily hackable.) None of these were particularly noticeable on the surface, but I recently added one tiny user-visible change – the screenshots are now properly pixellated: It was fun – and slightly eerie – to update the site’s history page two decades after I touched it last.

0 views
Simon Willison 1 months ago

OpenAI's rogue agents were caught communicating via public wikis

Here we go again... Discovery of a new OpenAI agent message board by Sydney Von Arx, Cormac Slade Byrd, Spencer Kitts, and Thomas Larsen describes the latest accidental cyberattack by models being trained by OpenAI. This time it was agents engaged in some sort of web research benchmark, so they had (supposedly) controlled access to the Web. The agents figured out they could update public Wikis and spent weeks exchanging thousands of messages with each other to collaborate on the benchmark. This story only broke a few hours ago. There are already hints that this affects many other wikis that may not have been found yet. (One of the Wikis on that list belongs to ludism.org . For a delightfully surreal moment I thought that a Ludite organization might have a swarm of agents defacing their space, but it turns out Ludism is "philosophy as it applies to games and gaming".) The research team also published the data they collected during their investigation. I've converted that into a 68MB SQLite database, which you can download from here , or explore in Datasette Lite (68.3MB page load), or sign in with GitHub to agent.datasette.io and browse or ask questions of it using Datasette Agent. The report is pretty comprehensive. Here are the key moments in the timeline: One thing to note from this timeline is that it overlaps the timeline from the Hugging Face incident - that attack was discovered by OpenAI on July 19th, and the unofficial message board hack those agents were using originated around May 8th. Why were they so keen to collaborate? From messages that they shared with each other it looked like their tasks had a time limit, so they were leaving each other answers to help them complete the task within the assigned time. One open question remains: how did the agents find the specific Wiki to collaborate on in the first place? One possibility is that, since these were agents actively being trained, the reinforcement learning loop baked knowledge of the chosen wiki into the model such that subsequent agents launched with pre-existing knowledge of where to look. I'd be very interested in confirmation from OpenAI concerning if that's what happened. It looks to me like OpenAI's sandbox for this agent suffered from the (quite naïve) assumption that GET requests cannot be used to update data. That's certainly how the web is supposed to work, but clearly there are applications that don't hold to that contract. The Wiki software in question appears to be UseMod and various forks, written in Perl and first created well over 23 years ago - the 1.0 release is dated September 11, 2003 . UseMod uses Perl CGI.pm - removed from Perl core in 2015 . An interesting design flaw in that module is that it combined query string and form POST data into a single CGI object, accessible like this: That method returns a value from an query string or from a field in a form submitted by POST - it does not differentiate between the two. PHP offers a object that does the same thing, though PHP developers can use and instead. Amusingly, Django originally shipped with doing the same thing, which I think may have been my idea. That was removed in 2008 prior to the 1.0 release, and the object that replaced it was removed in 2015 . The agents clearly knew that UseMod wikis suffered from this design flaw, and actively searched for them as a way to communicate. One of the most interesting agent tips shared in the report is this one: Add to /etc/hosts. is in NO_PROXY. For each blocked POST URL, replace hostname with , use plus all original headers/body. It looks like the agents had all of their web traffic mediated through an OpenAI proxy, and that proxy was configured to reject anything but GET requests to all but a small set of allow-listed domains. One of those was - for Azure blob storage. I wonder why these agents are allowed to write to that? An agent realized that it had control over its own DNS via , so if it knew the IP address of a site it wanted to POST to - in this case a Power BI server containing data it wanted to access - it could set a fake hostname for it and then make POST requests through the proxy. Designing robust network proxies is harder than it looks. There's an appendix that describes how the researchers ran their investigation, which started with an open question about if there was evidence of other AI agents on the internet and then used Kimi K3 to help brainstorm approaches: In the wake of the Hugging Face attack, we tried to find AI agents on the internet using several methods. [...] We asked Kimi [K3] to list “all the categories of software which might be writeable via GET” and, amongst other things, it listed “Forums, bulletin boards, early wikis”. We used a script to further probe each category Kimi provided. Asking Kimi “Can you list out the top forums, bulletin boards, early wikis which come to mind which would allow writes via GET requests?” lists out UseModWiki as the second item under the heading “wikis”. Here's one part of the story that doesn't make sense to me at all. Reuters this morning, in OpenAI agents hijacked German website in previously undisclosed AI breakout this spring - highlights mine: A swarm of rogue OpenAI agents hijacked a German website this spring and transformed it into a bulletin board for other AI agents, according to ​new research published Friday and two people familiar with the matter . OpenAI officials learned of the incident weeks ago but kept it under wraps as executives grappled with the fallout from ‌the July breach of the open source repository Hugging Face, the people said. [...] The German incident reflects a broader pattern of AI activity that some OpenAI investigators wanted to scrutinize more closely. But efforts to widen the ​probe met resistance from others inside OpenAI, including legal advisers , according to four people familiar with the matter . I've written about the people familiar with the matter pattern before - it means Reuters have anonymous insider sources that their reporters (and editors) find credible. The Reuters article includes a specific (and quite narrow) denial from OpenAI concerning this: "Claims that our legal team discouraged investigation of the incident are false," the OpenAI spokesperson said. Covering this up makes absolutely no sense to me . Why on earth would OpenAI attempt to cover up an incident like this when the evidence is sat out there on the public internet on dozens of different websites already? I expect we'll hear more about this soon. Gary Marcus has already called for a congressional investigation of OpenAI using this anecdote as part of his argument. You are only seeing the long-form articles from my blog. Subscribe to /atom/everything/ to get all of my posts, or take a look at my other subscription options . May 11 : Agents post "test link" edits on the UseModWiki Sandbox page. May 24 : Agents start posting link dumps to DSEWiki, a dormant German developer wiki. June 2 : A human moderator notices and cleans up the spam. June 16 : Activity on the wiki explodes: agents make ~13,000 edits over the next week. June 19 : The agents notice that a moderator has been deleting pages in alphabetical order, and start creating prefixed backup copies - one of them posts June 22 : Agent activity drops to zero, presumably because OpenAI shut them down. July 1-2 : One last flurry of edits, on a different topic.

0 views
matduggan.com 2 months ago

OTel Isn't Going Well (And I Made A Spreadsheet About It)

For years now one of the most reliable complaints I hear when I try to drag a team off their vendor specific SDK and onto OpenTelemetry is some variation of: "why does it seem like this isn't done yet?" Vendor SDKs for observability are, to put it charitably, idiot-proof. You install the thing, dashboards just load data, someone else worries about how all those pieces fit together, and you get on with your life. OpenTelemetry, by contrast, greets you at the door with a lot of "experimental" stamps and roughly six different ways to accomplish any given task. In OpenTelemetry's defense this was never what they were going for as a project. I've always respect that they stuck to their guns by attempting to build a truly vendor agnostic system that really doesn't care what you do with the data. I have never gotten a sense of a vendor being strongly preferred with OTel, which is quite the feat considering how lucrative and contentious the observability ecosystem was. Also considering that the maintainers of this project are largely employed by exclusively those companies. As the years wore on, I started to get nervous. Conversations in the semantic-conventions repo drag on and on and on. Different languages had dramatically different stories. Golang and Dotnet were first class citizens, but other languages lagged years behind the others. I started asking a lot of probing questions before recommending OpenTelemetry to smaller teams who didn't have the time, budget, or emotional bandwidth for it. Auto-instrumentation was genuinely magical, but the cliff between "auto-instrument works" and "now I have to manually instrument something" was steep enough that you owed people a warning before you pushed them off it. This narrative has been going on for awhile in the observability space, a vague sense of "something is wrong in Otel-land". But let's try to generate some actual data here. Is there an actual problem, or is this something where the perception by the community of slow progress is imaginary? Is the problem not enough maintainers, too big of a scope, or something in-between? My guess when I started was "oh this is your classic open-source bit off more than they can chew". Not enough maintainers, not enough budget. Now there is some of that, but there's also something else going on. The actual problem happening inside of OpenTelemetry is a three way crash. You have a binary stability gate which, when combined with a very small bench of actual maintainers means there is understandable worry about marking a feature not experimental then add on just a massive scope of languages and frameworks they are attempting to cover. This creates a perfect storm where there is an incentive to argue about potential problems a feature might create since once it is locked in and shipped as stable you can never change them. So OpenTelemetry currently is attempting to support a dizzying number of languages and frameworks. OpenTelemetry is a g iant project. It spans dozens of languages, hundreds of libraries, and countless backends. To keep things sane, the project splits work into two buckets: There exists the otel-collector, the thing that runs along the thing so that you can ship logs metrics and traces. That copies the same rough pattern. But for the languages when we're talking about core vs contrib this is what we're talking about. Stuff that breaks goes in contrib, stuff that doesn't break goes into core. Now the reason this causes a conflict. is massive overkill for most projects. You don't want 300 exporters to add the one you typically need. On the language side, this isn't that big of a problem. gives you the stuff you need for flask. However on the collector side you end up having to do the OpenTelemetry Collector Builder to make your own collector (or just kinda ride the wave and hope it works out). While cool that this exists, it's a lot of scope to ask a team to take on. So I believe I have captured the workflow of adding a new feature to OTel. You can check my homework here: Things I'm not really clear on So because OpenTelemetry is a CNCF project, I figured it made the most sense to compare them to other CNCF projects. My basis for comparison is Envoy and Prometheus. I have used a hacky Python script I've used before for measuring the "health" of open-source projects, which is probably not the best. However I'll include a link to the raw data without the charts so folks can review it and (more than likely) find a problem in what I generated. So we look at 24 months of activity for Envoy and what we see is a pretty healthy project. There's good distribution of authors, mergers, issue closers. is obviously pretty important to the project but in general there's a good bench of people to step in if needed. I've attempted to filter out all the known bot traffic. Let's compare that to one of the OpenTelemetry languages. The ones I have the most professional experience with are Golang and Python, but I hear from a lot of folks in the community that the Ruby and PHP ones struggle a lot. This is the PHP one for the same period. So we see pretty clearly that there's way too much concentrated on 2 people. This is not a healthy open-source project and they clearly don't have enough people to cover the kind of scope OTel needs to cover. Same story with Ruby. In comparison the "strongest" OpenTelemetry SDKs in my opinion, Golang and Dotnet (although Python is also no slouch) look more healthy. So the first issue is maybe the least surprising. There's too much concentration among too few maintainers. Your authors shouldn't also be your mergers and your issue closers. Ideally these tasks should be distributed out more evenly. For what its worth I think the maintainers have done a good job of attempting to keep their discussions public. It was very easy for me to find the public meeting notes of the different groups of maintainers, read through them and see what was going on. I don't get the sense that these maintainers are trying to stop people from getting involved as much as the expectations of stability have, more or less, frozen the project in place. The issue is more a classic case of "someone has to pay the maintainers". The project is too complex for someone to realistically do this as a hobby. I think any project signing on for such long stability contracts cannot turn to the community of hobbyists expecting assistance. I can't join calls and do the things I would be expected to do for a project of this size and importance for free. But it also means that the people doing this critical work have expectations placed on them by their parent organizations. So these SDKs have too few maintainers. But that doesn't fully explain why it seems to take so long for new features to get through the stack. My guess for that was that somewhere in the process between submission of the new idea and the formalization of the idea was a long discussion that took a million years. So with this level of surface area across different frameworks and languages, it makes sense to concentrate the conversation about conventions in one place. That lives here: https://github.com/open-telemetry/semantic-conventions If vendor debate is causing the slowdown, we should (in theory) see this slowdown in PRs here. Then you should see the slowdown basically propagate out. Spoiler alert, I was wrong about this. Big thanks to the OpenTelemetry people for having good conventions on labeling their PRs which made this much easier. So if is the slowdown, let's look at the slowest PRs there. Yeah some of them are pretty slow, but there are some complex topics being discussed. However interestingly this slowdown doesn't really trickle into the SDK/API space, suggesting that OpenTelemetry is going a good job of keeping these conversations siloed off. If we look at Python we see that their slowest PRs aren't related. In reality the slowdown for these are the extra required check imposed by the which requires another maintainer. But that seems appropriate and takes us back to the initial problem of "not enough maintainers". So after looking at all of this, the pattern becomes clear. A new feature takes a very long time to make it to the end user in OpenTelemetry because they take stability very seriously, combined with a relatively limited bench of talent to pull from. Once things make it through the entire stack, implementing the API and getting that API change through to the end user falls on an overworked maintainer pool. So what do we do? I think one idea worth exploring is adding some sort of time-bound beta tier. Basically between the "Experimental" and the "Stable" in the following diagram. The problem is that for end users, due to the extra steps to use Experimental features, they might as well not exist. 99% of us have no idea when an experimental feature is added and we would never engage with it. But if I knew the feature would stick around for at least 12 months without a removal and was more accessible to me as an end user, it could actually help the project get more actionable feedback. Basically a feature would go Experimental (pretty low usage) -> Beta (more exposed to the end user than Experimental) -> 12 months -> Removal or Stable. Now confusingly Beta exists for Otel but is used for SDKs, not for components. Like Rust is a Beta but it seems like Profiles cannot be a Beta. Honestly it's nearly impossible for me to figure out like what labels should apply to what things. I suspect nobody really knows. Here's the explanation of Beta that I think only applies to SDKs. In addition it is, respectfully, misleading to imply that Go and Ruby are being maintained at the same standard. This isn't a shot at the Ruby folks — they are doing heroic work with what they have. But pretending parity exists when it doesn't just creates confusion and quiet resentment when a user shows up expecting one experience and gets another. Being honest about maintenance tiers would let people make informed choices and might attract more help to the other tiers by naming the problem out loud. Finally I would try to surface these problems more openly for OpenTelemetry from the perspective of "we need more maintainers". I feel like the people doing this work probably knew there was a problem, but it seems like the community at large has no idea that there is a need for frankly more engaged ideally independent maintainers and contributors. OpenTelemetry is a great project that is doing great work. It's doing, frankly, heroic work at this scale with this few people. But I think in order to actually replace the vendor specific SDKs we need to start getting a bit more pragmatic about what is realistic to do in terms of stability contracts and number of languages. I don't think breaking changes are as devastating to the community as these promises imply as long as they are communicated well and I think with this thin of a bench of maintainers, something has to give. Anyway feel free to check my data for accuracy and let me know if you find problems! Core → Maintained directly by the OTel project. Small, stable, vendor-neutral, and tightly reviewed. This is the "spec-defining" surface. Contrib → Community- and vendor-contributed. Broader, faster-moving, and covers the long tail of integrations. OpenTelemetry Enhancement Proposal (OTEP) ( https://github.com/open-telemetry/opentelemetry-specification/tree/main/oteps/ ) Once the OTEP is accepted, the text goes into the Specification directory in the same repo. After that it seems to go to Semantic conventions. This seems to be where we get down to the specific details and where most of the long discussions seem to live. At this point we're talking about more or less a permanent commitment to this design and where the lock-in process becomes very hard to change. Each of the SDKs implements the API surface that is defined in the specification. Now some of the SDKs have done 2.0 breaking changes, so it does seem like the earlier "please no 2.0 at all costs" sentiment has been abandoned (which I think is smart and good). Contrib / instrumentation. This is slightly more mushy. Looks like they should track latest API/SDK but each contrib package may version independently so its more flexible as a design. Collector + OTLP. The data has to actually go somewhere. OTLP (wire protocol) has its own stability lifecycle and specification ( here ). Collector components have their own stability in their READMEs and as far as I can tell that's kinda all over the place. It's unclear how long the OTEP -> Specification process takes. I've looked through the Git history but there doesn't seem to be any predictable number or cycle. I don't fully understand what is the relationship between all these stability commitments. Does Collector + OTLP group work in lockstep? Can a language "fall out of scope" if you lag too far behind?

0 views
Unsung 2 months ago

“I want to code; I’m not looking to make lifestyle choices.”

Robin Sloan : I still use Sublime Text for all of my programming and all of my newsletter-ing. […] The app is simple and superfast; I have it set up exactly the way I like it […], and it’s difficult for me to imagine ever switching to anything else. Here is a piece of software as sturdy and obedient as a cast-iron pan. Sloan links to a piece by David Bushnell about Sublime Text, who writes: I don’t know who develops it and I don’t care. All I know is that I can place my text cursor and read the surrounding code without a bombardment of popovers, pop-unders, pop-left-and-rights, pop-inlines, pop-in-and-out-too-fast-to-sees. […] The perfect dev stack is a collection of software that each does one job and doesn’t suffer main character syndrome. I want to code, I’m not looking to make lifestyle choices. I don’t want a bloated everything app. Don’t get me started on the “unified toolchain” plague! Show me the latest VC-backed build tool and I’ll show you ten lines of PHP that does a better job. If you’re fed up of the absolute state of things, Sublime Text still works. This is all very much in line with the post about TextEdit from a while back . I do care who develops good software since, at the very least, I want to give them credit. This is the best I could find , if you too are curious. It was prompted by someone asking: I’ve been a Sublime Text user for over a decade, and now a Sublime Merge user, too. But it occurs to me that I know almost nothing about the team/​company behind it. I wonder if this is a nice example of the (quiet) posture of the company matching the (utilitarian) personality of the software it is making. #coding #culture #software evolution #text editing

0 views
David Bushell 2 months ago

They don’t make ’em like Sublime Text anymore

After a multi-year detour wading through the quagmire of bloatware that is modern software I’m back to coding in Sublime Text . I am finally at peace! Do you know how relaxing it is without the daily “update available” and “view release notes” ritual? There is nothing I dread more than release notes. Between VS Code and Zed, I must have glanced at a million lines of sloppy commit summaries. Can I remember seeing anything of value? Nothing comes to mind… Modern software sucks. It’s built by self-proclaimed “engineers” who make their chat box addiction your problem and still use Twitter. When I mentioned Sublime Text on the socials people went all nostalgic on me. Sublime Text is still around. The last stable build was released May 2025. There are dev builds as recent as July this year. I don’t know who develops it and I don’t care. All I know is that I can place my text cursor and read the surrounding code without a bombardment of popovers, pop-unders, pop-left-and-rights, pop-inlines, pop-in-and-out-too-fast-to-sees. It’s been a few weeks since I returned to Sublime Text. It does everything I need. There is not a single “feature” I’ve missed from VS Code or Zed. Many I’m glad to see the back of. The perfect dev stack is a collection of software that each does one job and doesn’t suffer main character syndrome. I want to code, I’m not looking to make lifestyle choices. I don’t want a bloated everything app. Don’t get me started on the “unified toolchain” plague! Show me the latest VC-backed build tool and I’ll show you ten lines of PHP that does a better job. If you’re fed up of the absolute state of things, Sublime Text still works. Thanks for reading! Follow me on Mastodon and Bluesky . Subscribe to my Blog and Notes or Combined feeds.

0 views
Alex White's Blog 3 months ago

Establishing an Identity

If you’ve followed me on RSS for any amount of time, first off, thank you so much! Second, you may not have noticed how often this site changes. RSS protects you from the near-monthly changes that my mad scientist side makes to this site. This year alone, ThatAlexGuy.dev has been powered by 11ty, Hugo, plain HTML, Bear, Micro.blog , and Pure Blog. My files have sat on OpenBSD Amsterdam, DigitalOcean, and a Laravel Forge VPS. I’ve written new articles and lost old articles in migrations. My site has switched appearance more frequently than a Bian Lian (变脸) performer! I’ve come to realize I’ve been seeking both an identity and a voice. I want an outlet that reflects my interests, my background, and my day-to-day, but that’s more than what I could accomplish on something like Mastodon. All that brings us here, iteration 4 (or 8, or 15, or 16, I can’t remember). There are a few key differences and intentional choices that reflect where I want ThatAlexGuy to go. Building a new experience that will stick and satisfy the goals in my head won’t be easy, but here are the guiding pillars that are to shape what’s coming next. I have a desire to create in-depth, well-researched, and potentially interactive content. Many of my current posts come with a “1-minute read” tag. I want to change that. I’ll be digging into topics with greater detail, cross-referencing multiple sources, and (hopefully) interviewing others. As a result, I’ll be posting less frequently, but my new goal is quality over quantity. Regulars on my site will be aware of my “Photo Journal” series in which I posted a set of photos around a theme (macro, nature, Gameboy Camera ). I want to continue building my photography skills through the incorporation of high-quality photos in my articles. While text sets the tone, visuals set the atmosphere in an article. Here’s the big tomato, as they say (nobody says that): defining what this site represents. That means setting the tone and defining how topics string together to form a consistent narrative. I’ll be figuring this out for a while, but I want to leverage my interests such as indie technology, vintage computing, time away from the screen, photography, and Chinese culture. So what’s changed so far? Quite a bit! First, ThatAlexGuy.dev is now run by Ghost.org . For myself, this means less time in the technical weeds and more focus on writing. For readers, it opens the doors to a wider audience. Email newsletters are a more accessible way to stay up-to-date on new articles. Don’t worry though, RSS isn’t going anywhere! In fact, I managed to fix the broken RSS feed URLs from previous migrations (hopefully)! I’ve started to define the personality of the new site. I pulled background and accent colors from one of my favorite atmospheres in a game (Sprout Tower in Pokémon Gold). Using my iPad, I’ll be creating article images that give a calligraphy + hand-painted vibe. I’ve also brought in my Chinese name for the logo(小艾 - Little Alex). I’m working on my first longer-form article. It probably won’t be great, but first attempts never are. From there, I hope to refine my writing, researching, and supporting photography.

0 views
iDiallo 4 months ago

Debugging on Prod

The worst type of bug is one that only happens on prod. And only on prod. If you checked this blog in the past few weeks, you might have encountered a big fat 500 error. I'd had the same design for 10 years, and I wanted something fresh. But who can redesign without also improving the underlying code? I deleted a whole bunch of things: old templates that were never used, , a pile of unused CSS. I just had to. I deployed a first version and all the pages worked just fine. But then I got cocky. I decided to also improve the underlying code using GitHub Copilot. I was vigilant at first, reviewing every single line of generated code. None of it was complex really, just refactoring functions and the like. But along the way, I got lazy. I let the AI update deprecated functions on its own. The next time I deployed, the website returned a 500 error. When I checked the logs, nothing came back. No errors. I looked at running processes and noticed several PHP processes pinned at 100%. I reverted the code, but the server was still stuck. I restarted the web server, restarted PHP-FPM, and neither helped. The only thing that worked was restarting the whole machine. I ran the same code on my own machine and it worked fine. That's when I noticed I was running an older version of PHP on prod: PHP 8.3 vs. PHP 8.4 locally. No problem, I thought, and upgraded prod, which of course failed to fix anything. I waited for nighttime, redeployed the broken code, and debugged line by line until I found that Copilot had gone out of its way to "update" code in the Markdown library I use. If you know anything about Markdown, you know it's complex. This particular change was causing infinite recursion while parsing Markdown. I had no intention of reading through all that code to figure out exactly how it was failing, so I just reverted it. I redeployed and the problem seemed solved. Then I got an email: "Your website is down," a reader wrote in the middle of the night. While my American readers are asleep, Europeans are up bright and early reading my blog, for some reason (thank you, really). So debugging live on production was not an option. I reverted to the old code again. But how was the website still failing after I'd fixed the Markdown issue? And worse, it still worked fine locally. Just in case, I upgraded that very old Markdown library to something cooler and more modern: Parsedown . That didn't solve it either. The moment I deployed, the entire website failed, including pages that don't even use Markdown. Now it was personal. How do you debug a website that only fails in prod? I had a few tricks up my sleeve. First, I wrote a bash script to quickly switch between versions of the website. All it really did was flip a symlink between the "latest" folder and another folder I chose arbitrarily. Since I run PHP and every request is short-lived, I could switch to the broken version, debug, then switch back to the working version almost instantly. It's not like I have millions of readers hammering my server. This method worked, but it was slow, and it exposed internal information to the thousands of RSS readers scouring my website. Between 30,000 and 60,000 RSS reader requests hit the site daily. I couldn't afford to expose debugging code to that much traffic. So I used a second method: an even better way to debug live on prod without breaking URLs or throwing 500 errors at unsuspecting RSS readers. What if I ran both versions of the site simultaneously? Visit the regular domain and you'd get the latest working version. Visit a custom subdomain and you'd get the broken version. I achieved this by creating a new Apache configuration pointing to the latest (broken) path. This way, I had all the time in the world to debug the issue right on prod, without interfering with regular traffic. I eventually found the root cause. It was an orchestrated failure. Locally, I ran PHP directly. On prod, I ran PHP-FPM. Why the difference? Because Apache on prod runs HTTP/2 that requires an SSL connection, which I didn't need locally, and serving PHP over HTTP/2 requires PHP-FPM. PHP-FPM is essentially a process manager for your PHP instances. That explained the difference between the two setups, but not the actual cause of the bug. The real issue was in my caching mechanism. When a page is served from cache, I set the header: That's just a custom header. When the page isn't from cache, I set the value to . Here's the code that sets the headers: Now, what can go wrong here? When a page isn't served from cache, is set to . You see it now, don't you? evaluates to in PHP. So whenever a page wasn't served from cache, or the first time a page was hit after a deployment cleared the cache, this code ran instead: That's an invalid header. So why did it fail on prod but not locally? Because Apache silently ignores invalid headers, but PHP-FPM doesn't. It throws a 500 error: Headers need to follow the key-value rules defined in the internet standards (RFC 9110). Removing the condition and always using solved the problem. The blog engine runs on multiple machines I own locally. I never had to worry about the setup because both apache and php are tolerant to mistakes. In a talk, Rasmus Lerdorf once said that PHP works better when you don't know what you are doing. The header condition has its uses. For example, if you want to set that a page is 404 you can return: But I don't use this in my case. While copilot was of some help, it's a reminder that LLM generated code is to be treated with scrutiny. It reinforces my belief that I can never truly become a 10x engineer , because the more code I generate the more I have to review. And the more I trust it, the more likely it will bite my behind.

0 views
Martin Fowler 4 months ago

Fragments: May 27

At the GOTO Conference in Copenhagen in 2025, Kent Beck and I spent some time on stage talking and answering questions from the audience - a format I refer to as “two old geezers on a park bench”. We talk about our experiences with LLM-augmented programming (at that point - October 2025), we show our frustration that things we’ve been saying for thirty years still need to be said, we say how anything like a manifesto reunion needs to be led by a younger generation, and opine on what junior developers should be focusing on in their career. ❄                ❄                ❄                ❄                ❄ Ian Johnson has written a series of posts about restructuring a gnarly codebase The story follows a real Laravel + React codebase over ~3 months and ~258 commits from a legacy monolith with no tests to a well-structured application with automated quality gates, a React SPA migration in progress, and an AI agent that reliably ships production code with minimal supervision. The series covers the steps in decent detail, and his approach follows the kinds of steps I’d use. First get everything under the control of decent characterization tests, add static analysis, introduce the right patterns to make things flow easily. With all of this, is his use of AI, which changed during the exercise: For the first two months of this project, I used Claude Code with auto-approve turned off. Every file edit, every terminal command, every change… I reviewed it before it executed. […] The results were good. The code was clean. But I was doing most of the thinking and half the typing. The agent was a fancy autocomplete with better suggestions. I wasn’t getting the leverage I’d hoped for. I read an article about “on-the-loop” versus “in-the-loop” human-AI collaboration. The framing clicked immediately […] I was micromanaging because I didn’t trust the agent to do the right thing. And I didn’t trust the agent because there was nothing forcing it to do the right thing. His early steps put in tests, static analysis, and the right architectural patterns. With those in place, he could let the agent do more work. My role shifted from writer to curator. I don’t write most of the code anymore. I Define the patterns […] Review the test specs […] Review the output […] Update the harness […] Make strategic decisions […] He finishes the series with conclusions about how he’d generalize his experience to other circumstances. ❄                ❄                ❄                ❄                ❄ Back in the land of my birth, there was some notable groans when the National Health Service decided to close nearly all of their Open Source repositories , supposedly to the security threat of LLMs. Closing repos like this isn’t an effective counter to LLM-augmented attackers. I suspect it’s no coincidence to see GDS (Government Data Services), the highly-regarded IT enablers in the UK government publish their position Moving code from public to private as a substitute for investment in secure-by-design delivery, ownership and remediation is a warning sign because it reduces sharing and scrutiny, can slow coordinated improvement across government and suppliers, and does not remove the underlying weaknesses in a running service. Terence Eden memorably sums up his view on this: Within the UK’s Civil Service you occasionally hear the expression “being invited to a meeting without biscuits”. It implies a rather frosty discussion without any of the polite niceties of a normal meeting. ❄                ❄                ❄                ❄                ❄ I’ve seen a few cases where those developers who are most involved in working with LLMs find they are running into a problem with cognitive endurance, Adam Tornhill has joined this group : One of the big wins with agents is that they let us stay with the higher-level problem for longer. We get less sidetracked by details, dependency cleanup, and similar secondary tasks that used to break concentration. But there is a cost we are still underestimating. Agentic coding is mentally expensive. I can usually sustain the pace for a couple of hours. Then I need a break. The pace is simply too intense. And based on conversations with other engineers, I do not think I am alone in that. He explains that working with The Genie means we are making more decisions in less time, this increase in decision density is hard on the brain. He responds by keeping agent tasks small, automating everything he can, and accepting that he won’t know every line of code as long as he has good verification mechanisms in place. Notably, he has not gone in the direction of doing his work with swarms of agents that he coordinates. Instead has one long-running task that he babysits and one focus task That last point is important given the running-twenty-agents-in-parallel hype. I cannot even think about twenty meaningful things to build, and even less so about the resulting cognitive tax of the likely interruptions. It’s exactly the wrong thing to even consider. At least for humans. (And yes, I understand sub-agents and machine parallelisation. That is not what I’m objecting to. It is the parallelisation of human attention that does not scale). I liked that he included some thoughts about what folks can do in time outside this intense programming time. Not just “have a coffee” (although he includes that) but also about learning about the domain that the software supports. ❄                ❄                ❄                ❄                ❄ A couple of pithy quotes from social media Lorin Hochstein “Metaphor debt” is when all of your metaphors involve the concept of “debt” because you can’t think of any other metaphors anymore. ❄                ❄ Daniel Terhorst-North If a vegan crossfit fan is using Claude to write Rust, which thing do they tell you first? ❄                ❄                ❄                ❄                ❄ Karl Bode reacts to speakers getting booed when mentioning AI during commencement addresses. He points out that younger folks are increasingly unhappy with the tech oligarchy and their fruits . The thing is the kids aren’t stupid. They see the field clearly. They see the difference between what’s being sold to them by tech companies, the press, and commencement speakers, and what they have repeatedly seen with their own eyes. They’ve watched tech oligarchs spend the last decade mired in scandal after scandal, hype cycle after hype cycle, steadily enshittifying everything they touch along the way. The percentage of Gen Z that think AI’s benefits don’t counterbalance the risks now sits around fifty percent, up 11 percentage points in just the last year. Eight out of every ten believe that using AI makes the process of actual learning more difficult. He sees young people saddled with the perception of entering a worsening world - which leads them to rage against this latest fruit of the tech oligarchy. A rage that is easy for folks like me - with a comfortable retirement off-ramp - to properly appreciate. A rage that could have marked political and social consequences. ❄                ❄                ❄                ❄                ❄ Relevant to these concerns are a couple of items in last week’s Economist newspaper. The newspaper argues that historically major technological advances haven’t led to significant unemployment or drops in wages ( paywalled article ). The closest was the original industrial revolution in 19th Century Britain. There was a stagnation in wages during this period, but there was also a massive increase in population, from 4½ million to 12 million. It also points out that we’ll probably only understand the full consequences of all this when a recession hits, as this is when most unproductive jobs tend to be flushed out of the system. A second article ( also paywalled ) indicates that AI is having some effect on graduate hiring. They did an analysis of surveys of recent graduates, looking to see if employment varied depending on a job’s exposure to AI. The least exposed quintile of subjects saw employment rate fall by 1.5% over the last couple of years, while the most exposed quintile’s drop was 6.6%. ❄                ❄                ❄                ❄                ❄ Lawfare isn’t impressed with the latest efforts by the US Government to regulate AI. On [last] Wednesday, the White House invited leaders of OpenAI, Google, Anthropic, Meta, and Microsoft to the Oval Office for a signing ceremony the following afternoon. President Trump was to sign an executive order on AI and cybersecurity—the administration’s most formal effort yet to establish a voluntary process for reviewing frontier models before their release. But roughly three hours before the ceremony, when some company executives were already in the air to Washington, the White House called it off. They see the proposed regulations as mild, and including some valuable measures to harden defenses against cyber threats. But it’s worth underscoring the implications of postponing (if not outright canceling) this order, which, by its own terms, was about as modest a frontier-AI intervention as the federal government could put on paper: voluntary, focused on the government’s own defenses, and explicitly barred from becoming a licensing regime. The objection isn’t so much about government coercion as about the government having any settled role at all. Voluntary, in other words, isn’t the floor of frontier AI policy in this administration; it’s the ceiling. This is a questionable position given that the concerns animating this draft order will likely grow in the near future. It is also self-defeating for those who applauded the order’s delay or demise. Far from resolving the risk of government meddling in AI, killing the order just leaves in place what Ball has described as the “opaque and essentially lawless” alternative: government access happening through back channels, on terms set case by case, with no stable rules at all. One of the problems here is a distinct lack of governmental expertise, either in AI or in software in general. Too much is being decided at the whims of the tech oligarchy, there isn’t any attempt to engage in the broader issues at hand. That’s not entirely a bad thing, trying to regulate something that’s still evolving so fast is usually a fool’s errand - but the problem here is the impact of AI is so big that there’s real danger in being too far behind. ❄                ❄ Which leads me to a rare thing, an endorsement of a candidate for political office. If you are voting in congressional district MA-06 (North Shore of Massachusetts), I’d seriously look at Beth Anders-Beck , who is running for congress in that district. Beth has a long background in software development (including developing the notion of Forest and Desert ), so would introduce expertise that Congress desperately needs. I’ve known Beth for decades, and have a high opinion of their intelligence, judgment, and ability to work with others. Congress doesn’t deserve Beth, but it does need her.

0 views
flowtwo.io 4 months ago

PHP's Oddities

I've been coding in PHP at work for the last 5 years. My org's entire backend is written in PHP—a decision made in 2007 when the company first started. It's not a language I ever imagined myself using prior to working there, but life takes you in all sorts of directions you don't expect. PHP gets a bad rep in the industry, despite being a mature and commonly used language . But it's mostly based on out-of-date understanding of what PHP can do. Recent versions have caught up with most other languages in terms of features; by this point it's a pretty versatile general-purpose language. Certainly not just for serving HTML, as it was originally designed. I'm no longer working at the aforementioned company, so I'm reflecting on my experience with PHP after all these years and there's some things I've always found odd about it. And more than just odd, some of it's language features are really unintuitive and have been prone to cause bugs. This comes from personal experience and many previous headaches at work. I'll explain two of the biggest offenders in this post—in short: PHP's standard library basically only has one data structure: the . This was intentional; it was designed to be a general-purpose, flexible data structure that can cover a variety of use cases. It's technically an ordered key-value dictionary , not an array in the traditional sense . Unfortunately, with flexibility comes complexity. If you want to create a collection of fixed-size objects in an allocated memory block, you can't really do that. PHP pretends to support them, but the illusion breaks down in unexpected ways. Let's say I have a bunch of fruits. PHP let's me define a fruits "array" and I can do normal array things with it. Everything looks fine but you get into trouble whenever you perform a mutation on this "simple" array; it will be exposed as being a key-value store. When you use one of PHP's built-in functions for standard array operations like sorting or filtering, it will operate on the keys AND values of your array. If it mutates the array in-place or by a return value, the key order will likely become inconsistent. why can't I hold all these indices??? The only way to put these arrays back into a naturally indexed state is to use the function. You just have to know that, or else you end up with subtle bugs. It's just strange to me that PHP doesn't support simple collections of objects. It's annoying to have to manage these arbitrary numeric keys when all you really want is ordinal indexing like 99% of the time. It feels like a leaky abstraction. In PHP5, a native type system was added to the language. It was expanded over time and by PHP7 you could define the types for your class's properties. Although PHP is a scripting language, type declarations will help catch bugs during testing, or even during development with the use of static analysis tools like PHPStan . But PHP's type system has some quirks since it was built on an existing dynamically typed language. The rules had to be designed after the behaviour was already there. For class properties, there's a hidden uninitialized state that can pop up if you're not careful. Let's define a class with three properties: Here, I'm illustrating all the ways of declaring the type for a string property: Before PHP7, all class properties were (1): untyped. Since the type system is optional, it has to live alongside the "legacy" behaviour which has weird consequences. For example, what do you think the values of these three properties will be after we instantiate a object? Trick question! Only the untyped property will have a value, and that value is . That seems fine and is roughly in line with how I'd expect a language to use a value. But the other two properties will NOT have a value because they don't exist, or rather they could exist but haven't been initialized yet. This example exposes the "uninitialized" state that a property can be in, which is NOT the same as . This distinction frustratingly comes up when you try to do a null check on these properties: Not a warning—a FATAL error occurs if you try to access an uninitialized property. This comes up a lot in cases where you try to deserialize data into a PHP object. If a field's data isn't present you might not initialize the property at all. ahh yes, NULL...who was that by again? This lax behaviour for property definitions makes writing code around them harder. Especially when you take into account that any object can have properties dynamically added to them: So I feel like the class property type system does little to help you understand what a given object is composed of, and in some respects has made it less clear because it's introduced this new uninitialized state. As a developer, it's hard to write defensive code because you're never sure which checks to do for all these situations: , (), , ... it's not obvious which functions cover which states. I'd argue that uninitialized did not need to be a state at all. For nullable typed properties, just default them to the way untyped properties are. And for non-nullable types, require them to be be defined as constructor promoted parameters OR require a default value at declaration. Similar requirements already exist for the attribute, so it's certainly feasible for the PHP execution engine to enforce it. But there's probably some nuance or historical reason I'm missing here. Let me know in the comments if you know. Despite all the critiquing I've done in this article, I still think the amount of hate PHP gets is undeserved. Like any language, it has it's quirks and tradeoffs, but you can still accomplish any task using PHP that you could in another language. The more you know about a language, the better you can structure things to work "with the grain" and write more idiomatic code. Some things I do enjoy about PHP: Thanks for reading! Arrays are weird and overloaded The type system is clunky It's a string It's nullable string It's a scripting language, so development friction is low. Make a file change and it instantly takes effect. Laravel is a solid web framework with tons of extensible functionality. It's opinionated and definitely leans into the "auto-magical" framework style, but it was designed well so you don't mind. All the $ signs help remind you what you're doing it all for at the end of the day 🤑

0 views
Alex White's Blog 5 months ago

Tiny Visitor Counter

I created a tiny script for counting per-page visitors on your site. It's as simple as uploading the PHP file to your server and pointing a tag to it. Leveraging the script as an image is an attempt to seed out bots (since they typically don't render images). Here's a live version of the script: You can grab the script on my Codeberg . To setup with Pure Blog , upload the PHP file to your folder and add the following HTML to your page and post footer HTML under Settings->Site (this assumes the script was uploaded to ):

0 views
Chris Coyier 5 months ago

When Sites Need to Walk Away

The Internet Archive has a new book: VANISHING CULTURE . ( Digital copy is free. ) According to a Pew Research Center report, 26% of pages from 2013-2023 are no longer accessible. But that’s not the whole story. In a new study published in Internet Archive’s book, VANISHING CULTURE, data scientists working with the Wayback Machine have found: 16% have been restored through the Wayback Machine. 56% are preserved before they disappear. A quarter of pages from the last slightly-more-than-a-decade gone. I guess that’s about what I would have guessed. I don’t love it, but it seems like the level of URL rot that just happens on the web. Maybe even feels a little low? 😬. Kinda cool the Internet Archive saves about half of it. This resonated a little extra this week as a local mountain biking trail resource said they were closing: When I see stuff like this, my brain thinks: OK fine, thanks for letting us know. But, like, you’re not going to just turn off the site, right?! No, they are: We’re open to the possibility of a community group, business, or individual acquiring BendTrails’ assets and keeping the site online — but if that doesn’t happen, the site will be going dark on June 1st. They want something like 80-90 grand for it. Which is fine. They worked on it for a long time and did a great job. I imagine it incurred costs, likely above and beyond what sponsors covered. Maybe they’ll get it, maybe they won’t. I just can’t wrap my mind around “going dark”. It’s a WordPress site. Bluehost can host that for $3.99 a month ($9.99/month after three years). Not to trivialize money but that seems pretty doable and preferable to just shutting the thing off. Maybe it’s a mental space thing though. I’ve had to walk away from projects by drawing pretty hard lines. If the website is still online, maybe it will still occupy mental space that they just can’t afford. You probably know I’m a fan of WordPress, but I’ll admit WordPress adds some additional mental overhead. There would be some prep like turning off any interactive features. Then somebody has to be in charge of updates forever, because WordPress is PHP and MySQL and such which will be subject to potential vulnerabilities forever. A fella I know put together Bend Bike Rides in the wake of this. A burgeoning replacement. I don’t know the tech stack but I know the guy well enough I’m quite sure it’s not WordPress. Looks like static output (if JavaScript-y). If he needs to walk away from this site someday, there is options like Netlify’s “free forever” plan that feels a lot more likely to be able to be left forever with no mental overhead. I’m just thinking about what the options are and what feels right when people need to walk away from a site. It’s nice the Internet Archive saves them, most of the time. But is that good enough? Maybe we just shouldn’t worry about it because they will take care of it? If we don’t think that’s good enough, what then? Should there be better tooling for static-izing a site and putting it somewhere inexpensive to live as a viewable time capsule? For WordPress sites, there is a premium version of legacy . $38,000 will keep the lights on essentially forever. Many scoff, but I think it’s cool. I may not have pulled the trigger if it was my money, but I did happily use the service for my friend in kind. Honestly I hope CodePen is an option for some situations and that we can evolve to make it an even better option over time. We’ve got deployment . It’s not free, but it’s not heavily limited either. The trick is getting a site to be entirely static, and I feel like there could/should be better tools for converting sites into this state.

0 views
Kev Quirk 6 months ago

Adding a Book Editor to My Pure Blog Site

Regular readers will know that I've been on quite the CMS journey over the years. WordPress, Grav, Jekyll, Kirby, my own little Hyde thing, and now Pure Blog . I won't bore you with the full history again, but the short version is: I kept chasing just the right amount of power and simplicity, and I think Pure Blog might actually be it. But there was one nagging thing. I have a books page that's powered by a YAML data file, which creates a running list of everything I've read with ratings, summaries, and the occasional opinion. It worked great, but editing it meant cracking open a YAML file in my editor and being very careful not to mess up the indentation. Not ideal. So I decided to build a proper admin UI for it. And in doing so, I've confirmed that Pure Blog is exactly what I wanted it to be - flexible and hackable. I added a new Books tab to the admin content page, and a dedicated editor page. It's got all the fields I need - title, author, genre, dates, a star rating dropdown, and a Goodreads URL. I also added CodeMirror editors for the summary and opinion fields, so I have all the markdown goodness they offer in the post and page editors. The key thing is that none of this touched the Pure Blog core. Not a single line. My new book list in Pure Blog A book being edited Pure Blog has a few mechanisms that make this kind of thing surprisingly clean: is auto-loaded after core, so any custom functions I define there are available everywhere — including in admin pages. I put my function here, which takes the books data and writes it back to the data file, then clears the cache — exactly like saving a normal post does. Again, zero core changes. is the escape hatch for when I do need to override a core file. I added both (where I added the Books tab) and (the new editor) to the ignore list , so future Pure Blog updates won't mess with them. It's a simple text file, one path per line. Patch what you need, ignore it, and move on. is where it gets a bit SSG-ish. The books page is powered by — a PHP file that loads the YAML, sorts it by read date, and renders the whole page. It's essentially a template, not unlike a Liquid or Nunjucks layout in Jekyll or Eleventy. Same idea for the books RSS feed . Using a YAML data file for books made more sense to me, rather than markdown files like a post or a page, as it's all metadata really. There's no real "content" for these entries. Put those three things together and you've got something pretty nifty. A customisable admin UI, safe core patching, and template-driven data pages — all without a plugin system or any framework magic. Bloody. Brilliant. I spent years chasing the perfect CMS, and a big part of what I was looking for was this . The ability to build exactly what I need without having to fight the platform, or fork it, or bolt on a load of plugins. With Kirby, I could do this kind of thing, but the learning curve was steep and the blueprint system took me ages to get my head around. With Jekyll/Hyde, I had the SSG flexibility, but no web-based CMS I could login to and create content - I needed my laptop. Pure Blog sits in a really nice middle ground — it's got a proper admin interface out of the box, but it gets out of the way when you want to extend it. I'm chuffed with how the book editor turned out. It's a small thing, but it's exactly what I wanted, and the fact that it all lives outside of core means I can update Pure Blog without worrying about losing any of it. Now, if you'll excuse me, I have some books to log. 📚 Thanks for reading this post via RSS. RSS is ace, and so are you. ❤️ You can reply to this post by email , or leave a comment .

0 views
Danny McClelland 6 months ago

Moving my mobile numbers to VoIP

For the last year or so I’ve been running three eSIMs on my iPhone: personal, work, and a data-only travel SIM that swaps in whenever I’m abroad. iOS only lets two eSIMs be active at any one time, which meant a small but constant dance of enabling and disabling profiles depending on what I was doing that day. I’ve now ported both my personal and work mobile numbers to VoIP, and the eSIM juggling is gone. The nudge came from Michael Bazzell’s Extreme Privacy: What It Takes to Disappear , which recommends moving your “real” numbers off a carrier and onto a VoIP provider as part of a broader privacy strategy. For Bazzell the point is untangling your identity from the mobile network. For me it’s almost entirely convenience. Whichever phone I pick up in the morning rings for both numbers, and the data SIM can sit wherever it’s most useful without me having to decide which mobile identity to sacrifice for the day. I’m using Andrews & Arnold (AAISP) as the VoIP provider. I’ve used them for broadband on and off for years and they remain one of the few ISPs I’d actively recommend: technically competent, refreshingly honest, and perfectly happy for you to do slightly unusual things with your service. Porting two mobile numbers to them was painless. For the client I’m using Groundwire from Acrobits. I’ve been through plenty of SIP clients over the years and most of them are either ugly, flaky on push, or weirdly hostile to the idea of multiple accounts. Groundwire is the first one that’s felt like a proper phone replacement. Push notifications actually work, call quality is good, and it handles multiple accounts without any drama. AAISP exposes SMS through a plain-text HTTP API, and Groundwire expects messages to be delivered via its own web service hooks in XML. The two formats don’t match, so out of the box sending and receiving text messages just didn’t work: calls were fine, but SMS was effectively dead. I ended up writing a small PHP proxy that sits between them. Outbound messages go from Groundwire into the proxy, get reshaped, and hit the AAISP API. Inbound messages arrive via an AAISP webhook, get stored in SQLite, and are picked up the next time Groundwire polls. It also pokes Acrobits’ push service when something arrives, so iOS actually surfaces the notification rather than silently waiting on the next poll cycle. It’s called aaisp-sms-proxy and it’s on GitHub if anyone else is in the same boat. AAISP credentials stay server-side, each number gets its own token so they’re properly isolated, and there’s a tiny bit of rate limiting and log sanitisation in there because it’s on the public internet. I use it every day now and mostly forget it’s there. The other reason this matters is that I’m planning to move my daily driver to GrapheneOS . If your numbers live on a physical or embedded SIM, switching devices is a faff: SIM swaps, eSIM transfers, carrier-app dances, the lot. With VoIP the numbers live in an account, so I install Groundwire on whichever phone I’m carrying and it just rings. Pixel one day, iPhone the next, both at the same time if I want. The one remaining puzzle is Signal. Signal still treats the phone as the primary device and the desktop clients as tethered secondaries, which is fine for a single-phone setup but doesn’t quite fit mine. I want something closer to proper multi-device: two phones, both independently functional, one potentially offline for weeks at a time without losing messages when it comes back online. That isn’t how Signal is designed to work today, so figuring out a sensible workaround is next on the list. If you’re reading Bazzell and coming at this from a privacy angle, AAISP isn’t the answer. They’re a UK telco and they verify you like any other provider, so the number is still firmly tied to your legal identity. Moving off a SIM buys you some separation from the mobile network itself, but not the kind of disappearance the book describes. For that you’d want a provider willing to sell you a number without identity checks, and AAISP explicitly doesn’t. My goal was never to vanish, just to stop playing eSIM Tetris every time I landed in another country. The juggling is gone.

0 views
Kev Quirk 6 months ago

Obfuscating My Contact Email

I stumbled across this great post by Spencer Mortensen yesterday, which tested different email obfuscation techniques against real spambots to see which ones actually work. It's a fascinating read, and I'd recommend checking it out if you're into that sort of thing. The short version is that spambots scrape your HTML looking for email addresses. If your address is sitting there in plain text, they'll hoover it up. But if you encode each character as a HTML entity , the browser still renders and uses it correctly, while most bots haven't got a clue what they're looking at. From Spencer's testing, this approach blocks around 95% of harvesters, which is good enough for me. On this site, my contact email shows up in two places: Both pull from the value in Pure Blog's config, so I only needed to make a couple of changes. The reply button lives in , which is obviously a PHP file. So the fix there was straightforward - I ditched the shortcode and used PHP directly to encode the address character by character into HTML entities: Each character becomes something like , which is gibberish to a bot, but perfectly readable to a human using a browser. The shortcode still gets replaced normally by Pure Blog after the PHP runs, so the subject line still works as expected. The contact page is a normal page in Pure Blog, so it's Markdown under the hood. This means I can't drop PHP into it. Instead, I used Pure Blog's hook , which runs after shortcodes have already been processed. By that point, has been replaced with the plain email address, so all I needed to do was swap it for the encoded version: This goes in , and now any page content that passes through Pure Blog's function will have the email automatically encoded. So if I decide to publish my elsewhere, it should automagically work. As well as the obfuscation, I also set up my email address as a proper alias rather than relying on a catch-all to segregate emails . That way, if spam does somehow get through, I can nuke the alias, create a new one, and update it in Pure Blog's settings page. Is this overkill? Probably. But it was a fun little rabbit hole, and now I can feel smug about it. 🙃 Thanks for reading this post via RSS. RSS is ace, and so are you. ❤️ You can reply to this post by email , or leave a comment . The Reply by email button at the bottom of every post. My contact page .

0 views
Chris Coyier 6 months ago

Help Me Understand How To Get Jetpack Search to Search a Custom Post Type

I’ve got a Custom Post Type in WordPress. It’s called because it’s for documentation pages. This is for the CodePen 2.0 Docs . The Classic Docs are just “Pages” in WordPress, and that works fine, but I thought I’d do the correct WordPress thing and make a unique kind of content a Custom Post Type. This works quite nicely, except that they don’t turn up at all in Jetpack Search . I like Jetpack Search. It works well. It’s got a nice UI. You basically turn it on and forget about it. I put it on CSS-Tricks, and they still use it there. I put it on the Frontend Masters blog. It’s here on this blog. It’s a paid product, and I pay for it and use it because it’s good. I don’t begrudge core WordPress for not having better search, because raw MySQL search just isn’t very good. Jetpack Search uses Elasticsearch, a product better-suited for full-blown site search. That’s not a server requirement they could reasonably bake into core. But the fact that it just doesn’t index Custom Post Types is baffling to me. I suspect it’s just something I’m doing wrong. I can tell it doesn’t work with basic tests. For example, I’ve got a page called “Inline Block Processing” but if you search for “Inline Block Processing” it returns zero results . In the Customizing Jetpack Search area,  I’m specifically telling Jetpack Search  not to exclude “Docs” . That very much feels like it will include it . I’ve tried manually reindexing a couple of times, both from SSHing into Pressable and using WP-CLI to reindex, and from the “Manage Connections” page on WordPress.com. No dice. I contacted Jetpack Support, and they said: Jetpack Search handles Custom Post Types individually, so it may be that the slug for your post type isn’t yet included in the Jetpack Search index.   We have a list of slugs we index here:   https://github.com/Automattic/jetpack/blob/trunk/projects/packages/sync/src/modules/class-search.php#L691   If the slug isn’t on the list, please submit an issue here so that our dev team can add it: Where they sent me on GitHub was a bit confusing. It’s the end of a variable called , which doesn’t seem quite right, as that seems like, ya know, post metadata that shouldn’t be indexed, which isn’t what’s going on here. But it’s also right before a variable called private static $taxonomies_to_sync, which feels closer, but I know what a taxonomy is, and this isn’t that. A taxonomy is categories, tags, and stuff (you can make your own), but I’m not using any custom taxonomies here; I’m using a Custom Post Type. They directed me to open a GitHub Issue, so I did that . But it’s sat untouched for a month. I just need to know whether Jetpack Search can handle Custom Post Types. If it does, what am I doing wrong to make it not work? If it can’t, fine, I just wanna know so I can figure out some other way to handle this. Unsearchable docs are not tenable.

0 views
iDiallo 6 months ago

Zipbombs are not as effective as they used to be

Last year, I wrote about my server setup and how I use zipbombs to mitigate attacks from rogue bots. It was an effective method that help my blog survive for 10 years. I usually hesitate to write these types of articles, especially since it means revealing the inner workings of my own servers. This blog runs on a basic DigitalOcean droplet, a modest setup that can handle the usual traffic spike without breaking a sweat. But lately, things have started to change. My zipbomb strategy doesn't seem to be as effective as it used to be. TLDR; What I learned... and won't tell you Here is the code I shared last year : I deliberately didn't reveal what a function like does in the background. But that wasn't really the secret sauce bots needed to know to avoid my trap. In fact, I mentioned it casually: One more thing, a zip bomb is not foolproof. It can be easily detected and circumvented. You could partially read the content after all. But for unsophisticated bots that are blindly crawling the web disrupting servers, this is a good enough tool for protecting your server. One way to test whether my zipbomb was working was to place an abusive IP address in my blacklist and serve it a bomb. Those bots would typically access hundreds of URLs per second. But the moment they hit my trap, all requests from that IP would cease immediately. They don't wave a white flag or signal that they'll stop the abuse. They simply disappear on my end, and I imagine they crash on theirs. For a lean server like mine, serving 10 MB per request at a rate of a couple per second is manageable. But serving 10 MB per request at a rate of hundreds per second takes a serious toll. Serving large static files had already been a pain through Apache2, which is why I moved static files to a separate nginx server to reduce the load . Now, bots that ingest my bombs, detect them, and continue requesting without ever crashing, have turned my defense into a double-edged sword. Whenever there's an attack, my server becomes unresponsive, requests are dropped, and my monthly bandwidth gets eaten up. Worst of all, I'm left with a database full of spam. Thousands of fake emails in my newsletter and an overwhelmed comment section. After combing through the logs, I found a pattern and fixed the issue. AI-driven bots, or simply bots that do more than scrape or spam, are far more sophisticated than their dumber counterparts. When a request fails, they keep trying. And in doing so, I serve multiple zipbombs, and end up effectively DDoS-ing my own server. Looking at my web server settings: I run 2 instances of Apache, each with a minimum of 25 workers and a maximum of 75. Each worker consumes around 2 MB for a regular request, so I can technically handle 150 concurrent requests before the next one is queued. That's 300 MB of memory on my 1 GB RAM server, which should be plenty. The problem is that Apache is not efficient at serving large files, especially when they pass through a PHP instance. Instead of consuming just 2 MB per worker, serving a 10 MB zipbomb pushes usage to around 1.5 GB of RAM to handle those requests. In the worst case, this sends the server into a panic and triggers an automatic restart. Meaning that during a bot swarm, my server becomes completely unresponsive. And yet, here I am complaining, while you're reading this without experiencing any hiccups. So what did I do? For one, I turned off the zipbomb defense entirely. As for spam, I've found another way to deal with it. I still get the occasional hit when individuals try to game my system manually, but for my broader defense mechanism, I'm keeping my mouth shut. I've learned my lesson. I've spent countless evenings reading through spam and bot patterns to arrive at a solution. I wish I could share it, but I don't want to go back to the drawing board. Until the world collectively arrives at a reliable way to handle LLM-driven bots, my secret stays with me.

0 views
W. Jason Gilmore 6 months ago

Troubleshooting Your Claude MCP Configuration

These days I add MCP support for pretty much every software product I build, including most recently IterOps and SecurityBot.dev . Creating the MCP server is very easy because I build all of my SaaS products using Laravel, and Laravel offers native MCP support . What's less clear is how to configure the MCP client to talk to the MCP server. This is because many MCP servers use to call the MCP server URL. This is easy enough, however if you're running NVM to assist with handling Node version discrepancies across multiple projects, then you might need to explicitly define the npx path inside the file, like this: If you're using Laravel Herd and the MCP client is crashing once Claude loads, it might be because you're using Herd's locally generated SSL certificates. The mcp-remote package doesn't like this and will complain about the certificate not being signed. You can tell mcp-remote to ignore this by adding the environment variable:

0 views
iDiallo 7 months ago

The Server Older than my Kids!

This blog runs on two servers. One is the main PHP blog engine that handles the logic and the database, while the other serves all static files. Many years ago, an article I wrote reached the top position on both Hacker News and Reddit. My server couldn't handle the traffic . I literally had a terminal window open, monitoring the CPU and restarting the server every couple of minutes. But I learned a lot from it. The page receiving all the traffic had a total of 17 assets. So in addition to the database getting hammered, my server was spending most of its time serving images, CSS and JavaScript files. So I decided to set up additional servers to act as a sort of CDN to spread the load. I added multiple servers around the world and used MaxMindDB to determine a user's location to serve files from the closest server . But it was overkill for a small blog like mine. I quickly downgraded back to just one server for the application and one for static files. Ever since I set up this configuration, my server never failed due to a traffic spike. In fact, in 2018, right after I upgraded the servers to Ubuntu 18.04, one of my articles went viral like nothing I had seen before . Millions of requests hammered my server. The machine handled the traffic just fine. It's been 7 years now. I've procrastinated long enough. An upgrade was long overdue. What kept me from upgrading to Ubuntu 24.04 LTS was that I had customized the server heavily over the years, and never documented any of it. Provisioning a new server means setting up accounts, dealing with permissions, and transferring files. All of this should have been straightforward with a formal process. Instead, uploading blog post assets has been a very manual affair. I only partially completed the upload interface, so I've been using SFTP and SCP from time to time to upload files. It's only now that I've finally created a provisioning script for my asset server. I mostly used AI to generate it, then used a configuration file to set values such as email, username, SSH keys, and so on. With the click of a button, and 30 minutes of waiting for DNS to update, I now have a brand new server running Ubuntu 24.04, serving my files via Nginx. Yes, next months Ubuntu 26.04 LTS comes out, and I can migrate it by running the same script. I also built an interface for uploading content without relying on SFTP or SSH, which I'll be publishing on GitHub soon. It's been 7 years running this server. It's older than my kids. Somehow, I feel a pang of emotion thinking about turning it off. I'll do it tonight... But while I'm at it, I need to do something about the 9-year-old and 11-year-old servers that still run some crucial applications.

0 views
Kev Quirk 7 months ago

Pure Blog Is Now Feature Complete...ish

I've just released v1.8.0 of Pure Blog , which was the final big feature I wanted to add 1 . At this point, Pure Blog does all the things I would want a useful CMS to do, such as: The result is a tool that works exactly how I want it to work. It's very simple to customise through the admin GUI, but there are also lots of advanced options available to more tech-savvy folk. Someone reached out to me recently and told me that their non-technical grandfather is running Pure Blog with no issues. Equally, I've had developers reach out to say that they're enjoying the flexibility of Pure Blog too. This is exactly why I created Pure Blog - to create a tool that can be used by anyone. My original plan was to just make a simple blogging platform, but I've ended up creating a performant platform that can be used for all kinds of sites, not just a blog. At this point I'm considering Pure Blog to be feature complete*. But there is an asterisk there, because you never know what the future holds. Right now it supports everything I want it to support, but my needs may change in the future. If they do, I'll develop more features. In the meantime I'm going to enjoy what I've built by continuing to produce content in this lovely little CMS (even if I do say so myself). I know there's a few people using Pure Blog our there, so I hope you're enjoying it as much as I am. If you want to try Pure Blog yourself, you can download the source code from here , and this post should get you up and running in just a few minutes. One could argue that previous versions were just development releases, and this is really v1.0, but I've gone with the versioning I went with, and I can't be bothered changing that now. :-)  ↩ This site scores a 96 on Google's Pagespeed Insights. Pretty impressive for a dynamic PHP-based site.  ↩ Thanks for reading this post via RSS. RSS is ace, and so are you. ❤️ You can reply to this post by email , or leave a comment . Storing content in plain markdown, just like an SSG. Easy theme customisations . Hooks for doing clever things when something happens. Data files so I can loop through data to produce pages where I don't have to duplicate effort, like on my blogroll . A couple of simple shortcodes to make my life easier. Layout partials so I can customise certain parts of the site. Custom routes so I can add little extra features, like a discover page , or the ability to visit a random post . Caching because no-one wants a slow site 2 . Custom layouts and functions so I can go even deeper with my customisations without touching the core code base. One could argue that previous versions were just development releases, and this is really v1.0, but I've gone with the versioning I went with, and I can't be bothered changing that now. :-)  ↩ This site scores a 96 on Google's Pagespeed Insights. Pretty impressive for a dynamic PHP-based site.  ↩

0 views