Document the credentials clobber trap and the smb CLI deploy path
The v1.1.0 deploy wiped the GitHub App credentials because they had been added with credentials:edit on the server: credentials.yml.enc is git-tracked and the hook's checkout -f replaces it. Both the release skill and the App runbook now say to edit locally and commit the .enc. Also replace the env presence-check advice with plain printenv, since the compound check produced a false positive, and document deploying via the smb CLI (key id_11).
Seto Elkahfi committed
Jul 6, 2026 at 07:27 UTC
6b1b166ffd3c2497eae8abc56f81676b639b8297
2 files changed
+44
-21
.agents/skills/release/SKILL.md
+29
-13
index 354dbd5..d6ed9db 100644
--- a/.agents/skills/release/SKILL.md
+++ b/.agents/skills/release/SKILL.md
@@ -111,16 +111,23 @@ git diff origin/main --stat -- config/initializers/ # new boot-time requirement
ssh smb1-deploy 'sudo -u git bash -lc "printenv | grep -oE \"^SOME_NEW_VAR[A-Z_]*\""'
```
-Notes that save time:
+Notes that save time (each learned from a real incident):
- The `git` user's `~/.profile` is the env source; the post-receive hook
starts with `. ~/.profile`, so vars set there do reach `db:prepare`, Puma,
- and `bin/jobs`. Rails credentials are the alternative for multi-line
- secrets (private keys) — see the runbooks under `docs/`.
-- Beware shell false positives when checking remotely: `printenv | grep -c X`
- can match `SUDO_COMMAND` (your own command line), and `pgrep -f X` over ssh
- matches its own invocation. Anchor patterns (`^X=`) and exclude the
- grep/ssh process before trusting a count.
+ and `bin/jobs`.
+- **Rails credentials must be edited locally and committed, never on the
+ server.** `config/credentials.yml.enc` is git-tracked and the deploy does
+ `git checkout -f main` — a `credentials:edit` in the server work tree is
+ silently wiped by the next deploy (this deleted the GitHub App credentials
+ once). Edit with the local `config/master.key`, commit the `.enc`, and let
+ the deploy distribute it.
+- The only trustworthy remote presence check is `printenv VAR` in the login
+ shell: `ssh ... 'sudo -u git bash -lc "printenv GITHUB_WEBHOOK_SECRET
+ >/dev/null && echo present || echo absent"'`. Compound `[ -n "$VAR" ]`
+ checks through the ssh/sudo/bash quoting layers have produced false
+ "set" readings, `printenv | grep -c X` matches `SUDO_COMMAND` (your own
+ command line), and `pgrep -f X` over ssh matches its own invocation.
### Production database state
@@ -146,15 +153,24 @@ git push origin main
## 4. Deploy
+The operator deploys with the interactive `smb` CLI from the repo root on
+`main` — it logs in, detects the Rails app, and pushes with the right key
+(`~/.ssh/id_11@smbcloud`), ending in "Deployment complete":
+
```bash
-git push smbcloud main # the bare-repo post-receive hook does the rest
+smb
```
-Remember the hook's sharp edges (deployment skill has the full list): no
-`set -e`, migration output goes to the pushing terminal — **read the push
-output**, it is the only record of `db:prepare`'s success — and `bin/jobs
-start` stacks a new Solid Queue supervisor on every deploy unless the hook
-has been amended with a `pkill -f solid_queue || true` line first.
+A plain `git push smbcloud main` does the same thing but only if that key is
+what ssh offers; the `Host api-1.smbcloud.xyz` entry in `~/.ssh/config` is
+not configured, so without the smb CLI expect `Permission denied (publickey)`.
+
+Either way it lands on the bare-repo post-receive hook, which has sharp
+edges (deployment skill has the full list): no `set -e`, migration output
+goes to the pushing terminal — **read the push output**, it is the only
+record of `db:prepare`'s success — and `bin/jobs start` stacks a new Solid
+Queue supervisor on every deploy unless the hook has been amended with a
+`pkill -f solid_queue || true` line first.
## 5. Post-deploy verification
docs/github-app-reviews.md
+15
-8
index b1f5a93..56dcee5 100644
--- a/docs/github-app-reviews.md
+++ b/docs/github-app-reviews.md
@@ -44,9 +44,9 @@ secret (`bin/rails secret | head -c 48`), and **Generate a private key**
## Configuration
-Preferred in production: Rails credentials (`bin/rails credentials:edit`),
-because the private key is multi-line and prod env vars live in the `git`
-user's `~/.profile`, which is single-line only:
+Preferred in production: Rails credentials, because the private key is
+multi-line and prod env vars live in the `git` user's `~/.profile`, which is
+single-line only:
```yaml
github_app:
@@ -58,11 +58,18 @@ github_app:
-----END RSA PRIVATE KEY-----
```
-This block is already set in production credentials (verify without booting
-the app: `bin/rails runner 'c = Rails.application.credentials.github_app; puts
-c.app_id, c.webhook_secret.length'`). When pasting the key, indent every PEM
-line to match `private_key: |`; on the server `bin/rails credentials:edit`
-needs an editor that waits, e.g. `EDITOR=nano bin/rails credentials:edit`.
+**Edit credentials locally and commit — never on the server.**
+`config/credentials.yml.enc` is git-tracked, and the deploy hook's
+`git checkout -f main` overwrites the server copy, silently deleting any
+server-side edit on the next deploy (this happened: the App credentials were
+added on the server on 2026-07-05 and wiped by the v1.1.0 deploy the next
+day). Run `EDITOR=nano bin/rails credentials:edit` in a local checkout that
+has `config/master.key`, paste the block, commit the `.enc`, and deploy.
+
+When pasting the key, indent every PEM line to match `private_key: |`.
+Verify after deploy without booting the app:
+`bin/rails runner 'puts GithubAppService.configured?'` on the server — and
+expect `POST /github/webhooks` to return 401 (not 503) to an unsigned probe.
Note the `webhook_secret` here is separate from `stripe.webhook_secret`; they
share a key name but live under different top-level parents.