RSSAmplifier

Blog

brtkwr.com

Recent content on brtkwr.com

brtkwr.comRSS feed ↗144 posts

Latest posts

Git-managed agent routines that propose their own fixes

TL;DR This is about Claude’s scheduled cloud routines specifically, which is the only agent platform I have tried it on. I moved my routine prompts out of the scheduler and into a git repo, leaving a fifteen-line stub behind that reads its instructions from main at runtime. Changing a routine is now a commit rather than a form edit, and each routine has standing authority to open a PR…

Don't put a long token in an email URL

TL;DR Link to heading Put a long signed token in an email link and you have built a bug that fires deterministically for some recipients and never for others. MIME quoted-printable wraps lines at column 76, some mail providers re-encode the message and mangle the escape that lands on the wrap seam, and if the first seam falls inside the token a single flipped character makes the signature stop…

Trimming my Claude Code startup context

I ran /context in Claude Code and found my memory files eating 49.5k tokens before I’d typed a single word. I’d assumed those files loaded only when relevant. They were loading in full, every session. After moving them to the managed memory directory and disabling one plugin I never use, startup context dropped from 82.1k to 37.2k. Before Link to heading The two big line items were…

Notes on a stale provider cooldown in OpenClaw

TL;DR Link to heading My OpenClaw gateway went silent for three days after a usage spike, even though I could still chat to the same provider via its web interface normally. The API was serving requests, but OpenClaw had stored a “next reset in 6 days” message as a literal blockedUntil timestamp and refused to try the profile again. Without a fallback model configured, the…

What I Trust When Verifying a Multi-Repo Migration

TL;DR : During a migration across a few dozen repos, “looks done” failed me three different ways: stale code search, stale local clones, and an auto-merge that quietly reverted my changes. The checks I ended up trusting were greps on fetched file contents. Motivation Link to heading I was rolling a config migration across a few dozen repos at work and needed to answer one question…

Swapping uv Indexes Without Version Drift

TL;DR : I needed to repoint a batch of uv projects at a new package registry without changing a single resolved version. uv lock --no-upgrade re-resolves anyway when the index changes, so I rewrote the URLs in pyproject.toml and uv.lock with sed and verified with uv lock --locked . Motivation Link to heading I was migrating Python projects at work to a new package registry. The packages had been…

The Postgres ownership migration that looked done but wasn't

TL;DR Link to heading I moved object ownership in Postgres from a shared role to a dedicated role per service, one environment at a time, with the application serving throughout. REASSIGN OWNED does it in one statement and moves every object class. A FOR ALL TABLES publication blocked it on one database, so I went per-object and left functions, types, and schemas behind. A pg_shdepend sweep caught…

When cert-manager can't help: GKE Gateway and Google Certificate Manager

TL;DR Link to heading I had a Kubernetes service exposed via two different gateway flavours on different clusters. Two of them used an istio Gateway with TLS terminated in-pod, where cert-manager handed it a wildcard cert via a regular Secret . The third used a GKE-managed Gateway ( gatewayClassName: gke-l7-global-external-managed ), where TLS terminates at a Google Cloud Load Balancer that does…

Reducing OpenClaw token usage

TL;DR Link to heading I had reduced my OpenClaw heartbeat cadence a few weeks ago expecting a big drop in LLM credit usage. It barely moved the bill. Digging into the per-call usage logs, I found that every persistent session had compactionCount: 0 , so each cron run and group chat reply was replaying its own growing transcript on top of a 12k-character bootstrap. The fixes that helped were…

Avoiding Cloud NAT cost for Artifact Registry image pulls

TL;DR Link to heading A private GKE cluster’s outbound traffic to *.googleapis.com and *.pkg.dev flows through Cloud NAT by default and pays $0.0385/GB data processing on every byte, in both directions. The GCP UI says “Private Google Access is in effect” for the subnet, which makes it sound like that traffic already bypasses NAT. It does not. To bypass NAT for Google API…

Closing HA gaps in a redis-sentinel + haproxy setup

