Skip to content

Technology

Beautiful, Structured Logs for Go CLIs with charmbracelet/log

VHS

If most of the logs from your Go CLI still look like a wall of monochrome timestamps and free-form strings, you're leaving a lot of developer experience on the table. Charm's log package gives you colorful, readable, structured terminal output without forcing you into a heavyweight API or a machine-first JSON mindset from the start.

There's nothing wrong with simple logging. The problem is that simple logging tends to stay simple long after your application stops being simple.

At first, a few log.Println calls feel harmless. Then the output gets noisy. Errors look almost the same as info messages. Request context gets squeezed into ad hoc strings. The one line you need is somewhere in the middle of a hundred identical timestamps, and your terminal gives you very little help separating signal from noise.

That's the gap charmbracelet/log tries to fill. It's a leveled, structured, human-readable logger built for terminals. It looks good out of the box, keeps the API small, and still gives you the structured fields and formatting controls you want for real projects.

The Fine Art of a Minimal Reproducible Example

Two hours in, what you're actually shipping is a GitHub issue, a JIRA ticket, or a Slack thread: symptoms, hunches, pasted logs, then one more file because someone asked nicely. Each round trip tugs another detail out of your repo while the runnable version still lives mostly on your machine. Then a follow-up makes you spell out the repro steps you sort of skipped the first time, and halfway through your reply the bug stops looking fuzzy. Nobody merged a fix for you. You just finally described it clearly enough to see it yourself.

That loop is about as expensive as debugging gets, and almost none of it is mandatory. The antidote is the minimal reproducible example (MRE): the smallest, most self-contained piece of code that reliably triggers the problem you're trying to explain. Being kind to whoever reads your ticket is a nice side effect. The main payoff is that your own picture of the break gets sharper. Most of the time you'll see the answer yourself before you hit send.

Bulk Delete GitHub Deployments

Repositories that ship often, especially to GitHub Pages or similar environments, can rack up a long list of deployments. The UI is fine for spot checks, but it doesn't give you a fast way to clear old rows when you only care about what is current. The REST API deletes one deployment per request, so the practical approach is the same as workflow run cleanup: loop with the GitHub CLI (gh).

This post starts with a loop that deletes deployments whose latest status is inactive, then adds a variant that also removes error and failure without touching rows that still end in success.

Setting up RDP on Ubuntu 26.04 LTS

I still use RDP when I want a remote desktop on another machine. On Ubuntu 26.04 LTS Desktop (Resolute Raccoon, April 2026) the built-in path is SettingsSystemRemote Desktop, matching what Canonical has documented since 24.04.

The steps below walk through the toggles, ufw, and how to connect with a client.

Procedure

Open Remote Desktop in Settings

  1. Open Settings.
  2. Navigate to System.
  3. Open Remote Desktop.

    Tip

    If you don't see it, click the search control at the top of Settings, type remote, and open the match under System.

    Settings System List with Remote Desktop

Select and Enable a Connection Method

You'll see two tabs that aren't interchangeable.

  • Desktop Sharing: View or control the desktop while you're logged in. Someone joins your live session. Resolution follows your local session, so the client can look a little soft.
  • Remote Login: Connect when the account isn't in use, closer to a headless workstation login. Resolution follows the client window, so it tends to look sharp. If you're already logged in locally, a remote user may get offered the nuclear option to end your session.

    Tip

    Generally, I use the Remote Login method.

  • Select the tab for the path you want.

  • Turn on the main toggle at the top of that tab.

    Tip

    You may need to click Unlock and enter your password before the toggle will stick.

Fast Local Checks, Trusted CI: Pre-commit and GitHub Actions for Go

For small Go projects, it's easy to tell yourself that gofmt, go mod tidy, and golangci-lint are habits, not policy. That usually works right up until a pull request fails because generated files changed, go.sum drifted, or a formatter was only run on one laptop.

