Entries for October 2026

  1. Portrait of Onur Solmaz
    Onur Solmaz · Post · /2026/10/08

    I bought my own TLD! (well not really)

    TLD stands for top-level domain, but if you clicked this post, you already know that. To make this joke more accurate, I would need to say “I bought a second-level domain”, but the amount of people who know what a SLD is is even smaller than the amount of people who know what a TLD is. Hence TLD in the title.

    My point is, I bought a 4 letter domain that is reminiscent of a .co.uk or .co.in. It is, *drumroll* o9.nz. It costs me around 10 usd per year.

    Now why would I do that?

    Because the amount of mini projects I have created and am actively using exploded with coding agents. But I don’t have the money, or patience to buy a new domain for each of them. I am not an exited cofounder who collects domains as a hobby (yet).

    Theo Browne’s t3.gg gave me the idea, but what convinced me was that I could use it as a dumping ground for my side projects without getting bankrupt.

    How I found the domain

    You might realize a New Zealand .nz domain is not the first choice, when I could get .gg. The problem is that THEY ARE ALL TAKEN. Apparently most 4 letter domains are taken.

    I knew I wanted it to start with o of onur and I wanted it to be a numeronym like t3 (= t(heo)).

    I slopped a script to scan for available domains, click here to use it yourself.

    With o3, literally everything was taken. My next choice was o9, from o(nursolmaz). 2 were available: o9.pe and o9.nz. I decided against .pe for Freudian reasons, and also it looks better when none of the letters have descenders.

    The keys for o and 9 are right on top of each other in QWERTY, which was another plus!

    Example

    unyolo is one of the projects I use whenever I run an agent that has the risk of getting prompt injected. For it, I had bought the domain unyolo.io before.

    But .io domains now cost 40-50 bucks a year. And I won’t get an .io domain for every side project. So now it lives under unyolo.o9.nz.

    Email

    Having a very short domain, I thought, how far can I push for maximum wtf effect?

    Apparently RFC 5322 allows for ? in email addresses like [email protected].

    But to my disappointment, Google workspace did not accept it. Neither does it accept [email protected].

    So I ended up choosing [email protected]. I will use this email whenever I don’t want to give my real one 🤗

    Conclusion

    Thank you New Zealand for hosting my vibeslop.

    Visit o9.nz to see the full directory.

  2. Portrait of Onur Solmaz
    Onur Solmaz · Post · /2026/10/01

    How to prevent data loss on GitHub when working with agents

    Note: This post is AI-assisted. It was written with Claude Opus 5.5 through Claude Code.

    My coding agents use the GitHub CLI (gh) to push code and merge pull requests. The token behind gh decides what an agent can do on GitHub, and the default one can also edit or switch off the branch rules that protect a repository. This post explains why that is a data loss risk, and how to set up a fine-grained personal access token that does the daily work but cannot change the rules.

    The default gh token

    When you run gh auth login through the browser, gh receives an OAuth token that starts with gho_ and carries the scopes repo, read:org, workflow and gist. The repo scope gives full control over every repository you administer, and that includes rulesets and repository settings. Classic OAuth scopes have no setting that removes only the admin part, so as long as gh runs on this token, so does an agent with full admin rights.

    The dedicated gh ruleset command is read-only, which makes this easy to miss. The gh api command passes any request through to the REST API, though, so a single gh api -X PUT repos/OWNER/REPO/rulesets/ID call can rewrite or disable a ruleset with the token you already have.

    Rulesets as the last line against data loss

    A force push to main replaces its history with whatever the agent has locally. Deleting a branch removes it entirely. In both cases the old commits stop being reachable from any branch, and getting them back means finding the old commit hashes in a local clone or asking GitHub support. Agents often work in fresh clones and worktrees that they delete afterwards, so the local copy you would rely on may not exist.

    A ruleset that blocks force pushes and deletion on the default branch closes this hole, but only against someone who cannot edit the ruleset. A token that can edit rulesets turns them into advice. An agent that hits the block while it tries to rewrite history will often read the ruleset, disable it, force-push, and enable it again, because in the middle of a task it always has a good reason. The block has to hold anyway. The same Administration rights also let a token change the default branch. With a fine-grained token, they even allow deleting the repository outright.

    When the token has no admin write access, the ruleset becomes a hard limit. The agent gets a 403, stops, and has to ask me. I then do the risky step myself with a full token, knowing what it does. Instructions in a prompt such as “never force-push” are text that a model can argue its way around in the middle of a task. A permission that GitHub enforces on the server cannot be argued with.

    Creating the token

    Fine-grained tokens can only be created in the browser, at github.com/settings/personal-access-tokens/new. There is no API to mint them, which I think is deliberate, since it keeps a human in the loop for every new token. After you pick the resource owner and the repositories, grant the permissions below.

    These are the permissions I grant, with the reason for each one:

    PermissionAccessReason
    ContentsRead and writePush and pull code
    Pull requestsRead and writeOpen and merge pull requests
    IssuesRead and writeIssue work from gh
    WorkflowsRead and writePush changes to files in .github/workflows
    ActionsRead and writeRead CI logs and rerun jobs
    Commit statusesRead and writeReport check results
    SecretsRead and writeSet CI secrets (see below)
    AdministrationRead-onlyRead rulesets to explain a rejected push, without editing them
    MetadataRead-onlyRequired by GitHub
    Gists (account)Read and writeShare snippets

    Everything else stays at “No access”. Leave Administration write off above all, because it is the permission that edits rulesets. Webhooks deserve special mention, since a token with webhook write access can send every event in a repository to a server outside GitHub without leaving a diff anywhere.

    Secrets is the judgment call in this list. The API never returns secret values. A token with write access can still replace one silently, and the next workflow run will use whatever the token put there. I set CI secrets through agents often enough that I keep this permission. If you don’t, set it to “No access” and do those changes by hand.

    It is tempting to grant everything except Administration and be done with it. I recommend against that, because the other permissions carry their own risks, and a daily token should only hold what the daily work needs. When a task needs more, grant it to the token for that task and take it away again, or do the task yourself.

    Installing the token

    Give the token to gh without putting it in your shell history or a chat window. Run the following and paste the token when it asks:

    gh auth login -h github.com --with-token
    gh auth setup-git
    

    The second command makes Git use the same token for git push. On macOS, check Keychain Access for an older github.com internet password entry. Git may still use that entry for pushes instead of the gh token, so delete it. On a Linux machine without a keyring, gh stores the token in plain text in ~/.config/gh/hosts.yml, so set that file to mode 600.

    Checking what a token can do

    The permission screen is easy to misread, so check the token against the API instead. For reads, send a GET request; 200 means the permission is there and 403 means it is not. For writes, send an empty body to a create endpoint, or a PUT to a made-up name. A 403 means the permission is missing. A 422 or 400 means GitHub accepted the permission and rejected the empty request during validation, so nothing changes.

    gh api repos/OWNER/REPO/rulesets                          # 200: Administration read
    gh api -X POST repos/OWNER/REPO/rulesets                  # 403: no Administration write
    gh api -X POST repos/OWNER/REPO/git/refs                  # 422: Contents write
    gh api -X PUT repos/OWNER/REPO/actions/secrets/__probe__  # 422: Secrets write
    gh api repos/OWNER/REPO/hooks                             # 403: no Webhooks access
    

    The second line is the one that matters for this post. If it returns 403, the token cannot edit or disable your rulesets.

    Limits of a token

    Contents write still lets the agent force-push to and delete any branch that no ruleset covers. The token makes the rulesets hold, so the rulesets have to cover the branches you care about. I apply a default-branch ruleset to every new repository with github-sane-defaults.

    A token is also a set of checkboxes. It can say “this token may push”, but it cannot say “allow normal pushes, deny force pushes to main, and ask me before merging”. For rules like that I am building unYOLO, a broker that holds the GitHub credential itself and gives the agent only a broker credential. Its policy file denies force pushes to main and ref deletion outright, and it sends a request to my phone before admin merges or repository deletion. Until that runs everywhere, the fine-grained token is the cheapest protection I have found, and it took about ten minutes to set up.