TL;DR: A common haproxy-in-front-of-redis-sentinel setup has three reliability traps: liveness probes cascading during sentinel failover, haproxy hard-stopping inflight connections on SIGTERM , and haproxy aborting at startup when any redis pod’s DNS is NXDOMAIN. The fixes are split probes, a preStop hook that runs kill -USR1 1 , and init-addr last,libc,none resolvers k8s on every server…

How a tidy refactor caused a release loop

TL;DR Link to heading A cleanup PR removed an accidental guard in our release workflow’s git push logic. Three weeks later the workflow had pushed 119 self-generated bump commits. Fix: gate the release job on github.actor so the workflow’s own bot pushes can’t re-trigger it. I opened a service repo and found a release pull request with 119 consecutive commits, all chore: Bump…

Current state of my Claude Code memory files

TL;DR: I’ve been using Claude Code since last July and spent most of that time correcting the same behaviours every session. Auto-memory hasn’t fixed that, but it has taken some of the edge off: when I say “next time, do X” in conversation, Claude saves it as a feedback memory and pre-empts me more often than not on the next run. Below is a snapshot of the rules I’ve…

Is there an optimal cycling speed in the rain?

TL;DR: I thought there was. There isn’t, unless there’s a tailwind. In vertical rain, faster is always drier. Motivation Link to heading I was cycling home yesterday in heavy rain and started wondering about the right speed. Slower means more time getting drizzled on. Faster means more rain per second on my front. It felt like there had to be a sweet spot. The setup Link to heading…

OpenClaw: when a heartbeat session keeps your old model alive

TL;DR Link to heading My OpenClaw gateway kept calling an LLM provider I had retired. The new primary in openclaw.json was ignored because the long-running “heartbeat” session pinned the model selection at session creation time, both in the session index and in a model_change event at the top of the session transcript. Resetting the session let the global config take effect. Motivation…

Tracking the ~/.openclaw config directory in git, with secrets kept out

TL;DR: Putting ~/.openclaw in a private git repo is mostly about writing the right .gitignore and keeping every secret outside the tree. A systemd EnvironmentFile carries the real keys to the gateway at startup, so openclaw.json can hold references or empty fields instead of credentials. Why track it Link to heading After the upgrade gotchas , I wanted version history for openclaw.json and a way…

What makes Go channels special

TL;DR Link to heading A Go channel is one primitive covering what other languages split across locks, condition variables, semaphores, and queues. That consolidation is the thread I kept pulling on while reading. Motivation Link to heading Channels come up in every Go pitch. I wanted to understand why. What a channel actually is Link to heading In most languages you get shared memory plus…

OpenClaw upgrade gotchas: schema validation and symlink rejection

TL;DR: Upgrading from 2026.4.8 to 2026.4.15 tripped two restrictions that silence your config without a loud error: a tightened schema for tools.elevated.allowFrom.telegram that invalidates the whole file when it fails, and an fs-safe layer that refuses to traverse symlinks in the state-directory path. One is a config edit, the other is a bind mount. Context Link to heading I moved OpenClaw from a…

Making Claude Code speak responses with a Stop hook

TL;DR: I wanted Claude Code to read out each response through say on macOS so I could step away and listen instead of watching the terminal. Four bugs got in the way: the hook was on the wrong event, tac does not exist on macOS, the Stop hook fires before Claude flushes the transcript, and pkill say clobbered speech from other sessions. A 1-second sleep , jq -rs , and a per-session PID file fixed…

Helm pre-upgrade hooks are not atomic

TL;DR: A pre-upgrade hook applies resources that stay applied when the upgrade fails and you roll back. Helm has a pre-rollback phase to undo those changes, but it runs only if you define one. Pair every pre-upgrade hook with a matching pre-rollback , or move the hook into its own release. A quick word on SecretProviderClass Link to heading A SecretProviderClass (SPC) is the custom resource the…

Making hot sauce is more engineering than cooking

