Keep secrets out of your project files — even from AI agents

When you give an AI coding agent access to your workspace, any .env file in the project is fair game. The agent will read it, and the secrets end up in conversation history, logs, or tool call outputs. The fix isn't stricter prompting — it's structural: don't put the secrets there at all.

credential-gateway is a local proxy that solves this by injecting credentials at the network level. Your application connects to localhost:8080 (or localhost:3307 for MySQL, localhost:6380 for Redis) with no credentials in the connection string. The gateway holds the real credentials in ~/.config/credential-gateway/config.yaml — outside every project worktree — and injects them before forwarding each request upstream.

The config file lives at ~/.config/credential-gateway/config.yaml (or /etc/credential-gateway/config.yaml). The gateway refuses to start if the file is group- or world-readable, so a forgotten chmod doesn't quietly undo the whole point.

# example: OpenAI-compatible HTTP proxy
proxies:
  - type: http
    listen: :8080
    target: https://api.openai.com
    auth:
      header: Authorization
      value: "Bearer sk-..."

Your app then points at http://localhost:8080 and uses an empty or placeholder token. No .env, no secrets in the repo, nothing for an agent to read.

Supported today: HTTP (OpenAI-compatible APIs), MySQL, Redis. PostgreSQL support is in progress; Oracle basic support is planned.

If you're running AI agents against a codebase that talks to external APIs or databases, this pattern is worth adopting before a key leaks rather than after.

Stuck mid-task because your Claude account hit quota? Run multiple accounts behind a local proxy

If you use Claude Code heavily, you've probably hit the quota wall mid-conversation and had to either wait or manually switch to a second account — digging up the right ANTHROPIC_AUTH_TOKEN, setting the env var, restarting. It's enough friction to break flow completely.

agent-quota-gateway is a small Go reverse proxy you run locally on 127.0.0.1. You configure your Claude Code client to point at it instead of api.anthropic.com, and it manages a pool of your accounts behind the scenes. When one hits its quota limit, the gateway automatically switches to the next one and tells the client to retry — the mid-task context survives, the switch is transparent.

# point Claude Code at the local gateway
export ANTHROPIC_BASE_URL=http://127.0.0.1:9099

# the gateway reads credentials from env vars at startup
AQG_POOL_MAIN_BACKEND_ACCT1=sk-ant-...
AQG_POOL_MAIN_BACKEND_ACCT2=sk-ant-...

A nice side effect: because the gateway does credential substitution on every request, your real OAuth tokens never travel through Claude's context. If Claude Code ever leaks what it thinks is "your token" in a tool call or a log, it's just the pool name — the actual credential stays in the gateway process.

Anthropic's weekly limit means this can't be used to multiply quota — two accounts gives you two accounts' worth of budget, no more. It's purely a quality-of-life tool for people who have multiple subscriptions and want seamless failover instead of manual juggling. The gateway won't even bind to a non-loopback address unless you explicitly enable shared mode for a Tailscale-internal network.

LightDM login bounces back to greeter after earlyoom kills dbus-daemon

When earlyoom kills dbus-daemon under memory pressure, the user's systemd instance ([email protected]) enters a broken state: all user-level sockets fail with status=219/CGROUP because the cgroup delegation context is corrupt. The next login attempt connects to /run/user/1000/bus — the socket file is still there — but nothing is listening, so dbus-update-activation-environment gets "Connection refused" and xfce4-session (or any DE) crashes immediately. LightDM sees the session exit and returns to the greeter.

The fix-display script (stop lightdm, kill stray X locks, restart lightdm) doesn't help here because the problem isn't an X lock — it's a dead D-Bus.

Diagnosis

cat ~/.xsession-errors
# dbus-update-activation-environment: error: unable to connect to D-Bus:
#   Failed to connect to socket /run/user/1000/bus: Connection refused
# /usr/bin/x-session-manager: X server already running on display :0

