projmux 0.12.2 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,157 @@
1
+ # Troubleshooting
2
+
3
+ Start with read-only diagnostics:
4
+
5
+ ```sh
6
+ projmux doctor
7
+ ```
8
+
9
+ `doctor` never installs packages, rewrites configuration, or changes a tmux
10
+ server. Run a remediation command only after reviewing the finding that
11
+ recommended it. For the operational journal's privacy and retention contract,
12
+ see [Operational Diagnostics](operational-diagnostics.md).
13
+
14
+ ## App socket marker migration
15
+
16
+ Projmux 0.13 and newer require two server-global markers before an ordinary
17
+ command may mutate the app tmux server:
18
+
19
+ - `@projmux_app=1` declares app ownership.
20
+ - `@projmux_socket_name=<name>` declares the logical `-L <name>` route.
21
+
22
+ An app server started by a pre-0.13 release can still be live with the first
23
+ marker and no logical marker. In that partial state, `shell`, attach, and
24
+ materialization commands fail closed and print the exact recovery command.
25
+ For the default app socket, run:
26
+
27
+ ```sh
28
+ projmux config apply --socket projmux
29
+ ```
30
+
31
+ This explicit apply keeps the live server and its sessions, binds `-L projmux`
32
+ to one absolute socket path and server PID, sources the generated config,
33
+ writes the missing logical marker, and verifies both markers against the same
34
+ server generation. Ordinary commands do not write the marker. Apply also
35
+ refuses a foreign server, a different existing logical marker, an alias/path
36
+ mismatch, or PID drift; do not replace the refusal with a raw `tmux set-option`
37
+ command.
38
+
39
+ For a non-default socket, use the exact name printed by the failing command:
40
+
41
+ ```sh
42
+ projmux config apply --socket <name>
43
+ ```
44
+
45
+ Then retry the original command.
46
+
47
+ ## Diagnostic sequence
48
+
49
+ Use this order so each step remains read-only until you deliberately run the
50
+ recovery command:
51
+
52
+ ```sh
53
+ projmux doctor --section runtime --verbose
54
+ projmux diagnostics log
55
+ tmux -L projmux show-options -gqv @projmux_app
56
+ tmux -L projmux show-options -gqv @projmux_socket_name
57
+ tmux -L projmux display-message -p -F '#{socket_path} #{pid}'
58
+ ```
59
+
60
+ Replace `projmux` in all three tmux commands with the exact logical socket name
61
+ you are diagnosing. Expected healthy marker output is `1` and that same socket
62
+ name. The final read records the physical socket/PID pair for comparison; it
63
+ does not grant authority or repair anything.
64
+
65
+ The marker-specific Doctor codes map to these actions:
66
+
67
+ | Code | Meaning | Remediation |
68
+ | --- | --- | --- |
69
+ | `runtime.route-marker.missing` | App-owned live server has no logical marker, normally after a pre-0.13 live-server upgrade. | Run the exact `projmux config apply --socket <name>` printed by the failing ordinary command, then retry it. |
70
+ | `runtime.route-marker.mismatch` | The app-owned server declares a different logical route. | Do not overwrite it. Inspect the diagnostic sequence and confirm which `-L` route owns the server. |
71
+ | `runtime.route-marker.unreadable` | Doctor could reach the socket but could not read one or both ownership markers. | Inspect `projmux diagnostics log`, tmux/socket permissions, and the exact marker reads. Do not apply until the read failure is understood. |
72
+
73
+ Other runtime Doctor codes use bounded remediation identifiers in text and
74
+ JSON:
75
+
76
+ | Code family | Remediation |
77
+ | --- | --- |
78
+ | `runtime.socket.unreachable` | Start the app with `projmux shell`; if a server should already exist, inspect the exact socket first. |
79
+ | `runtime.socket.probe-failed`, `runtime.backend.unknown` | Inspect `projmux diagnostics log` and the operational journal. |
80
+ | `runtime.config.generated-missing`, `runtime.config.generated-invalid` | Run `projmux config apply --socket <name>` after confirming the target server. |
81
+ | `runtime.config.generated-unreadable` | Inspect config and directory permissions before applying. |
82
+ | `runtime.config.applied-stale` | Run the exact config apply command to reload the generated config. |
83
+ | `runtime.config.applied-unknown` | Start or identify the app runtime before attempting a reload. |
84
+ | `logs.*` | Follow the finding's `inspect-state-permissions`, `inspect-log-permissions`, or `inspect-operational-journal` remediation. |
85
+ | `registry.materialize.*` | Inspect the reported Registry topology. Doctor is read-only and does not repair it. |
86
+
87
+ Informational `*.ready`, `*.current`, `*.reachable`, `*.none`, `*.clean`, and
88
+ `*.audited` codes need no remediation. JSON exposes the same `code` and
89
+ `remediation` values as verbose text.
90
+
91
+ ## Codex app-server install topology
92
+
93
+ Start with the read-only integration report:
94
+
95
+ ```sh
96
+ projmux doctor --section integrations --verbose
97
+ ```
98
+
99
+ The `Codex app-server` result keeps four readiness axes separate:
100
+
101
+ - `Endpoint readiness` says whether the existing endpoint is ready, dead, or
102
+ failed with a bounded reason.
103
+ - `running executable` plus the sanitized version fields distinguish a proven
104
+ managed executable from unknown identity and current from skewed versions.
105
+ - `manager ownership` comes only from the official daemon backend result; an
106
+ absent or unclear result is never guessed from endpoint health.
107
+ - `remote control` independently reports disabled, connecting, connected,
108
+ errored, unsupported, unavailable, or unknown.
109
+
110
+ `Source`/`reason`, `App-server probe`, `install capability`, and `lifecycle`
111
+ remain separate supporting fields. A ready endpoint therefore does not hide an
112
+ unmanaged process or version skew.
113
+
114
+ `external-cli-only` means the ordinary Codex CLI executable is present, but
115
+ the canonical managed payload needed by `codex app-server daemon start` was not
116
+ observed. It does not mean the ordinary CLI is unsupported and does not prove
117
+ who owns a running process.
118
+
119
+ An explicit native action refuses a ready unmanaged or version-skewed endpoint.
120
+ The refusal reports `shared-clients-disconnect`: replacing this shared process
121
+ can interrupt every attached Codex client. For a managed skew, confirm the
122
+ interruption and run `codex app-server daemon restart`. For an unmanaged
123
+ endpoint, close every sharing client, stop the process through the operator
124
+ that owns it, then run `codex app-server daemon start`. Projmux never performs
125
+ those stop/restart steps or invents an ownership-specific kill command.
126
+
127
+ If native app-server features are needed, review the
128
+ [official Codex CLI installation options](https://learn.chatgpt.com/docs/codex/cli)
129
+ and install or repair the managed standalone payload. Then rerun Doctor. Do not
130
+ copy binaries, create symlinks in the Codex home, or edit the control socket as
131
+ a diagnostic workaround. Doctor, Settings, and support-report collection never
132
+ start the daemon or modify the installation.
133
+
134
+ ## Incomplete npm install
135
+
136
+ If the npm shim exits before Projmux starts with:
137
+
138
+ ```text
139
+ projmux: unsupported or incomplete npm install for <platform>/<arch>.
140
+ Expected optional dependency @projmux/<platform>-<arch> to provide bin/projmux.
141
+ ```
142
+
143
+ the platform-specific optional package is missing. This can happen when npm
144
+ reuses stale package metadata or optional dependencies were disabled. Because
145
+ the Go binary is absent, `projmux doctor` cannot run yet. Re-resolve the current
146
+ release and its optional dependency:
147
+
148
+ ```sh
149
+ npm cache verify
150
+ npm install -g projmux@latest --include=optional
151
+ projmux version
152
+ projmux doctor
153
+ ```
154
+
155
+ If the same error remains, remove the incomplete global package, refresh npm's
156
+ package metadata, and reinstall. Do not copy a binary from another platform.
157
+ GitHub Release and source alternatives are documented in [Install](install.md).
package/docs/upgrading.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Upgrading
2
2
 
3
+ If an older live app server refuses ordinary commands after upgrading, or npm
4
+ cannot resolve the platform optional dependency, follow
5
+ [Troubleshooting](troubleshooting.md). The canonical marker recovery is the
6
+ exact `projmux config apply --socket <name>` printed by the refusal; ordinary
7
+ runtime commands never backfill missing markers.
8
+
3
9
  projmux has two update surfaces:
4
10
 
5
11
  - `projmux shell` reads the cached release status before opening the app. When
@@ -25,7 +31,9 @@ projmux update apply
25
31
  Use `--dry-run` to see the planned action and `--no-apply` to skip reloading
26
32
  the live tmux config after the binary changes. `--no-apply` suppresses live
27
33
  tmux access only; the new binary still migrates the keymap schema and
28
- marker-owned provider files, then writes the generated config. See
34
+ marker-owned provider files, then writes the generated config. It finishes by
35
+ printing `projmux config apply --socket projmux` as the explicit live
36
+ convergence still required before ordinary mutation. See
29
37
  [Keymap schema migration](#keymap-schema-migration) and
30
38
  [Managed Agent hook producer migration](#managed-agent-hook-producer-migration).
31
39
 
@@ -44,15 +52,253 @@ still requires an explicit `PROJMUX_INSTALLER=github-release`.
44
52
 
45
53
  ## Behavior Changes
46
54
 
55
+ ### Registry schema v2 final Window anchors
56
+
57
+ The unreleased intermediate schema-v2 Window field `primaryPaneRef` has been
58
+ replaced by the final v2 shape:
59
+
60
+ - `anchorPaneRef` is required and names a same-Window shell or managed Agent
61
+ Pane.
62
+ - `defaultShellPaneRef` is optional; when present it names a direct
63
+ Window-owned shell Pane.
64
+
65
+ The on-disk `schemaVersion` remains `2` because the intermediate shape was not
66
+ published. A schema-v1 Registry migrates directly to these fields. An
67
+ intermediate-v2 Registry is identified by raw field presence and normalized
68
+ under the Registry lock. The migration publishes an exact mode-0600 backup and
69
+ a checksum/repair report before staging and atomically replacing the Registry.
70
+ Mixed legacy/final fields, mixed Window authorities, dangling or cross-Window
71
+ anchors, and invalid default shells fail closed without changing the source.
72
+ Repeating migration on final-v2 is a byte-level no-op.
73
+
74
+ The final writer never emits `primaryPaneRef`. A matching intermediate
75
+ pre-release validator therefore sees final-v2 as invalid because its required
76
+ legacy authority is absent. Not every old read surface necessarily runs that
77
+ validator: an old command may ignore the new fields and return a partial view.
78
+ That is evidence of incompatibility, not permission to proceed. Operators must
79
+ refuse a binary-only downgrade before installation and must not permit the old
80
+ binary to write final-v2 bytes. To roll back during the prerelease window, stop
81
+ the final writer, restore the exact pre-normalization Registry backup, and
82
+ restore the matching intermediate binary as a pair. Snapshots are not migration
83
+ or rollback inputs and are never rewritten by this normalization.
84
+
85
+ Rollback rehearsal is byte-oriented: record the backup path, mode, SHA-256,
86
+ and matching pre-release binary revision; stop every final-v2 writer; atomically
87
+ restore those exact intermediate-v2 bytes; verify their checksum; install the
88
+ recorded matching binary; then run its read-only validation before permitting a
89
+ write. Installing only the intermediate binary while leaving final-v2 Registry
90
+ bytes in place is deliberately rejected and is not a rollback procedure.
91
+
92
+ ### `create` is resource-backed on every spelling
93
+
94
+ **Breaking.** `create pane`, `create agent`, and the `create
95
+ codex|claude|antigravity` shortcuts no longer have two product models behind the
96
+ presence of `--project`. Previously an invocation *with* `--project` created
97
+ Registry resources and materialized them detached, while an invocation *without*
98
+ it ran a runtime-only split of the current tmux window that created no Projmux
99
+ resource and moved the client. That second model is gone.
100
+
101
+ What changes for an existing invocation:
102
+
103
+ | Invocation | Before | Now |
104
+ | --- | --- | --- |
105
+ | `create codex --placement right` inside a managed Project | Runtime-only split of the current tmux window; no Registry resource; the client followed the new pane | Creates an Agent and its managed Pane below the **active managed Project's active Window**, anchored on the active Pane, materialized detached; the client does not move |
106
+ | `create codex -w hi --create-window` | `flag provided but not defined: -w` | Creates Window `hi` under the active managed Project, then the Agent and its managed Pane inside it |
107
+ | `create pane --placement down` inside a managed Project | Runtime-only shell split | Creates a Pane resource below the active Window and splits it detached |
108
+ | Any `create` inside Home, a control session, an unattributed or foreign pane | Runtime-only split | Exit `2` naming `--project`, with zero Registry writes and zero tmux mutations |
109
+ | Any `create` from outside tmux with no `--project` | Runtime-only split against the default server | Exit `2` naming `--project`; no server is probed |
110
+ | Any `create` with an explicit `--project` | Resource-backed | Unchanged |
111
+ | `create pane -o uid\|name\|ref\|metadata\|json` with no `--project` | Exit `2` saying the compatibility split creates no resource | Works: the projections resolve the created resource |
112
+
113
+ Two consequences are worth calling out:
114
+
115
+ - **The client no longer follows the new pane.** Every create is detached. Use
116
+ `projmux focus pane uid:<uid>` — or `-o pane-id` and your own `select-pane` —
117
+ when you want to move there.
118
+ - **Panes created this way are now managed.** They appear in `get panes`,
119
+ in the primary navigation surface, and in `reconcile resources`. Nothing
120
+ adopts the runtime-only panes created by older releases; they stay visible in
121
+ the Runtime diagnostics surface and are never imported automatically.
122
+
123
+ The default keybindings whose bodies are `create codex|claude|pane --placement
124
+ right|down` keep their spelling and their "split where I am" meaning, and now
125
+ produce Registry resources. `internal agent-pane launch-default` — the saved
126
+ default split mode, bound to Ctrl-Shift-R/L in the terminal adapters — kept its
127
+ spelling and its meaning, and now produces the same Registry resources: see
128
+ [Every split surface is Registry-first](#every-split-surface-is-registry-first).
129
+
130
+ The generated tmux config changed shape to support that. Every projmux
131
+ `run-shell` binding is now rendered as `run-shell "TMUX_PANE=#{pane_id} <bin>
132
+ ..."`, because tmux exports `$TMUX` to a `run-shell` child but never
133
+ `$TMUX_PANE`, and without the exact pane a binding cannot tell where it was
134
+ pressed. `projmux config apply` — which `make install` and `projmux update
135
+ apply` run for you — rewrites the generated file. If you hand-copied a projmux
136
+ `run-shell` line into your own `~/.tmux.conf`, add the same prefix.
137
+
138
+ If you need a raw, unmanaged tmux split, use tmux itself (`split-window`).
139
+ projmux does not spell that as a resource verb.
140
+
141
+ ### Every split surface is Registry-first
142
+
143
+ The saved-default split binding, the `Alt-7` provider picker, and the resume
144
+ picker used to open a pane by calling tmux's `split-window` directly. Those panes
145
+ were runtime-only: no uid, no owner Window, no Agent row, and no row in `get
146
+ panes` or the primary navigation surface. They now go through the same canonical
147
+ `create` route the `create codex|claude|pane` bindings and typed commands use, so
148
+ every pane a Projmux surface opens is a managed resource.
149
+
150
+ What changes for you:
151
+
152
+ - **Panes from the picker and the saved default are managed.** They appear in
153
+ `get panes`, `get agents`, the primary navigation surface, and `reconcile
154
+ resources`, and they are deleted with `projmux delete pane|agent`. Panes those
155
+ surfaces created in older releases are not adopted; they stay visible in the
156
+ Runtime diagnostics surface.
157
+ - **A launch needs a resolvable Project.** These surfaces create resources, so
158
+ they follow [Create scope](cli-guide.md#create-scope) like any other create. A
159
+ UI action taken outside a managed Project fails with the same
160
+ `pass --project <ref>` guidance instead of opening an unmanaged pane.
161
+ - **The legacy `ai split` route is gone**, along with its `--agent`,
162
+ `--force-agent`, and `--print-pane-id` flags. The `ai` root itself was retired
163
+ earlier; nothing reaches that handler now. Automation that wants the new pane's
164
+ handle uses `-o pane-id` on a canonical create — see
165
+ [AI Agent Shortcuts](ai-agent-shortcuts.md).
166
+ - **The AI title watcher no longer starts automatically.** The canonical create
167
+ route never started it, and these surfaces now behave the same way. Pane
168
+ titles, topics, and status come from provider hooks and from `projmux agent
169
+ topic`. `projmux internal agent-hook watch-title` still exists for the hook
170
+ contract.
171
+
172
+ If you need a raw, unmanaged tmux split, use tmux itself (`split-window`).
173
+
174
+ ### One lifecycle trigger route
175
+
176
+ The generated tmux config used to install two lifecycle routes: `internal tmux
177
+ reconcile-bindings` on `after-new-window`/`after-split-window`, and `internal tmux
178
+ release-dead-agent-panes` on `pane-exited`/`after-kill-pane`. Both answered the
179
+ same question with a different subset of one reconciliation, so a pane exit
180
+ inside a session that had also just gained a window paid for two registry
181
+ transactions to reach the state one pass reaches.
182
+
183
+ All four hooks now invoke one hidden route, `internal tmux converge
184
+ --socket-path <path> --session <id> --reason <config-apply|runtime-created|runtime-exited>`,
185
+ and so does `projmux config apply`. At most one convergence worker runs per exact
186
+ tmux server, so a burst of hooks costs one pass instead of one per event. Which
187
+ hooks each config installs is unchanged: the app config carries all four, and the
188
+ standalone snippet carries the two pane-exit hooks only, so a raw `new-window` in
189
+ a session projmux does not own still stays unmanaged.
190
+
191
+ `projmux config apply` — which `make install` and `projmux update apply` run for
192
+ you — rewrites the generated file. A tmux server still holding config from an
193
+ older binary keeps invoking the two retired routes, which now fail; the hooks
194
+ guard every invocation with `>/dev/null 2>&1`, so nothing surfaces, and the next
195
+ `config apply` replaces them. If you hand-copied a projmux `set-hook` line into
196
+ your own `~/.tmux.conf`, re-copy it from `projmux config render standalone`.
197
+
198
+ ### Discovery no longer registers Projects
199
+
200
+ **Breaking.** A directory found under a discovery root used to become a Registry
201
+ Project on its own. Any mutation route ran a reconcile prelude that walked the
202
+ configured workdirs and registered every child it did not recognize, so a single
203
+ `projmux create pane` in one repository could add a Project for every sibling
204
+ directory beside it. Scanning a directory is now a scan and nothing else.
205
+
206
+ Three collections, three authorities:
207
+
208
+ | Thing | What it is | What it is not |
209
+ | --- | --- | --- |
210
+ | Workdirs / `PROJMUX_MANAGED_ROOTS` / `PROJMUX_PROJDIR` | Scan roots to look inside | Not a statement that anything inside them is a Project |
211
+ | A discovered child | An unregistered candidate | Not managed identity, however often it is scanned |
212
+ | A Registry Project | Managed identity, with a stable uid | Not derived from a path, a name, or a scan |
213
+
214
+ What registers a Project now:
215
+
216
+ ```sh
217
+ projmux create project --root /abs/path/to/repo
218
+ ```
219
+
220
+ …or opening that one candidate from the Projects sidebar, which performs the same
221
+ registration for that exact path. Both are idempotent: a root an existing Project
222
+ already claims writes nothing at all. Sibling candidates under the same discovery
223
+ root stay unregistered.
224
+
225
+ What changes for an existing invocation:
226
+
227
+ | Invocation | Before | Now |
228
+ | --- | --- | --- |
229
+ | `create window --project <name>` where `<name>` is a discovered-but-unregistered directory | The reconcile prelude registered it (and every other discovered child) first, then the create succeeded | Exits non-zero naming the exact `--root` to register, or open it once from the sidebar |
230
+ | Any mutation route, with N children under a scan root | Up to N Projects appeared | Zero Projects appear |
231
+ | `switch open <unregistered directory>` | Opened a session; the Project appeared later, as a side effect of some unrelated mutation | Registers that one path, then opens it exactly as before |
232
+
233
+ If you relied on the old behavior to populate the Registry, register the roots
234
+ you actually want once:
235
+
236
+ ```sh
237
+ for root in ~/src/*; do projmux create project --root "$root"; done
238
+ projmux get projects
239
+ ```
240
+
241
+ ### Pins are typed, and migrate on request
242
+
243
+ **Breaking.** `~/.config/projmux/pins` held one absolute path per line, which
244
+ could not say whether the path was a Project. It is now a typed envelope:
245
+
246
+ ```text
247
+ projmux-pins v2
248
+ project proj-kwo4qozry2sr2ycij2g45zyvam
249
+ candidate /home/dev/src/scratch
250
+ ```
251
+
252
+ A `project` pin references a Registry Project uid; its displayed root and name
253
+ are projected from the Registry on every read, so the pin survives a rebind, a
254
+ rename, and a missing root. A `candidate` pin references a path that no Project
255
+ claims and stays a path.
256
+
257
+ Nothing migrates behind your back. Reads project a pre-v2 file in memory, so the
258
+ sidebar looks the same before and after; the file is rewritten only when you ask:
259
+
260
+ ```sh
261
+ projmux pin project migrate --dry-run # report only
262
+ projmux pin project migrate # store the typed form
263
+ ```
264
+
265
+ Per line, migration has exactly three outcomes:
266
+
267
+ | Registry Projects claiming the path | Result |
268
+ | --- | --- |
269
+ | exactly one | becomes that Project's `project <uid>` pin |
270
+ | none | stays a `candidate <path>` pin |
271
+ | two or more | the **entire** migration is refused; the pin file and the Registry keep their bytes, and the message names the repair |
272
+
273
+ Repair an ambiguous pin with `projmux rebind project` so one Project claims the
274
+ path, or pin the Project you meant directly with `projmux pin project add
275
+ uid:<uid>`, then re-run the migration. A corrupt file, or one written by a newer
276
+ projmux, is refused rather than partially read.
277
+
278
+ `pin project add|remove|toggle <dir>` is unchanged in argv and now resolves to a
279
+ typed pin under the same rule (one Project → managed, none → candidate, more than
280
+ one → refused). `pin project list` gained a kind column and a `--kind
281
+ project|candidate` filter; the first mutation after an upgrade migrates the file
282
+ first, so it can refuse for the reasons above.
283
+
284
+ Settings > Projects shows the three collections separately: **Additional
285
+ discovery roots** (scan roots), **Pinned Projects** (managed, by uid, with rebind
286
+ and unpin), and **Candidate Pins** (unregistered paths, with register and unpin).
287
+ On Windows the discovery roots stay OS-native paths; drive-letter, case, and
288
+ separator differences are folded only when matching a candidate or migrating a
289
+ legacy pin, never to mint or merge a Project uid.
290
+
47
291
  ### Legacy compatibility routes removed
48
292
 
49
293
  This release removes the human-facing compatibility argv and old
50
294
  pre-namespace internal aliases listed in the [retirement ledger](legacy-cli-retirement.md).
51
- Removed public argv exits 2 with its exact canonical replacement, no stdout,
52
- and no side effect. Removed internal aliases (`tmux`, `status`, `statusbar`,
53
- `preview`, `session-popup`, `key-broker`, and `popup-wait-key`) are unknown
54
- top-level commands and exit 1. Use `internal ...` for generated plumbing and
55
- `config render|apply` for public configuration work.
295
+ Rejected compatibility argv below a surviving mixed root exits 2 with its exact
296
+ canonical replacement, no stdout, and no side effect. Fully removed human roots
297
+ (`current`, `kill`, `notify`, `sessions`, `session-state`, `tag`, `upgrade`, and
298
+ `usage`) and removed internal aliases (`tmux`, `status`, `statusbar`, `preview`,
299
+ `session-popup`, `key-broker`, and `popup-wait-key`) are unknown top-level
300
+ commands and exit 1. Use `internal ...` for generated plumbing and `config
301
+ render|apply` for public configuration work.
56
302
 
57
303
  The mixed roots retain only `attach project`, `focus project|window|pane`, `pin
58
304
  project`, and `prune project|snapshot`. Shortcuts and singular/plural resource
@@ -300,10 +546,17 @@ npm install -g projmux
300
546
  For npm-managed installs, `projmux update apply` runs:
301
547
 
302
548
  ```sh
549
+ <current-projmux> config apply --bin <published-projmux> --socket projmux
303
550
  npm install -g projmux@latest
304
551
  projmux config apply
305
552
  ```
306
553
 
554
+ The first apply must succeed before npm publication begins. The final apply is
555
+ post-publication verification by the new binary. Go, GitHub Release, and source
556
+ `make install` use the same pre-converge → publish → verify contract. A failed
557
+ pre-apply prevents publication; a publication or verification failure returns
558
+ non-zero and prints the exact `projmux config apply --socket projmux` recovery.
559
+
307
560
  `npm install -g projmux@latest` is used instead of `npm update -g projmux`
308
561
  because `npm update -g` honors the installed semver range and frequently
309
562
  refuses to move a global install across a newer minor/major release, leaving it
@@ -326,9 +579,10 @@ projmux update apply --no-apply # migrate + write config, skip the live reload
326
579
  projmux update apply --dry-run # print the steps only
327
580
  ```
328
581
 
329
- `projmux update apply` reinstalls via `go install`, atomically replaces the active
330
- file, and reapplies the live tmux config so a running `-L projmux` server picks
331
- up new bindings without a restart.
582
+ `projmux update apply` first converges the live route with the current binary
583
+ and exact eventual executable path, reinstalls via `go install`, then reapplies
584
+ with the published binary so a running `-L projmux` server picks up new bindings
585
+ without a restart.
332
586
 
333
587
  The command reads `PROJMUX_PROJDIR` from the calling shell and memoizes the
334
588
  primary path to `~/.config/projmux/projdir`, so the new binary keeps the same
@@ -347,8 +601,9 @@ PROJMUX_PROJDIR="/main/repos:/secondary/repos" projmux update apply
347
601
 
348
602
  When `PROJMUX_INSTALLER=github-release`, `projmux update apply` downloads the
349
603
  latest matching `projmux_<version>_<goos>_<goarch>.tar.gz` asset from GitHub
350
- Releases, extracts the binary, atomically replaces the current executable, and
351
- then runs the new binary's `projmux config apply` — or `config apply --no-reload`
604
+ Releases, extracts the binary, converges the exact live route, atomically
605
+ replaces the current executable, and then runs the new binary's
606
+ `projmux config apply` — or `config apply --no-reload`
352
607
  when `--no-apply` is set, so the keymap schema migration still happens.
353
608
 
354
609
  Set the installer explicitly if you manage a release binary outside npm or Go:
@@ -75,7 +75,31 @@ floor.
75
75
 
76
76
  ### Codex (`internal/core/usage/adapters/codex`)
77
77
 
78
- Local rollout JSONL parser. No network calls.
78
+ The native Codex app-server account API is primary:
79
+
80
+ - An explicit `projmux agent usage` request reuses the app-server
81
+ ensure-ready lifecycle, then opens one initialized official stdio-proxy
82
+ connection and calls `account/rateLimits/read` with a bounded deadline.
83
+ Automatic HUD refreshes probe/read an already-running daemon but do not gain
84
+ daemon-start authority.
85
+ - When `rateLimitsByLimitId` is present it is the authoritative multi-bucket
86
+ view; the backward-compatible `rateLimits` mirror is not duplicated. Each
87
+ bucket preserves its map key, nullable `limitId`, nullable `limitName`,
88
+ primary/secondary slot, percentage, nullable reset, and nullable cadence.
89
+ `300` and `10080` minutes project to `5h` and `weekly`. Missing or unknown
90
+ positive cadences remain lossless `quota` rows instead of disappearing.
91
+ - `account/rateLimits/updated` is merged sparsely into the just-read native
92
+ snapshot during one bounded event-settle window. Null account metadata does
93
+ not clear a prior label or identity. The event-refreshed rows then use the
94
+ same Manager throttle, replace, store, and last-known-good path as a read.
95
+ - Validation is row-level. A malformed bucket/window is dropped with a bounded,
96
+ field-only warning while valid siblings remain native. Native and rollout
97
+ rows are never combined in one invocation.
98
+
99
+ When app-server is unavailable, unsupported, disconnected, timed out, or the
100
+ account exposes no supported rate-limit bucket (including API-key-style
101
+ accounts), the adapter invokes the existing newest-rollout collector exactly
102
+ once without attempting login, logout, token refresh, or config writes:
79
103
 
80
104
  - Walks `${HOME}/.codex/sessions/<YYYY>/<MM>/<DD>/rollout-*.jsonl`
81
105
  newest-first by mtime (NOT filename — Dropbox-synced rollouts can
@@ -99,8 +123,10 @@ Local rollout JSONL parser. No network calls.
99
123
  dropped and reported rather than projected as a genuine `0%`.
100
124
 
101
125
  Codex shares the manager's default `30s` throttle (no
102
- `ThrottleHinter`). It does not implement `BackoffStater` — local-only
103
- read.
126
+ `ThrottleHinter`) and does not implement `BackoffStater`. A successful
127
+ rollout fallback row records `source=rollout` plus the closed fallback reason.
128
+ If neither lane produces rows, Manager preserves the previous source/value and
129
+ adds a closed `stale_reason` to the last-known-good row.
104
130
 
105
131
  ### Antigravity (`internal/core/usage/adapters/antigravity`)
106
132
 
@@ -163,13 +189,17 @@ For `--model all`, calls `Manager.Collect` (or `ForceCollect` with
163
189
  Enabled agents, filters by window, and renders the tab-aligned table:
164
190
 
165
191
  ```
166
- MODEL WINDOW PCT RESETS_AT RESET_IN STALE
192
+ MODEL WINDOW PCT RESETS_AT RESET_IN STALE SOURCE REASON
193
+ codex 5h/codex · General 12% 2026-05-07T14:00:00+09:00 - app-server
167
194
  claude 5h 80% 2026-05-07T14:00:00+09:00 -
168
195
  antigravity quota/gemini-weekly 6% 2026-07-06T16:50:32+09:00 560580s
169
196
  claude quota/group-redacted · Model Redacted Alpha 38% 2031-02-03T15:05:06+09:00 - *
170
197
  ```
171
198
 
172
- `STALE` is `*` when `now - UpdatedAt > 10m`. A `--json` payload returns
199
+ `SOURCE`/`REASON` are included when source-aware rows are present. Native
200
+ Codex rows report `app-server`; fresh rollout fallback and retained
201
+ last-known-good rows report their closed fallback/stale reasons. `STALE` is
202
+ `*` when `now - UpdatedAt > 10m`. A `--json` payload returns
173
203
  the `Snapshot` array; if any adapter is in backoff, the wrapper
174
204
  `{snapshots, backoff: {model: {until, consecutive}}}` is emitted
175
205
  instead. A backoff note is appended to the human table:
@@ -215,6 +245,17 @@ excluded; only its aggregate official `5h` and `weekly` rows reach the HUD.
215
245
  The Settings provider list consumes `aiprovider.UsageSupported()` order, but a
216
246
  window toggle exists only when this same projection seam declares it. A future
217
247
  provider or an opaque bucket cannot manufacture a window row.
248
+ For native Codex multi-bucket rows, the exact `codex` bucket wins the HUD
249
+ projection, then the legacy empty bucket, then lexical bucket order. The HUD
250
+ compact identity is derived from that same row: a healthy authoritative
251
+ `app-server` row is simply `Codex`, a fresh rollout row is
252
+ `Codex [fallback]`, and a retained last-known-good row is `Codex [stale]`.
253
+ Blank, malformed, or future non-stale provenance also fails conservatively to
254
+ the existing `[fallback]` identity rather than looking native or expanding the
255
+ compact vocabulary. The exact raw source and closed fallback/stale reason stay
256
+ in `agent usage --model codex` table/JSON output and in
257
+ `projmux diagnostics log --component usage`; compact labels never replace
258
+ those fields.
218
259
 
219
260
  Settings > Appearance > Status Bar > Agent Usage HUD can hide the whole HUD,
220
261
  a provider, or one supported window. Parent off states preserve child saved
@@ -250,8 +291,10 @@ The `~` / `~~` markers are the legacy stale vocabulary, carried inside the
250
291
  indicator while the age text is still rendered and glued to the label once the
251
292
  drop order has shed it. Staleness stays muted however far the segment has
252
293
  degraded: warning and critical colors are reserved for usage thresholds, not
253
- cache age. Codex opts out of the indicator because the rollout file is always
254
- near-current (no throttle gap to report).
294
+ cache age. A healthy Codex row does not need a cosmetic age indicator, while a
295
+ retained Codex last-known-good row opts in and carries the compact `[stale]`
296
+ identity. Its exact closed stale reason remains available on the full table,
297
+ JSON, and diagnostics surfaces.
255
298
 
256
299
  The statusbar usage **popup**'s sync line is a different surface with a
257
300
  different meaning (last successful collect, 60s amber threshold) and is
@@ -284,10 +327,13 @@ When a collection fails, the failure is visible in three places:
284
327
  projmux diagnostics log --component usage --tail 20
285
328
  ```
286
329
 
287
- The row carries the provider and a closed failure enum and nothing else:
330
+ The row carries the provider and closed source/failure enums and nothing else:
288
331
  `collect-failed` (whole-adapter failure, `level=error`) or `rows-skipped`
289
- (partial failure, `level=info`). A successful collection writes no row at
290
- all. Identical `(provider, failure)` tuples are recorded at most once per
332
+ (partial failure, `level=info`). Codex rollout fallback records
333
+ `source=rollout` plus its closed fallback reason; retained data records
334
+ `source=last-known-good` plus its closed stale reason. A healthy native
335
+ collection writes no row at all. Identical `(provider, source, failure)`
336
+ tuples are recorded at most once per
291
337
  process run — the same suppression the notify/focus recorder uses — so a
292
338
  repeating failure cannot flood the bounded journal. Journal writes are
293
339
  best-effort and never change what the usage command returns.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projmux",
3
- "version": "0.12.2",
3
+ "version": "0.13.1",
4
4
  "description": "tmux project session manager",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/crevissepartners/projmux#readme",
@@ -28,9 +28,9 @@
28
28
  "package:npm:pack": "scripts/package-npm.sh --pack"
29
29
  },
30
30
  "optionalDependencies": {
31
- "@projmux/linux-x64": "0.12.2",
32
- "@projmux/linux-arm64": "0.12.2",
33
- "@projmux/darwin-x64": "0.12.2",
34
- "@projmux/darwin-arm64": "0.12.2"
31
+ "@projmux/linux-x64": "0.13.1",
32
+ "@projmux/linux-arm64": "0.13.1",
33
+ "@projmux/darwin-x64": "0.13.1",
34
+ "@projmux/darwin-arm64": "0.13.1"
35
35
  }
36
36
  }