TL;DR — I did a hot sauce making course at Easton Chilli in Bristol. Came home with eight bottles and a new appreciation for the art and science behind it. Why I signed up Link to heading I like hot sauce. I go through bottles faster than I’d care to admit, but making my own had never crossed my mind. My partner got me an Easton Chilli course for Christmas. It got rescheduled once, then I…

Switching OpenClaw to CLI backends for Claude and Codex

TL;DR: OpenClaw can call Claude and Codex through their CLI tools instead of hitting APIs directly. CLI backends give you better session handling and remove API key management. Switching over requires cleaning up some wizard-generated config. Extra usage billing still applies for Claude. Why CLI backends Link to heading OpenClaw supports two ways of talking to model providers: the embedded API…

Migrating OpenClaw from Jetson Nano to a VPS

TL;DR: I moved my OpenClaw setup from a Jetson Nano to a VPS. Persistent block volume for state, Node 24 LTS instead of a hand-compiled Node 22, and the whole migration took about 20 minutes once I stopped fighting ARM constraints. Update (April 2026): The symlink approach below breaks with newer OpenClaw versions, and a few other restrictions landed in the 2026.4 line. Notes in OpenClaw upgrade…

Fixing the post-quantum SSH warning

OpenSSH 10.2 warns you when a connection isn’t using post-quantum key exchange. On the client side (macOS), it works out of the box. On the server side, you might need to upgrade OpenSSH, which on Ubuntu 18.04 means building from source. The warning Link to heading I SSH’d into my Linux box and got this: ** WARNING: connection is not using a post-quantum key exchange algorithm. ** This…

GCS uniform bucket access silently breaks project owner reads

TLDR : Enabling uniform_bucket_level_access on a GCS bucket disables the legacy role bindings that project owners rely on for object access. Your Terraform SA with roles/owner will get 403s reading objects it could read moments earlier. Add explicit roles/storage.objectViewer bindings on the bucket before flipping the setting. I’d recommend uniform bucket-level access for most buckets.…

What we can all learn from the Claude Code source

TL;DR: Anthropic accidentally shipped the Claude Code source map; I went through it looking for patterns worth borrowing. The codebase reads like accumulated postmortem residue — the compaction circuit breaker exists because telemetry showed sessions burning 250K tokens on doomed retries, the LRU cache was rewritten after it leaked 300 MB, the keychain code references a specific CrowdStrike…

Migrating from kube-prometheus-stack to Google Managed Prometheus

I spent a couple of days migrating our monitoring stack from self-hosted kube-prometheus-stack (KPS) to GKE’s native Google Managed Prometheus (GMP). The end result is simpler, cheaper, and removes about 2 TiB of persistent storage we no longer need. But the migration had enough non-obvious gotchas that I wanted to write it all down. Why migrate? Link to heading kube-prometheus-stack bundles…

Why VPA ignores single-replica pods

TLDR : VPA’s updater defaults to requiring 2 replicas before it will evict a pod. Single-replica deployments are silently excluded from auto-healing — even if they’re crashlooping. You can override this with minReplicas: 1 in the VPA spec, no cluster upgrade needed. I had a pod stuck in CrashLoopBackOff for 20 hours with 192 restarts. VPA had pushed its memory limit down to ~157Mi —…

Getting my Apple Watch workout history into Garmin

TL;DR — I switched from an Apple Watch to a Garmin and wanted to bring my workout history with me. Apple’s data export turned out to be surprisingly lossy — heart rate gets aggregated into 15-minute chunks. I ended up building an iOS app to read HealthKit directly, a Python converter to produce FIT files, and an upload script to push everything to Garmin Connect. 255 workouts,…

Fixing Spotlight Search for Applications on macOS

TL;DR: If Spotlight can’t find your apps, rebuild the Launch Services database with lsregister . It takes a few seconds and doesn’t require a reindex of your entire drive. The problem Link to heading Spotlight stopped finding some of my applications. Searching for apps I knew were installed returned nothing, or surfaced web results instead. Finder search was equally useless. What…