The pattern I keep coming back to is simple: define the validation contract once, usually in a Makefile or Taskfile.yml, then let pre-commit provide fast local feedback and GitHub Actions enforce the same expectations for everyone else. Local hooks keep the loop short. CI keeps the rules shared, visible, and hard to accidentally bypass.

This isn't the only way to validate a Go repository, and it isn't always necessary. But for Go projects with generated code, multiple contributors, or public pull requests, it's a very practical baseline.

Dependabot vs. Renovate: Dependency Management on GitHub

Dependabot vs. Renovate Dependabot vs. Renovate

Dependency updates are easy to picture one repo at a time. They get political fast when the count is hundreds or thousands: security response, CI minutes, reviewer attention, repo standards, and how much supply-chain theater your org is willing to run.

Most organizations and users will start with Dependabot on GitHub. It's an obvious choice: first-party, no extra service to stand up. Plenty of teams still reach for Renovate when policy, monorepos, or odd manifests stop fitting Dependabot's mold.

Blunt matrix:

Capability Dependabot Renovate
GitHub integration First-party, tight dependency graph and advisory wiring Strong GitHub support, but you run it as an app, Action, or your own worker
Configuration model Simple repository-local YAML Layered policy model with presets and package rules
Custom dependency extraction Limited to supported ecosystems Strong, especially with custom regex managers
Monorepo handling Good for straightforward layouts Better for large, mixed, policy-heavy monorepos
Pull request grouping Useful but bounded Very granular by manager, package, path, dependency type, and update type
Automerge policy Usually implemented with GitHub Actions and branch protection First-class Renovate policy, still gated by GitHub rules
Security updates Best native GitHub Advisory Database integration More programmable, but integration depends on deployment model
Fleet-scale operations Low operational burden, fewer controls More operational burden, much more control

Ghostty: The Terminal Emulator That Refuses to Compromise

Ghostty logo

Most terminal emulators ask you to make peace with an awkward tradeoff. You can have something fast but austere, polished but slow, powerful but painfully non-native. Ghostty, created by Mitchell Hashimoto, is interesting because it doesn't merely try to improve one side of that triangle. It tries to make the tradeoff smaller. The result is a terminal that is GPU-accelerated, deeply configurable, standards-aware, and designed to feel like a first-class citizen on the platform it runs on.

At the highest level, Ghostty is a modern terminal emulator for macOS and Linux. That sounds ordinary right up until you look at how it positions itself. The project is explicitly built around three goals:

  • Fast
  • Feature-rich
  • Native

The third goal is where the project gets interesting.

There are plenty of fast terminals. There are plenty of featureful terminals. There are also terminals that feel at home on a specific operating system. Very few try to be genuinely strong in all three categories at once, because that is much harder than slapping a fast renderer on a cross-platform UI toolkit and calling it a day.

Ghostty takes the harder path. On macOS, it uses real Apple frameworks. On Linux, it integrates with GTK4 and the surrounding desktop environment. Underneath those platform-specific shells is a shared Zig core that handles terminal emulation, rendering, and the heavy lifting that makes Ghostty feel so absurdly responsive.

If you spend a good chunk of your day in a shell, an editor, a multiplexer, or a remote session, that combination matters more than it may seem on paper.

GitHub Milestones as Release Payloads

Issues and pull requests accumulate quickly on any active project. Without structure, a repository becomes a flat list of work with no clear sense of what belongs together, what's blocking a release, or how close you're to shipping. Milestones give that structure: lightweight containers that define the payload of a release, track progress automatically, and surface what remains without requiring a separate project board or external tool.

This post covers how milestones work, how to name and manage them, how to operate them through the UI, the GitHub CLI, and the REST API, and how to automate the tedious parts with GitHub Actions. It's opinionated: these are the practices that work in production open source repositories, not a neutral survey of every option.

A milestone is a named container attached to a repository. It holds issues and pull requests, carries an optional description and due date, and displays a live progress bar computed from the ratio of closed to total items. Nothing more. That simplicity is the point.