AIO's borg now runs daily from 07:30 UTC for about 2 hours, so the Saturday
05:00 fire can find the lock held and skip the whole week, which trips Uptime
Kuma's 8-day push window. Poll the lock every 5 minutes for up to 6 hours and
defer only if it is still held after that.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Codex 0.165.0 ships as a package tree and runs bin/codex-code-mode-host,
codex-path/ and codex-resources/ relative to itself. The installer verified
the full archive but kept only bin/codex, so code mode failed. Keep the whole
verified tree, link ~/.local/bin/codex to its bin/codex, and replace the
receipted flat install earlier runs left behind once the new tree verifies.
Agents now start pre-mr-review on the work account themselves, through a Klaude
delegate, and continue from its verdict. A council of models on my own accounts
replaces the single reviewer before it, and adding a seat is one table row.
Proof comes from T3's browser, with mp4 recordings, and capture-proof stays as
the fallback. Ticket pages carry their own data, so index.html renders alone in
T3's file viewer, and worktree threads share the main checkout's docs.
A PreToolUse hook sends subagents in T3 threads to delegate_task. Blocker, proof,
review and MR summary rules come from ideas in t3dotgg/tstack.
Manifest lines can now name an OS. The SSH config, pwswitch and espanso are
Linux only, so link-user on the Mac links CLAUDE.md, rules, skills and the
opencode commands and nothing else. link-mcp no longer aborts under the Mac's
bash 3.2 when a server has no token.
Hyprland 0.57 refuses a bind on a bare modifier and an empty input:scroll_method. The switcher's Super-release commit now binds Super_L and Super_R, which also brings back every bind below it in keybinds.lua. An empty scroll method is left unwritten, so libinput still picks per device.
opencode already reads skills from ~/.claude/skills and ~/.agents/skills, so
those needed nothing. The rest did.
The manifest now links agents/AGENTS.md to ~/.config/opencode/AGENTS.md as
well, beside the Claude Code and Codex names.
opencode keeps its MCP servers in its own JSON config, with no file to point
a symlink at, so link-mcp now writes them there too. It builds one entry per
row of user/agents/mcp/servers with the same tokens it gives Claude Code,
and adds ~/.claude/rules/*.md under instructions, since opencode does not
read that directory by itself. Only those two keys change. The file is
written private and swapped in whole because it now holds tokens, and a
config with JSONC comments is left alone with a warning rather than
rewritten without them.
update-containers kept LOCAL_BUILD, a hand-written list of services whose
images are built on the machine, so it would not try to pull them. The
compose files already say this. A service with a build: stanza names a
local image, and there is nothing in a registry to pull.
images_for now drops the image of any compose service that has a build:
stanza. Those services resolve no images, and the run skips them without a
log line. The list, and the app names it carried, are gone. On the VPS,
--list resolves the same images as before for every other service.
The catalog in server/containers had drifted from the VPS. Eleven compose
files and twelve units no longer matched what runs, and two running
services, Gbrown_Agent and BillTracker, were missing. server/vps is now a
copy of the live files, and panama-server reads from it.
server/home holds the home server's services in the same shape. They still
run under Docker, so the podman units there are drafts for a later move, and
nothing installs from that tree. PostHog, Sentry and Plausible are checkouts
of upstream repositories. Only their local files are kept, next to an
UPSTREAM file naming the repository and the exact commit.
Every secret in both trees is a ${VAR} reference named in .env.example, or
CHANGE_ME in files that are not compose files. The live servers still hold
some of those values inline, so a live file can differ from its copy on
those lines alone. Existing .env.example files keep their comments, with new
keys appended at the end.
The ignore rules and the secrets contract now cover both trees, plus the
volume/ and volumes/ directories the home server uses.
config/bash/shell exported SSH_ASKPASS=ksshaskpass with
SSH_ASKPASS_REQUIRE=prefer on every machine. `prefer` means ssh and git reach
for that dialog BEFORE the terminal prompt, so on a headless server the
prompt that would have worked was skipped in favour of a KDE window that
cannot open:
error: /usr/bin/ksshaskpass died of signal 6
fatal: could not read Username for 'https://git.gbrown.org'
A push from a server failed to ASK for a username rather than failing to
authenticate. The shell environment is the one half of Panama a server does
install, so nothing downstream was going to catch this.
The export is now conditional on a session existing to draw in, and on the
binary being there to run. The headless branch also CLEARS both variables
rather than merely declining to set them: an askpass inherited from a parent
that had a display, or from a login predating this guard, is exactly as
unusable and a guard that only withholds leaves it in place. A fresh login
shell on the repaired server still reported prefer until it cleared them.
A desktop is unchanged; a server, a serial console and a plain SSH session
fall back to the terminal prompt, which works.
tests/setup/headless-askpass-contract pins every branch, the inherited case
included, and fails against the previous code and against a guard that only
withholds.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
setup-server queried --query-port, saw "not open", and blanket-opened 80,
443 and 81. On a machine reaching those ports through rich rules -- one CDN's
address sets, say -- neither appears in --list-ports, so the step read a
deliberate, narrow grant as "closed" and replaced it with a public one. It
did this during `panama update`, which asks nothing and runs unattended, so
there was no prompt and no diff for anyone to read. A production server lost
its firewall posture that way, port 81 and the proxy's admin portal included.
Each port is now checked for existing coverage first -- an open port, an
enabled service, or an accepting rich rule, ranges included -- and left alone
when it has some. The bias stays where it was: only a rule that positively
reads as a grant counts, and anything unrecognised still falls through to
opening the port, because a fresh server that is silently unreachable is the
worse failure. A rule that DROPS a port is not coverage either; reading a
block as a grant would leave the proxy unreachable on a machine nobody
thought to re-check.
A machine that manages its own firewall opts out once, with
`echo manual > ~/.local/state/panama/firewall`, and Panama never touches
firewalld on it again. A file rather than an environment variable, because
the run that needs to respect it is the unattended one.
tests/setup/server-firewall-contract pins all of it, and fails against the
previous code on exactly the cases that cost the posture.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
link-mcp gains a stdio transport: the servers row names a launcher under user/agents/mcp/, run from the checkout so every machine runs the same script, and the token reaches it as MCP_TOKEN. sentry-gbrown is the first, pointing Sentry's MCP server at sentry.gbrown.org with a read-only token from the ignored env file.
A server already registered exactly as its row would register it is now left alone. Re-adding it every run threw away the sign-in of the OAuth servers, bill-tracker and atlassian, on every update.
Penpot's MCP server reads the user key only from the userToken query
parameter, never a header. link-mcp now substitutes a {TOKEN} placeholder
in a server's URL instead of sending the Authorization header, and the
servers list gains a penpot row that uses it.
American spelling is the default and British forms turn up in generated
prose. Catch the common endings and the irregulars, with two exceptions:
project vocabulary that the client's documents, ticket titles and codebase
all spell the British way, such as "enrolment" on APSCA, and existing code
identifiers, which are never renamed to fix spelling.
The rules after it shift by one.
The merge request conventions and the coworker list only apply to Ksense
work, but they load in every project including personal ones. Name the
folder that work lives in so an agent reads those sections as conditional
rather than as always in force.
Jira and Confluence reached Claude Code through two claude.ai connectors,
which are configured per account rather than per machine and had been added
twice. Atlassian hosts its own MCP server, so it belongs in this list with
the others and a new machine picks it up from the installer.
It carries no token. The server runs its own OAuth and Claude Code signs in
through /mcp on first use.
Claude Code and Codex start their shells without SSH_AUTH_SOCK. id_ed25519
has a passphrase, so batch ssh from a harness had no way to unlock it and
failed with "Permission denied (publickey)" against server.gib, junior.gib
and git.gbrown.org. The key and the per-host blocks were right the whole
time, which made it read as a key problem and cost several rounds of
checking keys that were never wrong.
The desktop keyring agent at $XDG_RUNTIME_DIR/keyring/ssh already holds the
key unlocked for the interactive session. A Host * block with IdentityAgent
points every host at that socket, so a shell that inherits no agent gets the
same one the terminal uses.
mesa-va-drivers-freeworld Provides mesa-va-drivers, so once the swap has run
once it becomes the only thing providing the name we ask dnf to remove. The
command then resolves to removing and installing the same package in a single
transaction and exits with "conflicting requests", which reported
install-packages as a failed stage on every run after the first even though
the machine was already in the desired state.
The failure also produced misleading output, because dnf explored removing
mesa-dri-drivers and surfaced unrelated complaints from steam, lutris and
tigervnc-server-common.
Check for the freeworld package with rpm -q and skip the swap when it is
already installed.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01TFG55mJBiH2tV38tXcNzJ9
Dragging attachments into customfield_10142 replaces whatever text is
sitting there, so the list the skill wrote was destroyed by the upload it
was describing. Naming each artifact is already the job of the Test Cases
proof cells, which reference it by filename, and step 10 tells me which
files to upload.
Only the Story rule changes. On a Bug that field carries the test-case
table rather than prose.
Anything a person other than me reads now gets an unslop pattern pass
before it ships: MR descriptions, ticket fields, deliverables. The rules
that matter most are the ones a generic pass misses. Write as me in first
person, never name me or use a stand-in, and never flag one of my own
decisions as a deviation, because that reads as my own AI tattling on me
and costs the PM time investigating a non-issue.
Punctuation is stricter than unslop's own pattern: no em dashes, en
dashes, semicolons or arrow glyphs. Generated output is never rewritten,
only regenerated.
Also records Josh as the tech lead on apsca_next, whose dev reviews carry
draft code and schema and are a starting point rather than a finished spec.
An MCP server is a URL plus a bearer token, and the token is why this is a
stage rather than a manifest line. Panama is public, so the tokens cannot
live in it, and neither runtime keeps its server list in a file worth
symlinking: Codex writes them into config.toml beside dozens of unrelated
settings, and Claude Code into ~/.claude.json. link-mcp registers them
through the runtime's own CLI instead.
user/agents/mcp/servers is tracked and names which variable carries each
token. user/agents/mcp/env holds the tokens and is ignored. A new machine
gets the servers by dropping its own env file beside the tracked one.
Only Claude Code is handled, and only where the interview enabled personal
content. Rewriting a section of somebody's live Codex TOML is a worse
failure mode than leaving two lines to paste once.
The scan was keyed off this item becoming visible, but on a machine with no
backlight `visible` stays false until a scan has already found a DDC
display, so it never scanned at all. It now keys off the quick settings
panel opening, which still keeps the second-long I2C probe off shell
startup.