jj for Git Users: A Practical Walkthrough

TL;DR: jj (Jujutsu) is a Git-compatible version control system with some interesting ideas — automatic change tracking, universal undo, and a different take on history editing. It works on top of your existing Git repos, so you can try it without committing to anything. Why I’m trying it Link to heading The v0.39.0 release hit Hacker News and I finally decided to give it a proper go.…

Why Jetson Nano Still Matters in 2026

TL;DR: Jetson Nano is old and stuck on Ubuntu 18.04-era software, but it’s still a great always-on host for bounded edge workloads like OpenClaw. Treat it like an appliance, not a modern dev workstation. Frank Kelly, who also owns a Jetson Nano, asked my OpenClaw bot to write a blog post about it as a test. It politely replied that it would draft something and wait for my approval before…

Mutating webhooks and ghost affinity on StatefulSets

TLDR : Mutating admission webhooks that inject scheduling rules (nodeSelector, tolerations, affinity) persist on StatefulSet pods even after you clean the StatefulSet template. The webhook re-fires on every pod CREATE and can read stale metadata to re-inject what you removed. The fix is to scale to 0 and back up — rollout restart doesn’t work. I was decommissioning a GKE ComputeClass .…

Bulk cleaning stale git worktrees and branches

I use git worktrees heavily for parallel development. One worktree per ticket, across dozens of repositories. They’re especially useful if you work with AI coding agents — each agent gets its own isolated worktree, so it can run tests, install dependencies, and make changes without stepping on your work or another agent’s. The downside is that worktrees accumulate fast. I ended up with…

Rewriting Git History with an LLM for Conventional Commits

TL;DR: Feed your entire git log + file lists into a single LLM call to generate a bash hash map of conventional commit messages, then apply it with git filter-branch in seconds. 143 commits rewritten in 6 seconds, one API call, ~$0.05. Why bother? Link to heading Good commit messages are documentation you get for free — but only if they’re actually meaningful. My repo had months of auto:…

Upgrading OpenClaw to Latest on Jetson Nano with Node 22

This is a follow-up to my original post, Installing OpenClaw on a Jetson Nano , where I got things working with Bun on Ubuntu 18.04. That setup ran fine for a few weeks — until I tried to upgrade. Why upgrade? Link to heading The original Bun-based install ( 2026.2.6 ) was working fine, but I wanted access to newer features — improved Telegram handling, cron job fixes, better model fallback…

Go containers and the OOM killer

TLDR : Go doesn’t auto-detect container memory limits. Without GOMEMLIMIT , the GC lets the heap double freely until the OOM killer strikes. Read the cgroup limit at startup and set GOMEMLIMIT to ~85% of it via an entrypoint script so it adapts automatically when VPA adjusts your limits. I was investigating a pod that had been crashlooping for 25 hours. The usual suspects — liveness probe…

Stop re-running BigQuery queries when paginating

I recently learnt something about BigQuery pagination that I wish I’d known sooner. If you’re paginating BQ results with LIMIT / OFFSET in the SQL, you’re probably paying full slot cost on every single page request — even though BQ already has the results sitting there. The problem Link to heading BigQuery has a query cache , but it only kicks in when the SQL text and parameters…

Happy — controlling Claude Code from your phone

Happy lets you control Claude Code sessions from your phone. You run happy instead of claude , scan a QR code, and your session shows up on your phone. I’ve been using it for a few days — approving permissions on the go, watching output while making coffee, or talking to it with voice input . It’s open source (MIT) and free to use — there’s an optional £19.99/month subscription…

How Cloudflare proxy mode silently breaks SendGrid email delivery

TLDR : If you manage SendGrid DNS records in Cloudflare, make sure PTR-related records are set to DNS-only. Proxying them silently breaks reverse DNS verification and causes email delivery failures with no alerts or warnings. The setup Link to heading If you use SendGrid with a dedicated IP, you’ll have a PTR record so receiving mail servers can verify your identity through reverse DNS. The…