sudo -u davidw XDG_RUNTIME_DIR=/run/user/1000 systemctl --user list-units --failed
# dbus.service    failed
# dbus.socket     failed
# pipewire.service failed  (and everything else)

Fix

…more

Native TUI hangs in tmux on Git Bash? It's winpty, pcon, and fork-vs-exec

Launching a native Windows console TUI (Claude Code, codex, etc.) from a tmux pane on Git Bash gives you a frozen screen with a blinking cursor. The usual advice is "wrap it in winpty" — that's wrong, and it took three layers to get to the real fix.

winpty cannot bridge through tmux

winpty <app> works fine in plain mintty, so it's tempting. But inside a tmux pane it deadlocks: as the pane's initial command it hangs forever; as a child of the pane shell it exits with stdin is not a tty. winpty needs to own a Windows console and bridge stdio, and tmux's pty layer breaks that bridge. Don't reach for it in tmux.

The console comes from ConPTY, which disable_pcon kills

Native Windows apps can't use an MSYS/Cygwin pty as a console — to them it's just a pipe, so isatty() is false and the TUI renders garbled or bails. Modern MSYS solves this with ConPTY backing, controlled by the MSYS env var. Check yours:

echo "$MSYS"
# winsymlinks:nativestrict disable_pcon   <- the killer

disable_pcon (often set in dotfiles to mute some harmless MSYS warnings) turns off exactly the ConPTY backing the native app needs. Flip it back on, scoped to just the launch so your interactive shell keeps its setting:

MSYS=enable_pcon claude   # renders correctly

...but only if the app is forked, not exec'd

Here's the part that cost the most time. MSYS=enable_pcon claude works when you type it by hand, yet the same command wired into a launcher script still hangs. The difference is fork vs exec.

Cygwin only allocates a ConPTY when it spawns (fork+exec) a native console app. An interactive shell forks every command, so hand-typing works. A launcher built as an all-exec chain —

bash -lc 'exec bash launch.sh'  ->  exec ... ->  exec env ... claude

— never forks: the native binary replaces the pane-leader process in place via execve, Cygwin never runs its spawn path, no ConPTY, dead TUI. Even a non-interactive bash -c '... claude' works, because that forks.

The fix is to stop exec'ing into the agent and let the shell fork it as a child:

# was:  exec env MSYS=enable_pcon ... claude "$@"
env MSYS=enable_pcon ... claude "$@"
exit $?

The trailing exit $? matters twice: it propagates the child's status so the pane still closes when the app quits, and it defeats bash's "last command gets exec'd, not forked" optimization — guaranteeing the fork. The child stays in the pane's foreground process group, so the pty hangup on pane close still reaches it; nothing gets orphaned.

So the full recipe for a native console TUI in tmux on Git Bash: skip winpty, scope MSYS=enable_pcon to the launch, and make sure something forks the binary instead of exec'ing into it.

Tired of Jira's slow UI? Drive it from your terminal with `jr`

If your day looks like open Jira → wait for the SPA to boot → hunt for the ticket → click into the status dropdown → wait again, you already know the tax. Most of what we actually do to a ticket — move it, comment on it, assign it — is two seconds of intent buried under ten seconds of web app. jira-sh is a tiny bash CLI that skips all of it. One command, jr, talking straight to the Jira Cloud REST API.

jr move PROJ-123 "In Review"
jr comment PROJ-123 "Deployed to staging"
jr view PROJ-123

No Electron, no browser, no waiting. It's a single bash script plus curl and python3 (standard library only for the core commands).

Setup in four lines

git clone https://github.com/shukebeta/jira-sh ~/Projects/jira-sh
bash ~/Projects/jira-sh/install.sh          # adds a source line to ~/.bashrc

# put these in ~/.bashrc
export JIRA_BASE=https://yourcompany.atlassian.net
export [email protected]
export JIRA_TOKEN=your-api-token

source ~/.bashrc
…more