Where's Eric? Tracking NY politicians' public schedules

Note: I had mostly finished this project last weekend, before Eric Adams dropped out of the mayoral race. While Adams will still be the mayor until January, this project made more sense while he was an active candidate.

WHERE'S ERIC? provides a compilation and visualization of when Eric Adams and other New York officials have failed to make their public schedule available in advance, as reported by Politico.

As the New York City mayor's race escalates, I've been paying closer attention to the local politics-focused media outlets, including reading Politico's "New York Playbook" regularly. Aside from the actual news, they have a brief section where they ask: "Where's Kathy?" and "Where's Eric?", and summarize what their public schedules for the day are.

That is, if they receive them. Lately Adams' entry has been some variant of "Schedule unavailable as of 10 p.m. [previous night]".

I was curious what this actually meant in the long-term; was I coincidentally just reading Playbook on days he didn't provide his schedule? Or has he always been bad about providing his public schedule? Are other politicans any better?

Of course, the best way to answer this question was to look at literally the entire history of New York Playbook, so I processed the entire archive dating back to 2016 to get a more complete picture. The first Playbook issue that contained then-Governor Andrew Cuomo and then-Mayor Bill de Blasio's schedules was February 21, 2017: de Blasio had events in Manhattan and The Bronx while Cuomo had no public schedule.

Moving forward to 2025, I was mildly surprised to learn that Adams was actually perfect in providing his public schedule for the first three years of his term. Then on Friday, March 21, his first ever "Schedule unavailable as of 10 p.m. Thursday." appeared.

In April, he didn't provide his public schedule more often than he did. Over the past 8 weeks, his schedule has been unavailable 65% of the time.

This is...not great.

Knowing what our public officials are up to is a standard form of transparency that enables the press to document their actions so the public can hold them accountable. Not being up front with what you're doing undermines public trust, and while this might feel like a small thing, I think it's a decent indicator for how public officials respect the public and the press in general.

Given how chaotic the last few months of the Adams administration have been, part of me is curious whether this is due to incompetence or malice. We know quite well that Adams acts maliciously when it comes to the city hall press corps.

Where's Cuomo?#

And yet as bad as Adams is at this, he is still better than Andrew Cuomo, who, out of the four officials reported by Politico, is the worst.

WHERE'S ANDREW? shows how his record was consistently spotty since early 2017, but dramatically worsened in May 2020. Admittedly that was a pretty chaotic time for everyone, but this the same person who wanted us to celebrate his leadership during that time period.

Where's Bill and where's Kathy?#

During that same time periods Adams and Cuomo were failing at providing their public schedules, WHERE'S BILL? and WHERE'S KATHY? show in stark contrast that it was completely feasible to regularly provide their schedules.

Both provided their schedule to Politico 99% of the time, which I think shows that this is not a difficult task, and makes Adams' and Cuomo's failure to do so even more inadequate and unacceptable.

Methodology#

After scraping Politico's archive, the "Where's {name}?" fields were extracted into a database (raw data), with special handling for some edge cases. For example, on January 6, 2022, Politico had an joint item, "Where are Kathy and Eric?".