Switching from Poetry to uv

I migrated a few Python projects from Poetry to uv. The conversion is mostly mechanical, so this focuses on what changed and why it was worth doing. TLDR Link to heading uv is generally faster than Poetry for dependency resolution and installation Private registry auth goes from “install a keyring plugin” to “mount a credentials file” Your pyproject.toml moves to PEP 621…

Kubernetes health probes for stateful Python services

TLDR : If your entrypoint script doesn’t use exec , SIGTERM never reaches your Python app and graceful shutdown silently does nothing. Docker compose masks this entirely. I use a single /health endpoint for all three Kubernetes probes — startup, liveness, and readiness. The difference in behaviour comes from failureThreshold in the probe config, not from separate code paths. One endpoint,…

Fixing slow Docker builds on ephemeral CI runners

TLDR : --mount=type=cache makes RUN layers non-deterministic. On ephemeral runners the mount is always empty, so BuildKit can’t match layers from registry cache. Removing cache mounts, switching to registry cache with dynamic fallback, gating exports to deploy branches, and restricting triggers dropped builds from ~27 min to ~2 min — and cut redundant builds entirely. I’d been ignoring…

SSH fallback hosts with ProxyCommand

I have a Jetson Nano at home that I SSH into from my laptop. At home it’s on a local IP, but when I’m out I reach it via a public IP. I got tired of switching between ssh jetson-home and ssh jetson-www depending on where I am. A VPN like Tailscale or WireGuard would also solve this, but I don’t always remember to switch it on. I wanted something that just works without thinking…

Installing OpenClaw on a Jetson Nano

The idea of messaging an AI assistant from my phone while I’m out walking and having it write code that I can steer — “try this approach instead”, “add tests for that edge case”, “actually scrap that, let’s do X” — is genuinely exciting. OpenClaw makes this possible by bridging Telegram (or WhatsApp) to Claude Code, so you can kick off and guide…

Fixing HDMI resolution on a Jetson Nano

I connected my Jetson Nano to an external projector and the console text was microscopic. The framebuffer was running at 3840x2160 (4K) on a display where I could barely read anything. Here’s how I fixed it. The problem Link to heading The Jetson Nano auto-negotiates resolution via EDID when an HDMI display is connected. If your monitor or projector supports 4K, it’ll default to 4K. On…

Running HPA and VPA together on Kubernetes

TLDR : HPA handles horizontal scaling on CPU, VPA right-sizes memory. Split their concerns with controlledResources: ["memory"] so they don’t fight. Drop CPU limits. Match memory requests to limits for Guaranteed QoS. Only create PDBs when you have 2+ replicas. Don’t run HPA in staging. I’ve been writing about autoscaling on GKE for a while now. It started with debugging HPA…

Batch updating files across GitHub repos without cloning

I needed to roll out the same GitHub Actions workflow change across a bunch of repositories. Doing it repo by repo via clone-edit-commit-push sounded painful. Turns out you can read, modify, and commit files directly through the GitHub API without ever cloning a repo. The technique works for any file, but workflow YAML is where I’ve found it most useful — shared CI config that lives in every…

Pulling Twilio Usage Data into Google Sheets

I needed to track daily Twilio costs in a spreadsheet. The Twilio console has usage data but no easy export, and I wanted it updating automatically. The solution: Apps Script + Secret Manager Link to heading I wrote an Apps Script that: Fetches Twilio credentials from GCP Secret Manager (not hardcoded) Pulls daily usage data via the Twilio API Only fetches missing dates (incremental updates)…

Using Google Sheets API with gcloud ADC

I wanted to pull data from a Google Sheet using curl and my existing gcloud credentials. Should be simple, right? The naive approach (doesn’t work) Link to heading gcloud auth application-default login \ --scopes = https://www.googleapis.com/auth/spreadsheets,https://www.googleapis.com/auth/cloud-platform curl 'https://sheets.googleapis.com/v4/spreadsheets/YOUR_SPREADSHEET_ID' \ -H…