THIS ONE CHANGES THE DATABASE, and the plan says so with the command that proves
what the change is rather than the claim alone:
command_hq_sessions
+ plugin text null
+ plugin_config jsonb null
both nullable, no default, no constraint — so every existing row is valid the
moment they exist and there is nothing to backfill. no rename, nothing dropped, no
create-then-constrain, and therefore no --force needed or permitted.
THE PLAN DEMANDS A QUERY, NOT AN EXIT CODE. drizzle-kit push can fail and still
exit 0, so step 3 is a verification that must print exactly `plugin plugin_config`
and whose OUTPUT goes in the report per host — not "schema ✓". I ran that exact
command here before asking seven hosts to run it.
NO bun install step: package.json and bun.lock are untouched since .01. that also
sidesteps the step-order bug the last rollout found, where the standard
plugins:update runs before a plan's install and imports a package that is not there
yet.
the backup step reads the env variable name rather than assuming it. several hosts
use OFFICERDEV_POSTGRES_URL and not POSTGRES_URL, which is the kind of detail that
turns a backup step into a file containing an error message.
av-club's commander template reaches each host through the standard plugins:update
step rather than through this plan — worth saying, because av-club moving is
expected here rather than a surprise.
gate before tagging: typecheck clean, docs:check green, locale parity green. the
ModelSelector test still fails and is still not mine — same chat code, untouched by
this range.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
4.9 KiB
2026.08.29.02 — a plugin can ship a commander
This one changes the database. Two columns, both nullable additions, on a table that already exists.
There is no rename in it, nothing is dropped, and no existing row changes — but db:push is still not
to be trusted on its exit code, so there is a per-host query below and the report must carry its answer
for each host.
No dependency change: package.json and bun.lock are untouched since 2026.08.29.01. There is no
bun install step in this plan — which also sidesteps the step-order problem the last rollout found,
where the standard plugins:update ran before the plan's install.
What is in it
- Plugins can ship commanders. A plugin with
commanders/<name>/COMMANDER.mdoffers one; it appears in Command HQ under a new From plugins section, arrives not running, and must be configured before it can arm. - av-club ships the first one — Convert on arrival: watches a folder, converts what lands in it.
It reaches each host through the standard
bun run plugins:updatestep, not through this plan. - Marketplace, file browser and Convert Image work from earlier today rides along.
Schema — CHANGED. db:push is required.
command_hq_sessions
+ plugin text null which plugin's template made this commander
+ plugin_config jsonb null the owner's answers to that template's inputs
Verified as the whole of it:
git diff --name-only deploy/2026.08.29.01..deploy/2026.08.29.02 | grep -iE 'schema|/db/|databases'
# → src/databases/officer_db/src/command-hq/queries.ts
# src/databases/officer_db/src/command-hq/schema.ts
queries.ts is code, not schema. The only structural change is the two columns above.
Both are nullable with no default and no constraint, so every existing row is valid the moment they
exist and there is nothing to backfill. This is the shape db:push handles cleanly — there is no
create-then-constrain here, and no --force is needed or permitted.
Every host — the commands
After the standard steps in prompts/deploy-commander.md (dirty check, git fetch origin --tags,
git merge --ff-only deploy/2026.08.29.02, bun run plugins:update), in the checkout from PM2's
pm_cwd:
1. Back the database up first
pg_dump "$(grep -m1 -E '^(OFFICERDEV_)?POSTGRES_URL=' .env | cut -d= -f2-)" > ~/db-backups/officer-$(date +%Y%m%d-%H%M).sql
Read the variable name rather than assuming it: several hosts use OFFICERDEV_POSTGRES_URL, not
POSTGRES_URL. mkdir -p ~/db-backups first if it is not there.
2. Push the schema
bun db:push < /dev/null
stdin closed, so it can never sit waiting on a prompt in an unattended run — it fails instead, which
is the outcome you want. Expect [✓] Changes applied and then the two post-push scripts.
3. VERIFY BY QUERY. The exit code is not the answer.
bun -e "
const { db } = await import('officerdb/db'); const { sql } = await import('drizzle-orm');
const r = await db.execute(sql\`select column_name from information_schema.columns
where table_name='command_hq_sessions' and column_name in ('plugin','plugin_config') order by column_name\`);
console.log([...((r as any).rows ?? r)].map((x:any)=>x.column_name).join(' '));
process.exit(0)"
Must print exactly:
plugin plugin_config
Put that line in the report, per host. Not "schema ✓" — the output. A host printing one column, or none, is a failed host: report it, move on, and do not attempt a repair you invented.
4. Restart
pm2 restart officer
officer only. plugins:update has already restarted the plugin sidecars itself.
5. Check it came back
pm2 jlist: officer online and restart_time moved, then /api/health on the host's own port
(9010 on macbookpro and production-pertento-bridge, 9000 elsewhere). 200 or 401 are both healthy.
Per host
| Host | Note |
|---|---|
officer-dev |
SKIP ENTIRELY. The rollout runs here. Its schema is already pushed; nothing else is needed. Name it as skipped. |
alpha |
Nothing special. |
fofos |
The one that matters this time — av-club is installed there and the commander template is the point of the release. |
macbookpro |
macOS, port 9010, a laptop. Unreachable is a SKIP with a note. |
macbruno |
macOS, login brunoalcadapadezmonteirodaconceicao. |
production-pertento-bridge |
Port 9010. |
edge-pertento-bridge |
Nothing special. |
deadpool-rezio-bridge |
Login br, checkout /home/br/officerdev/platform. |
What "done" looks like
Per host: HEAD before and after, whether the backup was written, db:push result, the exact output of
the verification query, and officer back online with a moved restart_time.
The line that matters in the summary: which hosts print plugin plugin_config. That is the only
thing this release actually changes about a machine.