Also for about two weeks, Politico spelled it as "BlLL" (that's a lowercase L instead of an I). Oops.

A regular expression was used to identify days when the schedule was unavailable, specifically matching the phrases:

  • schedule unavailable
  • not available
  • schedule not available
  • schedule not released
  • unavailable as of
  • not released
  • by press time
  • schedule yet
  • no public schedule released as of
  • no public schedule available as of
  • as of {number}

Notably this does not match when a schedule was provided but there were no public events.

I performed a spot check against most of the unavailable dates and far fewer of the available ones, erring on the side of identifying false positives. If you do find an error, please contact me.

Major credit and thanks to the Politico reporters for collecting and reporting this data for nearly a decade.


Adventures of a YAML engineer

I want to brag about a bit of YAML code I wrote back in March for SecureDrop's completed migration to Ubuntu Noble that I neglected to mention in the blog post explaining the technical details. Yes, YAML, is a programming language.

We offered SecureDrop Administrators the option for a "semiautomated" upgrade: they run one command, ./securedrop-admin noble_migration, and it'll take care of the rest. The main advantage for doing so was that the upgrade would happen at the time you chose, and if something happened to go wrong, you were already on hand to deal with it!

Under the hood the semiautomated upgrade was starting an Ansible playbook that edited our JSON control file to mark the server as ready to be upgraded and then started the systemd service. And then it just waits until the upgrade completes, which ended up being the harder part to implement.

During the upgrade, the server reboots twice (once before installing updates and once after), which means Ansible will lose its SSH connection. I used Ansible's wait_for_connection module to reconnect instead of error out, and naively had it wait for that to happen twice before checking if the upgrade had finished.

But during testing we found a problem when using SSH-over-Tor, in which Ansible would disconnect three times. It disconnected on the first pre-upgrade reboot, then during the upgrade when the Tor package was restarted, and then again during the second post-upgrade reboot.

And, to make it even more fun, this was subject to a race condition. In at least one instance, it took long enough for Tor to come back that the server rebooted before it reconnected, so there were only two disconnections.

Knowing that, a naive solution wasn't going to cut it anymore, so I implemented the same state machine as the Rust code, just in the YAML playbook. It now parsed the JSON state file, looked up where in the overall process it was, and then calculated how many reboots are likely remaining. Once it disconnected and reconnected, it looked at the state file again, so it knew how many more to expect.

Here's the end result, it ended up being just over 200 lines of YAML (including comments).

Alternative clickbait titles for this post include: "Porting some of my Rust code to YAML" and "Writing a state machine in YAML".


A small change in plans

A small change in plans: I'm starting law school in the fall. I'll be attending the CUNY School of Law right here in Queens to become a public interest-focused lawyer.

I plan to continue working full time at the Freedom of the Press Foundation and go to school in the evenings, part time. And yes, law school is something I have always wanted to attend.

Going forwards you'll probably see me switch up the standard disclaimer to something like IANALY (I Am Not A Lawyer Yet).


Thoughts from vibecoding

I've been vibecoding for a while now and wanted to share some thoughts that I haven't seen discussed elsewhere regarding the process. To quote the original vibecoding definition by Andrej Karpathy:

It's not too bad for throwaway weekend projects, but still quite amusing. I'm building a project or webapp, but it's not really coding - I just see stuff, say stuff, run stuff, and copy paste stuff, and it mostly works.

And that's pretty much what I do as well. Some of vibecoded projects are on my website (said listing was also vibecoded — but all the text was written by hand), there are plenty more sitting in my ~/Downloads folder that I haven't bothered to clean up and publish.

Also note that I haven't gotten started with the whole "agentic" thing yet, so I'm just still typing prompts into a web browser and copying code out of it.

My standards have gone up#

Being a software engineer generally means that when something goes wrong with computers, you have a rough idea of what might have caused the error. Usually my response is along the lines of, well, all software is buggy, that's life.

These days I'm interested in seeing if I can do something better, both to make my life better, but also to just prove that we can make better software. As an example, a few months ago, I was filming a video for work and was relying on a random teleprompter website for our script.

I was struggling a bit with getting the emphasis right; the website didn't support any type of bold or italic formatting. Knowing what I know about HTML and JavaScript, I guessed that the website was treating everything as plain text, and that in theory, could support rich text if it used contenteditable.

I spent 10 minutes prompting Claude and then an hour fixing small bugs in the CSS/JS, and boom, an "Improved Teleprompter".

When the MTA released a new mobile app, it was okay, but not exactly the way I wanted my routes laid out. I built my own subway stops tracker that presents upcoming trains just how I want to see them, plus it stores all my data locally.

This doesn't necessarily mean the code is better, but rather the user experience is.

JavaScript is privacy-friendly#

Main article: Thinking of JavaScript as privacy-friendly tech

For the longest time, my stance was that JavaScript was a privacy-violating technology that needed to be avoided. I don't think that stance was originally irrational, given that most online tracking uses it, and tools like NoScript and RequestPolicy/uMatrix were effective at stopping that.

But today I think JavaScript has just as much to offer as privacy-friendly technology through things like client-side cryptography but also just keeping computing local instead of happening on someone else's computer.

You can get pretty far with JavaScript, localStorage, and optionally, some remote APIs. (Though CORS can be super annoying...)

Modifying is harder, but#

Modifying vibecoded projects is definitely harder because I didn't write the original code, and often it's not in the style I would have used. But I've found it's usually easy enough to just start over from scratch, replaying your original prompts, but with whatever modifications I needed.

I expect this is an area that I might have a different experience once I get around to agentic LLMs.

Content-Security-Policy is great#

This is not really news, I think it's well understood that the introduction of Content-Security-Policy has significantly improved web security. And that extends to vibecoded projects, especially if you haven't 100% reviewed everything.

The first version of my JSON diff tool that Claude created had a pretty glaring XSS that I noticed and fixed right away. But even if I hadn't, I deployed it with a policy of script-src 'self', so any inline <script> execution would be blocked.

In general it's nice to be able to clearly see and understand exactly what external websites (if any) these small projects will connect to.

I also really like how easy it is to share single-file HTML/CSS/JS projects (pages?), but having inline styles and scripts requires a weaker CSP. So I wrote resource-rewriter to use a single-file for deployment and then automatically move them to separate files during deployment.


In support of Zohran Mamdani

Unfortunately I don't have 8.3 million dollars to spend in support of a political candidate, but I do have my blog.

In the ongoing New York City mayor's race (specifically the Democratic primary), I'm supporting, canvassing and voting for my assembly member, Zohran Mamdani. His entire platform is centered around making NYC more affordable, specifically:

  1. freezing the rent for rent-stabilized tenants (previously done by de Blasio)
  2. making buses fast and free (he won a 1-year pilot on this, it was reasonably successful)
  3. free childcare (I didn't have a parenthetical for this)

This is not to mention his various plans to build more housing, both creating new public housing and speeding up construction of private housing.

And he has a plan to create a "Department of Community Safety", which will task dedicated professionals and mental health experts on helping people with homelessness and other crisis response. And that will let police do actual police things.

I know for sure that he can deliver on the first part of his platform, freezing the rent, since the mayor appoints all the members of the rent control board. The rest requires collaboration from the city council and most likely Albany.

It's not a guarantee that it's possible, but if he wins, there will be a public mandate for it, and suddenly, it'll be realistic.

Ultimately I want a mayor who is willing to try new ideas instead of constantly being stuck doing what is "safe" and continuing old policies that have gotten us here. Zohran is that person and my #1 vote.

Brad Lander#

The more I learn about Brad Lander, the more I like him. Out of all the candidates (including Zohran), I think he is best suited to hitting the ground running as mayor on day one. He seems to have the best grasp on the NYC bureaucracy and has incredibly detailed and technical plans on how to address, well, everything.

I ranked him #2 (in line with Zohran's cross endorsement), but respect and support anyone who ranks him #1 and Zohran #2. If Zohran ends up winning, I hope he gives Brad Lander a significant role in his administration.

Don't Rank Evil Andrew for Mayor#

I never actually lived in New York during Andrew Cuomo's tenure, but I've read enough from the time and everything that's come out since. The fact that he was governor for 10 years, and HUD secretary for another 4 means that he had the opportunity to fix it in the past, but didn't. It's time for new leadership.

I think this is a perfect example showing that letting people voluntarily resign under pressure is a bad idea; if he had been impeached and removed from office, there wouldn't have been a comeback.

Final thoughts#

Zohran has been a great representative for me, and I am looking forward to sharing him with the rest of the city.

I told someone once that I'm supporting Zohran because as my assembly member, he's the first elected official to represent me that I'm not embarrassed by. I don't mean that we agree on everything (we mostly do, but not 100%) — rather I think he has a good set of core guiding principles, and sticks by them in ways that are understandable and justifiable.

After having an incredibly embarrassing mayor for the past 4 years, I'm looking forward to one I respect and appreciate. I hope you'll rank Zohran #1 (and Lander #2).

Voting is open today, June 22 (9am-5pm), and then again for the last time on June 24 (6am-9pm).


Creating IPv4-only and IPv6-only containers with podman

By default, newer versions of podman run containers with a dual stack network that supports IPv4 and IPv6 (yay). But if you're doing something specific, you can set up IPv4-only and IPv6-only networks.

(Note: I tested this all with rootless podman 5.5.0, the current version in Fedora 42.)

I'm primarily writing this because it took me a while to figure this out, I got entirely tripped up by the --ipv6 option which turned out to not be what I wanted, despite the name implying it enables IPv6.

The documentation for it is technically accurate, as it says:

Enable IPv6 (Dual Stack) networking. If no subnets are given, it allocates an ipv4 and an ipv6 subnet.

The most important part is in parenthesis — it enables a dual-stack network. Which means that passing --ipv6 when creating a network doesn't just enable IPv6, it also enables IPv4!

Real IPv6-only#

What you actually want is:

$ podman network create --subnet fd00::/64 --gateway fd00::1 ipv6-only

You can verify that IPv4 doesn't work by:

$ podman pull quay.io/curl/curl:latest
$ podman run --rm -it --net=ipv6-only curl -v4 https://en.wikipedia.org
* Host en.wikipedia.org:443 was resolved.
* IPv6: (none)
* IPv4: 208.80.154.224
*   Trying 208.80.154.224:443...
* Immediate connect fail for 208.80.154.224: Network unreachable
* Failed to connect to en.wikipedia.org port 443 after 13 ms: Could not connect to server
* closing connection #0
curl: (7) Failed to connect to en.wikipedia.org port 443 after 13 ms: Could not connect to server

And that IPv6 works:

$ podman run --rm -it --net=ipv6-only curl -I6 https://en.wikipedia.org
HTTP/2 301 
date: Fri, 23 May 2025 00:29:41 GMT
...

IPv4-only#

And now for IPv4, which is even simpler:

$ podman network create ipv4-only

Yep, no options needed, you just need a network in which IPv6 is not enabled by the subnet and doesn't pass the --ipv6 flag.

Final notes#

The default networking stack for rootless containers is documented (under "pasta") as "IPv4 and IPv6 addresses and routes, as well as the pod interface name, are copied from the host". In my testing this is correct, but this is an entirely separate thing from podman network that appears to exist by default, which is IPv4-only.

I ended up figuring out the whole misleading --ipv6 thing thanks to a GitHub comment, which explicitly spelled out "The --ipv6 flags means dual-stack", and even explained the rationale why: "this is fully compatible with docker ..."

I shouldn't be too surprised that Claude also got tripped up by the --ipv6 flag and gave me bad advice. ¯\_(ツ)_/¯

Final final note: if you try a plain podman run curl ... without first pulling the image, it won't know which image you actually want, and none of the three prompts it gives you (registry.fedoraproject.org, registry.access.redhat.com, docker.io/library) are the official upstream image. I've submitted a PR to the containers/shortnames repo to fix that, so a plain curl image name will automatically be aliased to the upstream image.