projmux 0.13.0 → 0.14.0

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,175 @@
1
+ # Codex Native-Required Create Migration (0.14.0)
2
+
3
+ 0.14.0 makes native authority a requirement for a *prompted* managed Codex
4
+ create instead of something projmux attempts and silently gives up on. Where an
5
+ earlier release would quietly hand back a plain-CLI Codex Agent — one that looks
6
+ managed, carries no app-server thread binding, and answers no native turn
7
+ control — the create now refuses at the provider-mutation boundary and names the
8
+ one explicit way to ask for that plain Agent.
9
+
10
+ This note covers only that change. For update mechanics by installer type see
11
+ [Upgrading](upgrading.md); for endpoint readiness diagnosis see
12
+ [Troubleshooting](troubleshooting.md#codex-app-server-install-topology).
13
+
14
+ ## There is nothing to migrate
15
+
16
+ Upgrading is the whole migration. Every existing resource keeps working as it
17
+ is:
18
+
19
+ | Surface | Change in 0.14.0 |
20
+ | --- | --- |
21
+ | Registry schema | none — `internal/core/metadata` is byte-identical across the release range |
22
+ | Existing Agents and Panes | none — no stored field is added, read differently, or backfilled |
23
+ | Configuration files | none — no key added, renamed, or removed |
24
+ | Post-create and Agent hook contract | none — no `PROJMUX_*` env var added, renamed, or removed, and no payload schema change |
25
+
26
+ No command has to be run before or after the upgrade for this change. What
27
+ changes is the answer a *new* prompted Codex create gives when native authority
28
+ cannot be proven.
29
+
30
+ ## The shape that is now gated
31
+
32
+ Exactly one create shape is native-required. All five conditions must hold:
33
+
34
+ - provider is `codex`, and
35
+ - the payload is exactly one operand, and
36
+ - that operand is non-empty, and
37
+ - `--interactive-only` is not passed, and
38
+ - the create is not carrying a capability selection from the split-UI picker.
39
+
40
+ Anything outside that shape keeps its previous behavior unchanged. The closed
41
+ outcome table lives in `internal/app/codex_native_thread.go`
42
+ (`codexNativeLaunchOutcomeTable`) and is pinned by
43
+ `TestCodexNativeLaunchOutcomeTableIsClosed`.
44
+
45
+ ## Calls that now refuse
46
+
47
+ ### 1. Prompted create against an endpoint that is not ready or not attachable
48
+
49
+ ```sh
50
+ projmux create codex -- "review the diff"
51
+ projmux create agent --provider codex -- "review the diff"
52
+ ```
53
+
54
+ Before: native thread preparation failed as a safe fallback and the create
55
+ silently continued on the plain CLI lane, producing a managed Agent with no
56
+ native binding.
57
+
58
+ Now: the create refuses before the split, before the hook probe, and before the
59
+ Registry commit. Zero threads, zero Panes, zero Registry writes, zero tmux
60
+ objects. The refusal carries the typed reason from the endpoint (for example
61
+ `daemon-not-running`) and names `--interactive-only`. Exit code 1.
62
+
63
+ Fix it by making the app-server endpoint available — start with
64
+ `projmux doctor --section integrations --verbose` — or ask for the plain lane on
65
+ purpose with `--interactive-only`.
66
+
67
+ ### 2. Prompted create with `--add-dir` against an endpoint that cannot negotiate roots
68
+
69
+ ```sh
70
+ projmux create codex --add-dir /path/to/other-root -- "review the diff"
71
+ ```
72
+
73
+ Additional writable roots travel on the upstream experimental API. Before, the
74
+ create connection never negotiated that capability, so roots always failed the
75
+ request; that failure was classified as a safe fallback and the create silently
76
+ dropped to the plain CLI lane.
77
+
78
+ Now a rooted create negotiates the capability on its own connection and delivers
79
+ the exact cleaned list. An endpoint that cannot answer the negotiated form
80
+ fails closed with reason `additional-writable-roots-unsupported` rather than
81
+ creating an Agent whose writable workspace is narrower than what was asked for.
82
+ Exit code 1.
83
+
84
+ A create with no `--add-dir` keeps the plain, non-negotiated connection exactly
85
+ as before.
86
+
87
+ ### 3. Prompted create whose selector resolves several Windows
88
+
89
+ ```sh
90
+ projmux create codex --window main --window side -- "review the diff"
91
+ ```
92
+
93
+ One create owns exactly one native thread, and a Registry rollback cannot delete
94
+ an app-server thread, so a prompted native fan-out has no atomic shape. Before,
95
+ every target dropped to the plain CLI lane. Now the fan-out is refused before
96
+ the first allocation, as a usage error (exit code 2) naming how many Windows the
97
+ selector resolved.
98
+
99
+ Narrow the selector to one Window, or pass `--interactive-only` to keep the
100
+ previous one-Agent-per-Window cardinality.
101
+
102
+ ### 4. Resume picker: selecting an app-server-sourced Codex row
103
+
104
+ This is the split-UI resume picker (`Alt-7` / the resume selection surface), not
105
+ the `projmux agent resume` command.
106
+
107
+ When the picker's Codex rows come from a healthy app-server, selecting one of
108
+ those native rows resumes that exact thread through the native lane. Before, a
109
+ failed native resume preparation silently rebound the selection onto the rollout
110
+ CLI lane — it reported a resume while answering no native turn control. Now it
111
+ refuses. Exit code 1.
112
+
113
+ There is no `--interactive-only` escape hatch here: the operator picked an
114
+ existing conversation, not a launch mode. Rows that came from the rollout scan
115
+ rather than the app-server are unaffected and keep the current CLI lane.
116
+
117
+ ## The escape hatch: `--interactive-only`
118
+
119
+ `--interactive-only` is the only public spelling that asks for a plain
120
+ interactive Codex Agent with no native thread binding, and it is the only way to
121
+ reach the plain CLI lane on purpose.
122
+
123
+ ```sh
124
+ projmux create codex --interactive-only -- "interactive task"
125
+ projmux create agent --provider codex --interactive-only -- "interactive task"
126
+ ```
127
+
128
+ - Both spellings are equivalent: identical flag acceptance, identical manifest
129
+ and rendered help, byte-equal stdout.
130
+ - The native controller is not consulted at all. No thread is created, no native
131
+ Pane state is bound, and the Agent keeps the existing hook activation contract
132
+ (`provider-hook`).
133
+ - The payload stays the provider's initial task on the CLI argv, exactly as
134
+ before.
135
+ - It gives up native turn control for that Agent — start, steer, interrupt, and
136
+ approval routing through the app-server. That is a deliberate reduced
137
+ capability, not a defect.
138
+ - It is Codex-only. Passing it to `--provider claude` or `--provider antigravity`,
139
+ or to the `create claude` / `create antigravity` shortcuts, is a usage error
140
+ (exit code 2) raised before any transaction opens, so nothing is created.
141
+ - It is a create-time flag and is not stored on the Agent. `projmux agent resume`
142
+ has no way to tell an interactive-only Agent apart later and does not treat one
143
+ specially.
144
+
145
+ ## What did not change
146
+
147
+ | Surface | Behavior |
148
+ | --- | --- |
149
+ | `projmux agent resume` | unchanged. A stored Agent whose native resume cannot be proven keeps its existing safe fallback to one provider resume of the stored conversation. `internal/app/agent_resume.go` has a net diff of zero lines in this release. |
150
+ | Empty-prompt Codex create (`projmux create codex` with no payload) | unchanged byte-for-byte, including argv, hook acknowledgement, output, and late refinement. An empty prompt is not attachable native input, so it was never in the gated shape. |
151
+ | Multi-operand payload (`projmux create codex -- a b`) | unchanged. The legacy CLI owns provider parsing for a multi-operand payload, so it is not a native create candidate and keeps the plain lane. |
152
+ | Claude and Antigravity | unchanged lifecycle, fan-out, and hook activation contract. |
153
+ | Public hook env and payload schema | unchanged. |
154
+ | Post-thread-creation failures | unchanged. A native failure *after* `thread/start` returned still refuses without offering any second lane — including `--interactive-only` — because starting another Codex process could submit the same prompt twice. |
155
+
156
+ ## Verifying this yourself
157
+
158
+ ```sh
159
+ go test ./internal/app/ -run 'TestInteractiveOnlyIsTheOnlyPlainCodexLaneAndBothSpellingsAreEquivalent|TestDefaultNativeCodexFanOutRefusesWithZeroMutationsAndInteractiveOnlyKeepsCardinality|TestEmptyPromptCodexCreateIsByteForByteUnchangedByTheNativeRequiredGate|TestClaudeAndAntigravityLifecycleAndHookContractAreUnchangedByTheNativeGate|TestUnavailableNative|TestCodexNativeLaunchOutcomeTableIsClosed'
160
+ go test ./internal/integrations/agents/codexappserver/ -run TestStartDefaultThread
161
+ ```
162
+
163
+ | Claim in this note | Test |
164
+ | --- | --- |
165
+ | Prompted create refuses instead of silently creating a plain Agent, for both the unavailable-endpoint and unsupported-roots rows | `TestUnavailableNativeCreateRefusesInsteadOfSilentlyCreatingAPlainAgent` |
166
+ | Roots are delivered exactly on a negotiated connection, fail closed otherwise, and stay off the wire when empty | `TestStartDefaultThreadDeliversAdditionalRootsOrFailsClosed` |
167
+ | Prompted fan-out refuses with zero mutations; `--interactive-only` keeps the old cardinality | `TestDefaultNativeCodexFanOutRefusesWithZeroMutationsAndInteractiveOnlyKeepsCardinality` |
168
+ | Picker resume refuses instead of rebinding onto the rollout lane, and offers no launch-mode escape hatch | `TestUnavailableNativePickerResumeRefusesInsteadOfRebindingOntoTheRolloutLane` |
169
+ | `agent resume` keeps its safe fallback to one provider resume | `TestUnavailableNativeResumeKeepsTheStoredConversationOnTheProviderResumeLane` |
170
+ | Both `--interactive-only` spellings are equivalent, and non-Codex providers refuse it at zero transactions | `TestInteractiveOnlyIsTheOnlyPlainCodexLaneAndBothSpellingsAreEquivalent` |
171
+ | Empty-prompt create is unchanged | `TestEmptyPromptCodexCreateIsByteForByteUnchangedByTheNativeRequiredGate` |
172
+ | Claude and Antigravity are unchanged | `TestClaudeAndAntigravityLifecycleAndHookContractAreUnchangedByTheNativeGate` |
173
+ | One payload sends exactly one `turn/start` and never repeats the prompt in Pane argv | `TestPromptedNativeCodexCreateIssuesOneTurnAndNeverRepeatsThePromptInPaneArgv` |
174
+ | The post-mutation row still refuses a second lane | `TestIndeterminateNativeCreateRefusesASecondLaneAndWritesZero` |
175
+ | The outcome table describes exactly these rows and no others | `TestCodexNativeLaunchOutcomeTableIsClosed` |
@@ -422,26 +422,40 @@ user/global preference in this release.
422
422
  ## Codex app-server health
423
423
 
424
424
  Settings > AI includes a read-only `Codex control plane` row. It reports one of
425
- `App Server`, `Hook fallback`, or `Unavailable`, together with a closed reason,
426
- endpoint kind, connection state, lifecycle outcome/reason, and a sanitized
427
- version when initialization returned one. Read-only surfaces report
428
- `not-attempted/read-only`. `projmux doctor --section integrations` and explicit
429
- support reports expose the same bounded fields without socket paths, prompts,
430
- tokens, process output, or response payloads.
425
+ `App Server`, `Hook fallback`, or `Unavailable`, together with the existing
426
+ effective reason. Endpoint readiness, running executable/version, official
427
+ daemon-manager ownership, and remote-control capability are separate closed
428
+ axes. Separate `probe_reason` and `install_capability` fields preserve the
429
+ app-server root cause and bounded PATH/managed-payload topology. Lifecycle
430
+ outcome/reason and sanitized CLI, managed, and running versions remain
431
+ independent. Read-only surfaces report `not-attempted/read-only`.
432
+ `projmux doctor --section integrations` and explicit support reports expose the
433
+ same bounded fields without executable/socket paths, prompts, tokens, process
434
+ output, or response payloads.
431
435
 
432
436
  There is no app-server source setting or environment override. Authority is a
433
437
  capability result, not a preference: Projmux probes the existing local control
434
- socket through `codex app-server proxy` with a short timeout and otherwise keeps
435
- the current hook behavior. Doctor, Settings, and support reports never start or
436
- otherwise mutate the daemon.
438
+ socket through `codex app-server proxy`, reads official manager evidence through
439
+ `codex app-server daemon version`, and reads remote-control state through
440
+ `remoteControl/status/read`, all with short timeouts. An older endpoint that
441
+ does not expose the last method reports `unsupported` on that axis without
442
+ hiding endpoint readiness. Doctor, Settings, and support reports never start or
443
+ otherwise mutate the daemon, configuration, login state, or control socket.
444
+
445
+ An `external-cli-only` install capability acknowledges that the ordinary CLI
446
+ exists while the canonical managed daemon payload was not observed. It does
447
+ not identify a package manager. Install topology is not manager ownership:
448
+ only the official daemon response's backend field proves a managed process.
437
449
 
438
450
  The Codex integration has a lifecycle seam for later native features. Only an
439
- actual native user action may use it, and only an exact missing or
440
- connection-refused default control socket is eligible. That path invokes the
451
+ actual native user action may use it. A ready unmanaged, version-skewed, or
452
+ ownership/version-unknown endpoint is refused without mutation and reports the
453
+ shared-client interruption risk plus bounded operator recovery. Only an exact
454
+ missing or connection-refused default control socket is start-eligible. That path invokes the
441
455
  official idempotent `codex app-server daemon start` command at most once per
442
456
  in-flight process decision, then retries proxy initialization with a bounded
443
- backoff. Phase 1 itself does not route Agent create/resume, usage, catalog,
444
- model, or review behavior through that seam.
457
+ backoff. Projmux never automatically stops, kills, restarts, adopts, or enables
458
+ remote control on the shared app server.
445
459
 
446
460
  The default `Codex` row in the provider picker launches immediately through the
447
461
  canonical create route. It does not start or probe the app-server, call
@@ -922,6 +936,15 @@ that target subtree atomically, runs the ordinary materializer, and performs an
922
936
  explicit client handoff last when `--client` is present. Restore never modifies
923
937
  or deletes the source snapshot.
924
938
 
939
+ Interactive `projmux quit` also offers `Save Project snapshots and quit`. It
940
+ recaptures the latest snapshot for every live Registry-bound Project on the
941
+ exact app server, regardless of the global or Project auto-save toggle, and
942
+ stops the server only after all captures succeed. A partial failure keeps the
943
+ server running and keeps each successful atomic snapshot for inspection or
944
+ retry. Control/Home, ephemeral, unmanaged, conflicted, and sibling-server
945
+ sessions are never promoted into Project snapshots. `Quit without saving`,
946
+ `quit --yes`, and `quit --force` perform no snapshot inventory or store I/O.
947
+
925
948
  ## Decoration Mode
926
949
 
927
950
  Settings > Appearance controls optional icon decoration per surface:
package/docs/hooks.md CHANGED
@@ -328,10 +328,31 @@ command = "projmux internal agent-hook ingest codex-hook >/dev/null 2>&1 || true
328
328
  ```
329
329
 
330
330
  Repeated installs are idempotent and preserve unrelated Codex config, including
331
- unmanaged hook entries for the same events. If projmux sees an unmanaged
332
- `projmux internal agent-hook ingest codex-hook` command, it refuses to install over it rather
333
- than guessing ownership. `--dry-run` previews the TOML update. `--remove`
334
- removes projmux-managed Codex hooks wiring.
331
+ unmanaged hook entries for the same events. If projmux sees a
332
+ `projmux internal agent-hook ingest codex-hook` command it did not author —
333
+ a different matcher, a wrapped command, or an extra key — it refuses to install
334
+ over it rather than guessing ownership. `--dry-run` previews the TOML update.
335
+ `--remove` removes projmux-managed Codex hooks wiring.
336
+
337
+ The marker block is not a projmux-owned byte range. Projmux owns only the hook
338
+ definitions it wrote and, inside the block, the `[features] hooks = true`
339
+ toggle; everything else in the file belongs to Codex or to you and is handed
340
+ back verbatim. That split is what keeps hook trust alive across convergence:
341
+ Codex records the hooks you approved as `[hooks.state]` subtables holding a
342
+ `trusted_hash`, and it can write them anywhere in the same file, including
343
+ between the projmux markers. Convergence lifts that state back out of the block
344
+ unchanged instead of rewriting the block wholesale, so `projmux agent integrate
345
+ codex`, `projmux config apply`, and the `make install` convergence leave every
346
+ approval you already made in place.
347
+
348
+ A Codex TOML re-serialization can also drop the marker comments while leaving
349
+ the hook wiring projmux wrote behind. Projmux recognizes that wiring as its own
350
+ and restores the markers around the byte-identical definitions rather than
351
+ refusing, so a marker-less config still converges and keeps its trust. Recovery
352
+ stops where ownership does: hook entries you hand-wrote keep the refusal above,
353
+ and so does any layout where re-adopting the projmux entries would renumber a
354
+ hook entry projmux does not own, because Codex keys trust state by array
355
+ position.
335
356
 
336
357
  The Codex hooks install list is catalog-driven. Projmux ships an embedded
337
358
  default catalog at `internal/app/ai_hook_catalogs/codex.json`, and merges an
@@ -392,7 +413,11 @@ not stored.
392
413
 
393
414
  Codex may require reviewing or trusting hooks through its `/hooks` flow before
394
415
  commands run. Projmux only writes the managed config block; it does not attempt
395
- to auto-trust hooks.
416
+ to auto-trust hooks. It never computes, copies, moves, or deletes a
417
+ `trusted_hash`, and it never carries an approval from one hook identity to
418
+ another: if a hook's matcher, type, command, or event changes, Codex asks you to
419
+ review that hook again while the other hooks keep the trust you already gave
420
+ them.
396
421
 
397
422
  ## Tmux Bell Fallback
398
423
 
package/docs/install.md CHANGED
@@ -21,6 +21,11 @@ projmux doctor
21
21
  `doctor` performs read-only diagnostics for runtime tools such as `tmux`,
22
22
  `git`, and `stty`.
23
23
 
24
+ If the npm shim reports `unsupported or incomplete npm install`, or a live
25
+ pre-0.13 app server is missing its logical socket marker, use the canonical
26
+ [Troubleshooting](troubleshooting.md) diagnosis and recovery steps. Doctor is
27
+ read-only; its displayed remediation is never executed automatically.
28
+
24
29
  Provider integrations are opt-in and use the canonical installer spelling:
25
30
 
26
31
  ```sh
@@ -100,10 +105,15 @@ cd projmux
100
105
  make install
101
106
  ```
102
107
 
103
- `make install` builds the binary, atomically replaces
104
- `$(go env GOPATH)/bin/projmux`, runs `projmux config apply`, and reconciles the
105
- notify queue through `projmux notification reconcile`. Override the destination
106
- with `INSTALL_DIR=/usr/local/bin`.
108
+ `make install` builds the candidate, uses that candidate to run
109
+ `config apply --bin <install-target>` before publication, atomically replaces
110
+ `$(go env GOPATH)/bin/projmux`, and runs the installed binary's `config apply`
111
+ again as post-publication verification. This ordering migrates a pre-0.13 live
112
+ server's exact logical socket marker before a consumer that requires it becomes
113
+ reachable. A convergence or publication failure is non-zero and prints the
114
+ exact `projmux config apply --socket <name>` recovery. Successful installs then
115
+ reconcile the notify queue through `projmux notification reconcile`. Override
116
+ the destination with `INSTALL_DIR=/usr/local/bin`.
107
117
 
108
118
  Update source checkouts with the repository workflow:
109
119
 
@@ -121,11 +131,14 @@ marked as release-managed:
121
131
  export PROJMUX_INSTALLER=github-release
122
132
  ```
123
133
 
124
- With that set, `projmux update apply` downloads the latest matching release
125
- asset, replaces the current executable, and reapplies the live tmux config.
134
+ With that set, `projmux update apply` downloads and verifies the latest matching
135
+ release asset, converges the exact live route before replacement, atomically
136
+ replaces the current executable, and reapplies the live tmux config as
137
+ post-publication verification.
126
138
  `--no-apply` skips the live reload only — the new binary still migrates the
127
139
  keymap schema and marker-owned provider files, then writes the generated
128
- config. It does not touch a live tmux bell hook. See
140
+ config. It prints the exact explicit apply required before ordinary mutation
141
+ and does not touch a live tmux bell hook. See
129
142
  [Upgrading](upgrading.md#managed-agent-hook-producer-migration).
130
143
 
131
144
  ## npm Packaging Details
@@ -133,3 +146,6 @@ config. It does not touch a live tmux bell hook. See
133
146
  Repository packaging and publish details are maintained in
134
147
  [npm Distribution](npm-distribution.md). That document is for maintainers; end
135
148
  users should not need it for installation.
149
+
150
+ For incomplete optional dependencies, Doctor findings, and live app socket
151
+ marker recovery, see [Troubleshooting](troubleshooting.md).
@@ -406,7 +406,9 @@ installed yet, and only it knows its own canonical ids.
406
406
 
407
407
  `--no-apply` suppresses the live tmux reload, not the migration. Installer paths
408
408
  still invoke the new binary as `config apply --no-reload` so the schema does not
409
- fall behind the binary that writes it.
409
+ fall behind the binary that writes it. They omit pre-publication live
410
+ convergence entirely and print the exact explicit `config apply --socket`
411
+ still required before ordinary mutation.
410
412
 
411
413
  ### Downgrading or rolling back
412
414
 
@@ -96,6 +96,12 @@ scripts/package-npm.sh \
96
96
  This keeps the npm platform binaries byte-for-byte aligned with the GitHub
97
97
  Release binaries, including the `darwin && cgo` native key adapter, then
98
98
  publishes each staged package with `npm publish --access public`.
99
+
100
+ The GitHub release itself stays a draft until that npm job succeeds. release-please
101
+ creates the release with `draft` set, `release.yml` uploads archives to the drafted
102
+ release, and only the final `publish-release` job flips it visible. So by the time a
103
+ user can see release `vX.Y.Z`, npm `dist-tags.latest` already resolves to `X.Y.Z`;
104
+ a failed npm publish keeps the release hidden and the workflow red.
99
105
  The npm publish job uses GitHub Actions OIDC (`id-token: write`) instead of a
100
106
  long-lived `NPM_TOKEN` secret. PR CI runs `make npm-pack` so package staging and
101
107
  dry-run packing fail before release.
@@ -202,9 +202,10 @@ Its captured output is capped at 4 KiB. Doctor reads only a pre-existing
202
202
  regular generated config (at most 1 MiB) without following symlinks, and the
203
203
  shared read-only journal seam rejects non-regular inputs and files above 5 MiB.
204
204
  These conditions degrade to typed findings rather than blocking or repairing
205
- the source. Windows ACL privacy is reported as unverified because `os.FileMode`
206
- cannot prove it; a separate finding preserves the metadata-only writability
207
- result, and Doctor does not modify ACLs.
205
+ the source. The `privacy-unverified` finding remains in the schema for a path
206
+ whose privacy `os.FileMode` cannot prove, but no supported platform emits it:
207
+ Linux and macOS are the only build targets and POSIX mode bits are
208
+ authoritative on both. Doctor does not modify permissions.
208
209
 
209
210
  ### Registry materialization invariant audit
210
211
 
@@ -266,11 +267,9 @@ releases ownership when a process exits, so an orphaned lock path needs no
266
267
  path deletion or stale-owner reclamation and cannot race a successor owner.
267
268
  Lock acquisition has an explicit 200 ms total budget so this side channel
268
269
  cannot materially delay the original command result. When the file exceeds
269
- 5 MiB, a platform-specific atomic replacement retains approximately the
270
- newest 2 MiB, beginning at a complete valid record; Windows uses replace-
271
- existing semantics rather than plain rename. A trailing partial record is
272
- discarded before the next append, and the reader skips malformed or truncated
273
- records.
270
+ 5 MiB, an atomic `rename` replacement retains approximately the newest 2 MiB,
271
+ beginning at a complete valid record. A trailing partial record is discarded
272
+ before the next append, and the reader skips malformed or truncated records.
274
273
 
275
274
  Classification is intentionally conservative for mutation-capable interactive
276
275
  commands: opening session/project/settings/popup flows is treated as changing
package/docs/testing.md CHANGED
@@ -15,18 +15,39 @@ and humans run the same entrypoints.
15
15
  `tmux` server, and notify queue CRUD.
16
16
  - `make test-install-smoke` builds the same Docker image and runs
17
17
  `test/install/smoke.sh`. It validates `make install`, atomic binary
18
- replacement into an isolated install dir, `tmux apply`, and post-install
19
- `notify reconcile` initialization with a fresh HOME/XDG state tree.
18
+ replacement into an isolated install dir, pre-publication marker convergence,
19
+ concurrent legacy/candidate/installed shell and attach consumers, exact
20
+ server-generation/session preservation, `tmux apply`, and post-install notify
21
+ reconcile initialization with a fresh HOME/XDG state tree.
20
22
  - `make test-e2e` prepares one attempt-local immutable product binary, then
21
23
  runs four isolated Linux real-tmux fixtures plus the Codex lifecycle and npm
22
24
  staging fixtures. The required inventory is `L01`-`L19`, `C01`, and `N01`;
23
25
  every fixture has its own HOME/XDG/tmux/socket/evidence roots and every
24
26
  consumer records the same binary SHA. `E2E_SCENARIO=<ID>` selects one exact
25
27
  stable scenario for replay.
28
+ - Three mutually exclusive selectors narrow one invocation. `E2E_SCENARIO=<ID>`
29
+ replays one scenario, `PROJMUX_E2E_LINUX_SHARD=<shard>` runs exactly one
30
+ Linux fixture with the terminal inventory its `linux-shards.tsv` row owns,
31
+ and `PROJMUX_E2E_SUITE=codex-lifecycle|npm-staging` runs one non-Linux suite.
32
+ Setting none keeps the default: four Linux shards in parallel plus both
33
+ suites. CI uses the shard/suite selectors to give every suite its own runner,
34
+ so container isolation and schedule isolation are the same unit there; local
35
+ `make test-e2e` still runs the whole matrix on one machine.
36
+ - Every scenario wait states a budget in seconds rather than a loop count, and
37
+ `E2E_WAIT_SCALE=<factor>` multiplies all of them at once. Raise it when a
38
+ runner is slow or loaded; a wait that expires still fails with the description
39
+ of what it was waiting for, so a slow machine reports a timeout rather than
40
+ the regression message of the assertion that would have run next.
26
41
  - `make test-e2e-contract`, `make test-e2e-reliability`, and
27
42
  `make test-e2e-shards` validate typed attempt evidence, bounded semantic
28
43
  waits/owned cleanup, and exhaustive four-shard isolation without rerunning
29
- the full product matrix.
44
+ the full product matrix. The shard target also pins the CI job list to the
45
+ manifest: one non-fail-fast job per shard and per suite, each with its own
46
+ runner, its own timeout and its own uniquely named evidence artifacts, all of
47
+ them required children of the aggregate `Test` gate. It also pins the thin
48
+ `E2E Tests` job, which exists because the branch ruleset requires a status
49
+ check under that exact name; a required context that is never reported stays
50
+ pending rather than failing, so dropping that job would deadlock merges.
30
51
  - `make test-e2e-coverage` validates
31
52
  `test/e2e/ags-oedr-manifest.json`: executable scenario markers and shard
32
53
  assignments must match all 21 rows with orphan count zero. A matrix may move
@@ -41,10 +62,15 @@ and humans run the same entrypoints.
41
62
  checks scanner/rule/baseline identity, PR-range/full-history secret scans,
42
63
  cache miss-to-hit convergence, privacy-safe artifacts, and the fail-closed
43
64
  aggregate. CI exposes their stable aggregate as `Test`.
44
- - `make deadcode` runs `go tool deadcode` (pinned via the go.mod tool
45
- directive) over the module and reports unreachable functions, filtering out
46
- the intentional/MUST-KEEP baseline in `.deadcode-allowlist.txt`; it fails
47
- only on NEW dead code, and `make fix` runs it after `go fix`.
65
+ - `make deadcode` runs the focused baseline-contract fixture, then runs
66
+ `go tool deadcode` (pinned via the go.mod tool directive) over the module.
67
+ `.deadcode-allowlist.txt` is exact to current findings: duplicate and stale
68
+ rows fail. `.deadcode-must-keep.txt` separately records proactive migration,
69
+ compatibility, and proof APIs as `symbol<TAB>non-empty reason`; duplicates
70
+ within either file, overlap across files, malformed reasons, and findings
71
+ outside the two-file union fail deterministically. `make test` also runs the
72
+ focused fixture, and `make fix` runs the complete deadcode gate after
73
+ `go fix`.
48
74
 
49
75
  ## Docker-Covered Checks
50
76
 
@@ -0,0 +1,164 @@
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
+ A prompted managed Codex create also requires that endpoint. When it is not
128
+ ready or not attachable, `projmux create codex -- "<prompt>"` refuses instead of
129
+ silently creating a plain-CLI Agent, and the refusal carries the same typed
130
+ reason Doctor reports. `--interactive-only` creates that plain Agent on purpose,
131
+ without native turn control. See
132
+ [Codex Native-Required Create Migration](codex-native-required-migration.md).
133
+
134
+ If native app-server features are needed, review the
135
+ [official Codex CLI installation options](https://learn.chatgpt.com/docs/codex/cli)
136
+ and install or repair the managed standalone payload. Then rerun Doctor. Do not
137
+ copy binaries, create symlinks in the Codex home, or edit the control socket as
138
+ a diagnostic workaround. Doctor, Settings, and support-report collection never
139
+ start the daemon or modify the installation.
140
+
141
+ ## Incomplete npm install
142
+
143
+ If the npm shim exits before Projmux starts with:
144
+
145
+ ```text
146
+ projmux: unsupported or incomplete npm install for <platform>/<arch>.
147
+ Expected optional dependency @projmux/<platform>-<arch> to provide bin/projmux.
148
+ ```
149
+
150
+ the platform-specific optional package is missing. This can happen when npm
151
+ reuses stale package metadata or optional dependencies were disabled. Because
152
+ the Go binary is absent, `projmux doctor` cannot run yet. Re-resolve the current
153
+ release and its optional dependency:
154
+
155
+ ```sh
156
+ npm cache verify
157
+ npm install -g projmux@latest --include=optional
158
+ projmux version
159
+ projmux doctor
160
+ ```
161
+
162
+ If the same error remains, remove the incomplete global package, refresh npm's
163
+ package metadata, and reinstall. Do not copy a binary from another platform.
164
+ GitHub Release and source alternatives are documented in [Install](install.md).