projmux 0.16.0 → 0.16.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.
- package/docs/agent-message-replies.md +77 -14
- package/docs/architecture.md +355 -31
- package/docs/claude-coordination-endpoints.md +35 -13
- package/docs/cli-guide.md +688 -122
- package/docs/cli.md +1114 -222
- package/docs/configuration.md +597 -48
- package/docs/globalization.md +7 -4
- package/docs/hooks.md +466 -50
- package/docs/keybindings.md +9 -5
- package/docs/npm-distribution.md +4 -0
- package/docs/operational-diagnostics.md +270 -7
- package/docs/release.md +4 -0
- package/docs/replacement-contract.md +6 -0
- package/docs/repo-layout.md +3 -0
- package/docs/resource-attribution.md +4 -0
- package/docs/session-restore.md +11 -7
- package/docs/statusbar.md +23 -19
- package/docs/testing.md +68 -18
- package/docs/theme-palette.md +6 -6
- package/docs/tmux-surface-inventory.md +13 -0
- package/docs/upgrading.md +25 -23
- package/docs/usage-tracking.md +16 -1
- package/package.json +5 -5
package/docs/hooks.md
CHANGED
|
@@ -15,7 +15,7 @@ projmux-owned internal tmux hooks such as `pane-focus-in`, `pane-focus-out`,
|
|
|
15
15
|
| `pre-create` | Before projmux creates a missing persistent or ephemeral session | Non-zero exit, exec error, or timeout aborts creation | Logged with `[pre-create] ` |
|
|
16
16
|
| `post-create` | After projmux creates a brand-new persistent or ephemeral session | Logged and ignored; creation continues | Logged with `[post-create] ` |
|
|
17
17
|
| `post-attach` | After projmux switches the current tmux client to an existing session/target from inside tmux | Logged and ignored | Logged with `[post-attach] ` |
|
|
18
|
-
| `send-noti` | After `projmux create notification` (or the in-process AI notify producer) successfully writes a queue entry |
|
|
18
|
+
| `send-noti` | After `projmux create notification` (or the in-process AI notify producer) successfully writes a queue entry | Runs after the queue entry is written; the command waits until the hook exits or is killed at its timeout, and a failure or timeout is only a warning that changes neither the queue entry nor the exit code | Receives JSON on stdin; stdout/stderr are logged with `[send-noti] ` |
|
|
19
19
|
|
|
20
20
|
Deferred Phase A candidates remain future work until their behavior can be
|
|
21
21
|
specified without exposing projmux's internal tmux hook machinery: pane exit,
|
|
@@ -35,9 +35,18 @@ Project-local hooks are discovered from the lifecycle context's `PROJMUX_CWD`:
|
|
|
35
35
|
<repo>/.projmux/config.toml
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
projmux reads `.projmux/config.toml` directly in that directory. It does not
|
|
39
|
+
look in parent directories.
|
|
40
|
+
|
|
41
|
+
For `send-noti`, that directory is resolved as described in
|
|
42
|
+
[Send Noti](#send-noti-working-directory): when `PROJMUX_CWD` is not inherited, it is the nearest
|
|
43
|
+
`.projmux` or `.git` root above the notifying process's working directory.
|
|
44
|
+
|
|
45
|
+
File-form hooks from the historical global
|
|
46
|
+
`${XDG_CONFIG_HOME:-$HOME/.config}/projmux/hooks/<event>` layout and the project
|
|
47
|
+
`.projmux/<event>` and `.projmux/hooks/<event>` layouts are no longer executed.
|
|
48
|
+
Use declarative `[hooks.<event>] run` entries instead. projmux converts or warns
|
|
49
|
+
about these files; see [Troubleshooting](#troubleshooting).
|
|
41
50
|
|
|
42
51
|
If both a global hook and a project-local hook exist for an event, projmux runs
|
|
43
52
|
the global hook first, then the project-local hook.
|
|
@@ -145,8 +154,9 @@ session-level tmux environment before the startup command is sent. It does not
|
|
|
145
154
|
run when projmux attaches to an existing session or target.
|
|
146
155
|
|
|
147
156
|
Before sending the startup command, projmux polls tmux `pane_current_command`
|
|
148
|
-
for the new pane and waits
|
|
149
|
-
|
|
157
|
+
for the new pane and waits up to 2 seconds for it to report a shell command. If
|
|
158
|
+
no shell appears in that time, the startup command is not sent. This avoids
|
|
159
|
+
fixed sleeps as the primary readiness mechanism.
|
|
150
160
|
|
|
151
161
|
The configured string is sent into the new pane:
|
|
152
162
|
|
|
@@ -162,9 +172,10 @@ An empty `[startup] run` is a no-op.
|
|
|
162
172
|
already durable before the hook starts, so a failing or slow hook cannot drop
|
|
163
173
|
the notification or block the normal desktop notification flow.
|
|
164
174
|
|
|
165
|
-
The hook runs
|
|
166
|
-
|
|
167
|
-
`projmux create notification
|
|
175
|
+
The hook runs with the same default timeout (`5s`) as other hooks. projmux
|
|
176
|
+
waits until the hook exits or is killed at that timeout before returning from
|
|
177
|
+
`projmux create notification`; a failure or timeout is logged as a warning and
|
|
178
|
+
does not change the exit code.
|
|
168
179
|
|
|
169
180
|
stdin receives one JSON object:
|
|
170
181
|
|
|
@@ -204,6 +215,33 @@ If a `send-noti` hook itself calls `projmux create notification`, projmux sees
|
|
|
204
215
|
`PROJMUX_NOTIFY_HOOK_DEPTH=1` in the child environment and skips another
|
|
205
216
|
`send-noti` hook fire. The queue write itself still succeeds.
|
|
206
217
|
|
|
218
|
+
### Send Noti Working Directory
|
|
219
|
+
|
|
220
|
+
The `PROJMUX_CWD` a `send-noti` hook receives is resolved by the projmux
|
|
221
|
+
process that queues the notification, whether that is
|
|
222
|
+
`projmux create notification` or projmux queuing an AI agent notification, in
|
|
223
|
+
this order:
|
|
224
|
+
|
|
225
|
+
1. If that process's environment has a `PROJMUX_CWD` that is non-empty after
|
|
226
|
+
trimming whitespace, projmux uses the trimmed value as is. It does not walk
|
|
227
|
+
up for markers or check that the directory exists.
|
|
228
|
+
2. Otherwise projmux reads its working directory and uses the nearest
|
|
229
|
+
directory, starting at the working directory itself and walking up to the
|
|
230
|
+
filesystem root, that contains a `.projmux` or `.git` entry (file or
|
|
231
|
+
directory).
|
|
232
|
+
3. If no such directory exists, projmux uses the working directory itself.
|
|
233
|
+
4. If the working directory cannot be read or is empty, the value is empty.
|
|
234
|
+
|
|
235
|
+
The same value is the hook command's working directory and the only directory
|
|
236
|
+
whose `.projmux/config.toml` is read for a project `send-noti` hook; projmux
|
|
237
|
+
does not look in its parents. From a subdirectory of a repository, the project
|
|
238
|
+
hook therefore comes from the repository root's `.projmux/config.toml`. When
|
|
239
|
+
the value is empty, no project config is read and the hook runs in the
|
|
240
|
+
dispatching process's working directory.
|
|
241
|
+
|
|
242
|
+
A `projmux create notification` run from inside another hook inherits that
|
|
243
|
+
hook's `PROJMUX_CWD`, so it takes rule 1.
|
|
244
|
+
|
|
207
245
|
`Settings > Notifications > Delivery sources` surfaces the active Codex,
|
|
208
246
|
Claude, Antigravity, and tmux AI notify diagnostics: status, conflicts, config
|
|
209
247
|
paths, and copyable CLI install/remove/dry-run commands. It also shows whether
|
|
@@ -280,7 +318,7 @@ not semantic overrides.
|
|
|
280
318
|
| `PreToolUse` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
281
319
|
| `UserPromptSubmit` | marks the matched pane hook-active and sets AI state to thinking/busy; no notify queue entry is pushed |
|
|
282
320
|
| `PermissionRequest` | pushes a critical approval row with the tool name and a concise tool/action summary |
|
|
283
|
-
| `PostToolUse` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
321
|
+
| `PostToolUse` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed; while `agent-approval-answering` is `projmux` it also closes the one waiting permission request the terminal answered (see [below](#answering-claude-permission-requests-in-projmux)) |
|
|
284
322
|
| `PreCompact` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
285
323
|
| `PostCompact` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
286
324
|
| `SessionStart` | marks the matched pane hook-active and writes a quiet ingest diagnostic; for an exact managed initial-task binding it records `pending` startup readiness and opens the separately bounded acknowledgement window, but never acknowledges the task; no notify queue entry is pushed |
|
|
@@ -495,7 +533,7 @@ default install catalog is based on Claude Code 2.1.140 and represents the
|
|
|
495
533
|
| --- | --- |
|
|
496
534
|
| `PreToolUse` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
497
535
|
| `PostToolUse` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
498
|
-
| `PostToolUseFailure` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
536
|
+
| `PostToolUseFailure` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed; while `agent-approval-answering` is `projmux` it also closes the one waiting permission request the terminal answered (see [below](#answering-claude-permission-requests-in-projmux)) |
|
|
499
537
|
| `PostToolBatch` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
500
538
|
| `PermissionDenied` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
501
539
|
| `Notification` | pushes a Claude notify row for response-ready, approval-required, or input-ready based on `notification_type` |
|
|
@@ -523,6 +561,15 @@ default install catalog is based on Claude Code 2.1.140 and represents the
|
|
|
523
561
|
| `CwdChanged` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
524
562
|
| `FileChanged` | marks the matched pane hook-active and writes a quiet ingest diagnostic; no notify queue entry is pushed |
|
|
525
563
|
|
|
564
|
+
`Stop` is the first transcript tail reader. It opens the `transcript_path` its
|
|
565
|
+
own payload reports, reads at most the last 256 KiB, and keeps one string: the
|
|
566
|
+
text of the last assistant line in that tail. That text becomes the completion
|
|
567
|
+
row's text, so it goes wherever the row goes: the notify queue, the desktop
|
|
568
|
+
notification (whose duplicate check keeps a normalized copy in a tmux pane
|
|
569
|
+
option), and the `send-noti` payload. It is not written to the Registry or to
|
|
570
|
+
the Agent session history, and nothing else the read saw is kept. With no
|
|
571
|
+
readable path or no assistant text, the row text is `Ready`.
|
|
572
|
+
|
|
526
573
|
Current Claude Code (2.1.277) sends no hook when the operator denies a
|
|
527
574
|
permission dialog, so neither `PermissionDenied` nor `Stop` closes it and the
|
|
528
575
|
Agent keeps its `approval_required` observation. The held coordination message
|
|
@@ -533,6 +580,17 @@ at each line's type, subtype, and timestamp and whether an assistant line has a
|
|
|
533
580
|
`tool_use` item. Nothing it reads is stored, logged, or forwarded. See
|
|
534
581
|
[held messages](claude-coordination-endpoints.md#held-while-the-target-awaits-its-operator).
|
|
535
582
|
|
|
583
|
+
A permission dialog or an elicitation raises its Agent once, so the Agent's
|
|
584
|
+
pane supervisor is a third transcript tail reader. While the Agent's
|
|
585
|
+
interaction is a provider-hook `approval_required` or `input_required`, about
|
|
586
|
+
every ten minutes it reads the same bounded tail of the Agent's own recorded
|
|
587
|
+
`transcript_path`. While the turn has not ended and an assistant `tool_use`
|
|
588
|
+
still has no `tool_result`, it recommits the same interaction, so an open
|
|
589
|
+
dialog does not decay to `unknown` after thirty minutes. It looks only at each
|
|
590
|
+
line's type, subtype, and timestamp, the `tool_use` ids, and the `tool_result`
|
|
591
|
+
`tool_use_id`s. Nothing it reads is stored, logged, or forwarded. A denied or
|
|
592
|
+
dismissed dialog ends the turn, and it stops.
|
|
593
|
+
|
|
536
594
|
Hook-generated queue rows use the same compact body catalog: agent label,
|
|
537
595
|
event category, then the best available summary (Codex assistant text, Claude
|
|
538
596
|
tool/action summary, transcript summary, error, or teammate labels). Structured
|
|
@@ -546,9 +604,12 @@ Pane matching follows the shared AI ingest order: inherited `$TMUX_PANE`, then
|
|
|
546
604
|
payload `cwd`, then cached session id pane options. A matched pane is marked
|
|
547
605
|
with `@projmux_ai_hook_active=1`, so `projmux internal agent-hook watch-title` skips the pane
|
|
548
606
|
after a minimal hook-active gate instead of polling pane title/capture output;
|
|
549
|
-
hook payloads become the primary signal. The
|
|
550
|
-
|
|
551
|
-
|
|
607
|
+
hook payloads become the primary signal. The hook reads the pane's current
|
|
608
|
+
markers once and writes only the ones that differ, so a hook on an already
|
|
609
|
+
marked pane sends tmux no `set-option`: every option write redraws all clients,
|
|
610
|
+
and the first redraw of a second re-runs the status line's `#()` jobs. The tmux
|
|
611
|
+
bell fallback does not mark panes hook-active, so title/capture fallback
|
|
612
|
+
remains available for panes that only emit bells.
|
|
552
613
|
|
|
553
614
|
The Claude hook payload is intentionally accepted directly at the ingest
|
|
554
615
|
boundary. Core identity fields accept `hook_event_name`/`event_name`,
|
|
@@ -697,36 +758,131 @@ Codex and are managed from `Settings > Notifications > Agent event behavior`.
|
|
|
697
758
|
They only affect ingest delivery; `projmux agent integrate claude` still uses the
|
|
698
759
|
catalog `install` field for installed hook events.
|
|
699
760
|
|
|
700
|
-
### Answering AskUserQuestion
|
|
761
|
+
### Answering AskUserQuestion In projmux
|
|
701
762
|
|
|
702
763
|
`projmux agent integrate claude` also installs one `PreToolUse` entry with
|
|
703
764
|
`"matcher": "AskUserQuestion"` that runs
|
|
704
765
|
`projmux internal claude-question-hook` (marker
|
|
705
|
-
`projmux-managed:claude-question:v1`, `"timeout"
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
766
|
+
`projmux-managed:claude-question:v1`, `"timeout"` the fixed ceiling `604800`
|
|
767
|
+
seconds (7 days), which outlasts every answer window including `unlimited`;
|
|
768
|
+
it is the safety net for a stuck hook, which has no recover, and is 7 times
|
|
769
|
+
the longest bounded window, so it never cuts a normal one). Unlike the
|
|
770
|
+
ingest command its stdout is not discarded, because that is where an answer is
|
|
771
|
+
handed to Claude Code. Re-running the integration keeps exactly one such entry,
|
|
772
|
+
`--remove` deletes it, and `config apply` never adds or changes it. An entry
|
|
773
|
+
installed by an older projmux carries an old timeout, the window plus 15
|
|
774
|
+
seconds (`915` by default) or the earlier, longer ceiling of about 25 days;
|
|
775
|
+
run `projmux agent integrate claude` once after upgrading
|
|
776
|
+
to rewrite it to the ceiling.
|
|
777
|
+
|
|
778
|
+
A question is answered one of two ways:
|
|
779
|
+
|
|
780
|
+
| Way | When | What happens |
|
|
781
|
+
| --- | --- | --- |
|
|
782
|
+
| 1: Claude Code's prompt (default) | the Agent is not opted in and `agent-question-answering` is not `projmux` | the hook prints nothing and exits at once; Claude Code shows its usual question prompt |
|
|
783
|
+
| 2: projmux | the Agent is opted in, or `agent-question-answering` is `projmux` | the hook records the question, opens a projmux picker popup, and takes the first answer from the popup or the command line |
|
|
784
|
+
|
|
785
|
+
The Agent's own switch wins over the setting, so an opted-in Agent is always
|
|
786
|
+
way 2:
|
|
711
787
|
|
|
712
788
|
```sh
|
|
713
789
|
projmux agent question enable <agent-ref>
|
|
714
790
|
projmux agent question disable <agent-ref>
|
|
715
791
|
```
|
|
716
792
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
793
|
+
The setting is the central
|
|
794
|
+
`${XDG_CONFIG_HOME:-$HOME/.config}/projmux/agent-question-answering` file (see
|
|
795
|
+
[configuration.md](configuration.md#agent-question-answering)). The hook reads
|
|
796
|
+
it only after the Registry shows that the question comes from a projmux
|
|
797
|
+
Claude Agent's own conversation; a session projmux did not start, and a
|
|
798
|
+
subagent's question, are always way 1 and never read it.
|
|
799
|
+
|
|
800
|
+
In way 2 the hook holds the tool call open for the answer window: 900 seconds
|
|
801
|
+
unless `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/agent-question-window-seconds`
|
|
802
|
+
holds another value in 60–86400 (see
|
|
803
|
+
[configuration.md](configuration.md#agent-question-window)). The window binds
|
|
804
|
+
only in way 2; way 1 never waits. While it waits, Claude Code shows the hook's
|
|
805
|
+
status message instead of the question prompt.
|
|
806
|
+
A question projmux holds also shows the Agent as `input_required` until the tool runs, however long it waits.
|
|
807
|
+
|
|
808
|
+
The hook opens a tmux popup right away on the terminal you are looking at: the
|
|
809
|
+
client of the Agent's tmux server that you used most recently, whatever
|
|
810
|
+
Session, Window, or Pane it shows. Clients of other tmux servers are not
|
|
811
|
+
considered. The popup's title names the asking Agent and its Project/Window,
|
|
812
|
+
for example `Agent question from reviewer (alpha/main)`, and it runs the projmux
|
|
813
|
+
picker, one question at a time. Enter picks an option of a single-select
|
|
814
|
+
question; on a multi-select question Enter toggles an option and `Done`
|
|
815
|
+
finishes, with at least one option chosen; `Other / type an answer` takes free
|
|
816
|
+
text. When no client is attached the hook keeps waiting and looks again about
|
|
817
|
+
once a second, and opens the popup when one attaches. Nothing is drawn on
|
|
818
|
+
Claude Code's own terminal.
|
|
819
|
+
|
|
820
|
+
Questions show one at a time per client. tmux does not draw a popup over
|
|
821
|
+
another one, so a question that arrives while that client already shows a
|
|
822
|
+
popup (another Agent's question, or any other popup) keeps waiting and shows
|
|
823
|
+
once that popup closes. A popup that tmux did not draw is tried again, never
|
|
824
|
+
counted as ended.
|
|
825
|
+
|
|
826
|
+
A long question wraps inside the popup instead of being cut, and a line break
|
|
827
|
+
in the question starts a new line. The popup is 80% × 70% of the client, but at
|
|
828
|
+
least 72 columns × 22 rows (or the whole client if that is smaller). Only on a
|
|
829
|
+
client smaller than that is the question shortened with `…` so the options stay
|
|
830
|
+
reachable; the full text is available from `projmux agent question list`.
|
|
831
|
+
|
|
832
|
+
Pressing Esc in the popup gives the question back: the record is closed and
|
|
833
|
+
Claude Code shows its own prompt. So does a picker that fails. A popup that
|
|
834
|
+
ends any other way while the question still waits, most often because its
|
|
835
|
+
client detached, does not give the question back: the question stays in
|
|
836
|
+
projmux, and the next look, within about a second, opens the popup again on the
|
|
837
|
+
client you used most recently, or on the first client to attach when none is
|
|
838
|
+
left. Only a popup that keeps failing gives the question back: three popups in
|
|
839
|
+
a row that each ended within 2 seconds of opening, a popup that failed to open
|
|
840
|
+
among them. A popup that stayed up longer starts that count again, so leaving
|
|
841
|
+
clients never runs it out. Pressing Esc in Claude Code itself cancels the wait
|
|
842
|
+
and declines the question.
|
|
843
|
+
|
|
844
|
+
A question is given back only once its record is written `closed`. While the
|
|
845
|
+
question store cannot be written (the disk is full, or its lock is held), Esc
|
|
846
|
+
in the popup cannot close the record, which still waits, so the popup opens
|
|
847
|
+
again. When the hook gives the question back after popups that kept failing,
|
|
848
|
+
the record stays `waiting` and the hook keeps holding the question, so it is in
|
|
849
|
+
neither a popup nor Claude Code's prompt; the hook tries again every 250
|
|
850
|
+
milliseconds (each try waits up to 2 seconds for the store lock) and hands the
|
|
851
|
+
question back as soon as the write succeeds, so the record's `updatedAt` is
|
|
852
|
+
that moment, not the moment the last popup ended.
|
|
853
|
+
|
|
854
|
+
A `closed` record says why it closed in its `disposition`, and that reason
|
|
855
|
+
tells whether the provider still asks the question in its own prompt:
|
|
856
|
+
|
|
857
|
+
| Disposition | Written when | Provider still asks |
|
|
858
|
+
| --- | --- | --- |
|
|
859
|
+
| `popup-dismissed` | Esc in the popup | yes |
|
|
860
|
+
| `popup-failed` | the picker failed; for a Claude question, three popups in a row failed to open or ended within 2 seconds of opening; for a Codex question, the popup failed to open or ended without an answer (its client detached) | yes |
|
|
861
|
+
| `hook-canceled` | Claude Code canceled the hook (Esc in Claude Code, or its hook timeout), which declines the question | no |
|
|
862
|
+
| `hook-failed` | the hook crashed after it recorded the question | yes |
|
|
863
|
+
| `channel-off` | `agent question disable` | yes |
|
|
864
|
+
| `turn-ended` | the Codex turn that asked it ended | no |
|
|
865
|
+
| `watch-stopped` | projmux's Codex observer stopped watching the request (its connection to Codex ended, or the observer stopped) | yes |
|
|
866
|
+
| `answered-elsewhere` | Codex's own input surface answered it first | no |
|
|
867
|
+
|
|
868
|
+
`agent question list` shows the reason and says whether the provider still
|
|
869
|
+
asks, for example `closed (popup-failed; Claude Code still asks it in its own
|
|
870
|
+
prompt)` or `closed (hook-canceled; Claude Code no longer asks it)`; `-o json`
|
|
871
|
+
has the reason in `disposition`. A record closed by an older projmux has no
|
|
872
|
+
reason, and a reason this projmux does not know is shown as written. The
|
|
873
|
+
`question-closed` refusal of `answer` carries the same words after the reason.
|
|
874
|
+
|
|
875
|
+
The same question can be answered from any shell:
|
|
724
876
|
|
|
725
877
|
```sh
|
|
726
878
|
projmux agent question list <agent-ref> [-o json]
|
|
727
879
|
projmux agent question answer <agent-ref> <question-id> --option 1=<label> --index 2=<n> --text 3=<free text>
|
|
728
880
|
```
|
|
729
881
|
|
|
882
|
+
Whichever answer lands first wins. An answer from the command line closes the
|
|
883
|
+
popup, and a popup answer that arrives after it is refused as
|
|
884
|
+
`question-not-pending`.
|
|
885
|
+
|
|
730
886
|
Questions are numbered from 1 in the order Claude asked them, and so are the
|
|
731
887
|
options of each question. `--option <n>=<label>` picks an option by its exact
|
|
732
888
|
label, `--index <n>=<k>` by its number, and `--text <n>=<text>` answers with
|
|
@@ -736,13 +892,165 @@ occurrences, which are joined with `", "` in option order; a single-select
|
|
|
736
892
|
question takes exactly one. Every question needs an answer. A refused answer
|
|
737
893
|
changes nothing and names one reason token: `question-not-found`,
|
|
738
894
|
`question-not-pending` (already answered), `question-expired`,
|
|
739
|
-
`question-closed`, `question-invalid-answer`, `question-channel-off
|
|
740
|
-
`question-provider-unsupported`.
|
|
895
|
+
`question-closed`, `question-invalid-answer`, `question-channel-off` (way 1),
|
|
896
|
+
or `question-provider-unsupported`.
|
|
897
|
+
|
|
898
|
+
If nobody answers within the window, the question expires, the popup closes,
|
|
899
|
+
and Claude Code shows its own prompt as usual. The hook reads the window for
|
|
900
|
+
every question and the installed timeout is the fixed ceiling, so a window
|
|
901
|
+
changed with `projmux config agent-questions` or in the file applies to the
|
|
902
|
+
next question without re-running `projmux agent integrate claude`. `agent
|
|
903
|
+
question disable` also hands every question the Agent is still holding back to that
|
|
904
|
+
prompt immediately. Records live in `<state dir>/agent-questions/` and settled
|
|
905
|
+
ones are kept for a day.
|
|
906
|
+
|
|
907
|
+
The hook never blocks the tool: every failure, and even a crash inside the
|
|
908
|
+
hook, ends with no output and exit status 0, which Claude Code reads as no
|
|
909
|
+
decision.
|
|
910
|
+
|
|
911
|
+
### Answering Claude Permission Requests In projmux
|
|
912
|
+
|
|
913
|
+
`projmux agent integrate claude` also installs one more `PermissionRequest`
|
|
914
|
+
entry, separate from the ingest entry on the same event, that runs
|
|
915
|
+
`projmux internal claude-permission-hook` (marker
|
|
916
|
+
`projmux-managed:claude-permission:v1`, no matcher, `"timeout"` the fixed
|
|
917
|
+
ceiling `3615` seconds, the longest window plus 15 seconds). Unlike the ingest
|
|
918
|
+
command its stdout is not discarded, because that is where a decision is handed
|
|
919
|
+
to Claude Code. The ingest entry, and the `approval_required` row and badge it
|
|
920
|
+
raises, are unchanged. Re-running the integration keeps exactly one such entry,
|
|
921
|
+
`--remove` deletes it, and `config apply` never adds or changes it.
|
|
922
|
+
|
|
923
|
+
The hook decides nothing unless the central `agent-approval-answering` setting
|
|
924
|
+
is `projmux` (see
|
|
925
|
+
[configuration.md](configuration.md#agent-approval-answering)). The hook
|
|
926
|
+
captures Claude permission requests only; Codex approvals are held by the
|
|
927
|
+
Codex app-server and never captured (see [Codex answers](#codex-answers)
|
|
928
|
+
below). In the default `claude` way the
|
|
929
|
+
hook reads the payload and the Registry, and only once the Registry confirms a
|
|
930
|
+
projmux Claude Agent the one setting file; it prints nothing, records nothing,
|
|
931
|
+
and touches no tmux. A session projmux did not start never reads the setting.
|
|
932
|
+
A request in `bypassPermissions` or `dontAsk` mode is never captured. A
|
|
933
|
+
subagent's request is captured, and its `agent_type` is shown by `agent
|
|
934
|
+
approval list` and written to the audit log.
|
|
935
|
+
|
|
936
|
+
In way `projmux` the hook records the request and waits up to the answer
|
|
937
|
+
window (900 seconds unless `agent-approval-window-seconds` holds another value
|
|
938
|
+
in 60–3600). Claude Code shows its own prompt at the same time, and that prompt
|
|
939
|
+
stays fully usable: whichever answer comes first wins.
|
|
940
|
+
|
|
941
|
+
```sh
|
|
942
|
+
projmux agent approval list <agent-ref> [-o json]
|
|
943
|
+
projmux agent approval answer <agent-ref> <request-id> --allow|--deny [--via popup|cli|<client>]
|
|
944
|
+
```
|
|
741
945
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
946
|
+
`list` shows each waiting request with its id, tool name, subagent type, full
|
|
947
|
+
tool input, and created and deadline times. A listed request may already have
|
|
948
|
+
been answered in the terminal. `answer` takes exactly one of `--allow` or
|
|
949
|
+
`--deny`:
|
|
950
|
+
|
|
951
|
+
- `--allow` prints `{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow"}}}`.
|
|
952
|
+
It allows that one tool call once, exactly as requested: it never carries
|
|
953
|
+
`updatedInput` or `updatedPermissions`, so no permission rule changes, and the
|
|
954
|
+
request's `permission_suggestions` are never applied.
|
|
955
|
+
- `--deny` prints a deny decision with the message `denied by the operator in
|
|
956
|
+
projmux` and `"interrupt": false`.
|
|
957
|
+
|
|
958
|
+
The hook is deny-by-default: it prints the allow decision only for a request
|
|
959
|
+
the store settled as allowed under its lock. Everything else prints nothing
|
|
960
|
+
and exits 0, which Claude Code reads as no decision: no answer before the
|
|
961
|
+
window ends (the request expires), a store that cannot be opened, written, or
|
|
962
|
+
read, a cancellation (SIGTERM, SIGINT, SIGHUP), a crash inside the hook, and a
|
|
963
|
+
malformed or oversized payload. It never exits 2.
|
|
964
|
+
|
|
965
|
+
A second or late answer is refused and changes nothing, with one reason token:
|
|
966
|
+
`permission-not-pending` (already allowed or denied, or already answered in
|
|
967
|
+
Claude Code's own prompt), `permission-expired`, `permission-not-found`,
|
|
968
|
+
`permission-answering-off` (the setting is `claude`), or
|
|
969
|
+
`permission-provider-unsupported` (neither a Claude nor a Codex Agent; nothing
|
|
970
|
+
is written).
|
|
971
|
+
|
|
972
|
+
Answering in the terminal: "No" or Esc sends the waiting hook SIGTERM, which
|
|
973
|
+
closes its record. "Yes" does not tell the hook at all, so the Claude ingest of
|
|
974
|
+
the `PostToolUse` or `PostToolUseFailure` that follows closes the one waiting
|
|
975
|
+
record of the same session, tool, and tool input (compared as canonical JSON)
|
|
976
|
+
with the reason `answered-in-terminal`; the hook then exits with no output.
|
|
977
|
+
When no record or more than one matches, nothing is closed and the window
|
|
978
|
+
expiry ends the wait. In the default way that ingest reads only the setting,
|
|
979
|
+
and with no store file it opens nothing. Known limit: an answer from projmux
|
|
980
|
+
that the store records after the terminal "Yes" but before its `PostToolUse`
|
|
981
|
+
is still accepted and printed. Claude Code has already run the tool by then
|
|
982
|
+
and ignores the late output, but the audit log shows that projmux answer.
|
|
983
|
+
|
|
984
|
+
`--via` is reported by the caller and recorded as given; projmux does not
|
|
985
|
+
verify it. Every event (`requested`, `allowed`, `denied`, `expired`, `closed`,
|
|
986
|
+
`refused` for a late or invalid answer, and `uncommitted` for an answer that
|
|
987
|
+
did not take effect) is appended as one JSON line to
|
|
988
|
+
`<state dir>/agent-approvals/audit.jsonl` (mode 0600, rotated to one `.1`
|
|
989
|
+
generation at 1 MiB) with the request id, Agent, Pane, session, subagent type,
|
|
990
|
+
tool name, a bounded one-line input summary (Bash `command`, a file tool's
|
|
991
|
+
path, otherwise only the input's key names), request and decision times in
|
|
992
|
+
UTC, and `via`. The full tool input lives only in the request record under
|
|
993
|
+
`<state dir>/agent-approvals/`, and settled records are kept for a day.
|
|
994
|
+
|
|
995
|
+
An answer's `allowed` or `denied` line is written and synced before the answer
|
|
996
|
+
takes effect. If that line cannot be written, `answer` fails with an error
|
|
997
|
+
naming the audit log and the request stays waiting: fix the log and answer
|
|
998
|
+
again, or answer in Claude Code. The other events stay best effort and never
|
|
999
|
+
fail the transition they describe. If the line was written but the request
|
|
1000
|
+
record write then fails before the new record replaces the old one, the answer
|
|
1001
|
+
did not take effect: `answer` fails, the request stays waiting, and an
|
|
1002
|
+
`uncommitted` line with `reason` `record-write-failed` follows the answer line
|
|
1003
|
+
for the same request id. If that `uncommitted` line cannot be written either,
|
|
1004
|
+
`answer` says so and the log keeps the `allowed` or `denied` line alone. If
|
|
1005
|
+
the new record is already in place and only the sync of its directory fails,
|
|
1006
|
+
the write took effect: `answer` succeeds with a warning on stderr, the hook
|
|
1007
|
+
keeps waiting on a request it recorded, and the `requested`, `expired`, and
|
|
1008
|
+
`closed` lines are written as usual, but that write may not survive a power
|
|
1009
|
+
loss. No `uncommitted` line is written when the answer took effect, or when its
|
|
1010
|
+
outcome is unknown, such as a crash between the line and the record write. No
|
|
1011
|
+
line marks that crash afterwards: the request stays waiting, and a later
|
|
1012
|
+
`expired`, `closed`, `allowed`, or `denied` line for the same request id shows
|
|
1013
|
+
how it actually ended. For example, `requested`, `allowed`, `denied` for one
|
|
1014
|
+
request id means the first answer never took effect and the request was denied;
|
|
1015
|
+
`requested`, `allowed`, `expired` means it expired without a decision. If the
|
|
1016
|
+
hook died too, as when the machine went down, no later line may follow and the
|
|
1017
|
+
answer's outcome stays unknown. A write killed before its rename can leave a
|
|
1018
|
+
`.requests.tmp-*` file in `<state dir>/agent-approvals/`; the next write of the
|
|
1019
|
+
store removes it. Read the log this way: an `allowed` or `denied` line followed
|
|
1020
|
+
by an `uncommitted` line for the same request id did not take effect, and
|
|
1021
|
+
otherwise the last `expired`, `closed`, `allowed`, or `denied` line for a
|
|
1022
|
+
request id shows how it ended. The log may over-report an answer, never
|
|
1023
|
+
under-report one.
|
|
1024
|
+
|
|
1025
|
+
#### Codex Answers
|
|
1026
|
+
|
|
1027
|
+
The same `list` and `answer` also work for a Codex Agent with an exact native
|
|
1028
|
+
control binding (see [cli-guide.md](cli-guide.md)). projmux stores no Codex
|
|
1029
|
+
request: the provider holds it, so the audit log gets only the `allowed` or
|
|
1030
|
+
`denied` line of an answer sent from projmux and, when that answer is
|
|
1031
|
+
confirmed not to have reached Codex, its `uncommitted` line; never
|
|
1032
|
+
`requested`, `expired`, `closed`, or `refused`. That line carries the normalized request id, Agent,
|
|
1033
|
+
Pane, the approval kind as the tool name, a bounded input summary (a command
|
|
1034
|
+
approval's command, otherwise only the detail key names), `via`, the decision
|
|
1035
|
+
time, and the provider decision actually sent in `reason` as
|
|
1036
|
+
`decision=accept`, `decision=decline`, or `decision=cancel`. `--deny` sends
|
|
1037
|
+
`decline` when the request offers it and otherwise `cancel`, which also
|
|
1038
|
+
interrupts the turn; nothing runs either way. The line is written and synced
|
|
1039
|
+
before that one decision is sent; if it cannot be written nothing is sent and the request stays
|
|
1040
|
+
pending. If the send then fails and the decision is confirmed not to have
|
|
1041
|
+
reached Codex, an `uncommitted` line with `reason` `send-failed` follows the
|
|
1042
|
+
answer line and `answer` says the answer did not take effect. That is the case
|
|
1043
|
+
when the request never left projmux (the binding fence refused it, or the
|
|
1044
|
+
control socket could not be reached) or when the control server refused it
|
|
1045
|
+
before claiming the approval (`stale-epoch`, `stale-binding`, `unavailable`,
|
|
1046
|
+
`invalid-frame`, `ambiguous-request`, or `unsafe-decision`). Any other send
|
|
1047
|
+
failure may have reached Codex: the line stays alone and `answer` says the
|
|
1048
|
+
decision may or may not have reached Codex, so the log may over-report a Codex
|
|
1049
|
+
answer too. If the `uncommitted` line cannot be written, `answer` says so and
|
|
1050
|
+
the log keeps the answer line alone. A crash of `answer` between the line and
|
|
1051
|
+
the send also leaves the answer line alone, and no later line follows it: read
|
|
1052
|
+
it like a send that may or may not have reached Codex. An answer given in the
|
|
1053
|
+
Codex TUI or with `agent approval review` is not logged.
|
|
746
1054
|
|
|
747
1055
|
## Antigravity Hook Ingest
|
|
748
1056
|
|
|
@@ -872,7 +1180,7 @@ empty.
|
|
|
872
1180
|
| Variable | `pre-create` | `post-create` | `post-attach` | `send-noti` |
|
|
873
1181
|
| --- | --- | --- | --- | --- |
|
|
874
1182
|
| `PROJMUX_SESSION` | new session name | new session name | target session name | target session when known; otherwise empty |
|
|
875
|
-
| `PROJMUX_CWD` | requested session directory | created session directory | resolved target directory; may be empty if lookup fails |
|
|
1183
|
+
| `PROJMUX_CWD` | requested session directory | created session directory | resolved target directory; may be empty if lookup fails | `PROJMUX_CWD` inherited by the dispatching process; otherwise the nearest `.projmux` or `.git` root above its working directory, else that directory; empty if it cannot be read — see [Send Noti](#send-noti-working-directory) |
|
|
876
1184
|
| `PROJMUX_SESSION_KIND` | `persistent` or `ephemeral` | `persistent` or `ephemeral` | empty | empty |
|
|
877
1185
|
| `PROJMUX_VERSION` | projmux version | projmux version | projmux version | projmux version |
|
|
878
1186
|
| `PROJMUX_SOCKET` | app socket metadata (`projmux`) | app socket metadata (`projmux`) | app socket metadata (`projmux`) | queue-entry socket when known; otherwise omitted |
|
|
@@ -889,14 +1197,29 @@ context to either event.
|
|
|
889
1197
|
|
|
890
1198
|
## Examples
|
|
891
1199
|
|
|
1200
|
+
Save hook scripts outside the legacy paths (`projmux/hooks/<event>`,
|
|
1201
|
+
`.projmux/<event>`, `.projmux/hooks/<event>`). projmux rewrites or ignores
|
|
1202
|
+
files at those paths. Make each script executable and name it in a `run` line.
|
|
1203
|
+
`run` goes through `sh -c`, so `$HOME` expands.
|
|
1204
|
+
|
|
892
1205
|
### Global Post Create Stub
|
|
893
1206
|
|
|
1207
|
+
Save this as `~/.local/bin/projmux-post-create`:
|
|
1208
|
+
|
|
894
1209
|
```bash
|
|
895
1210
|
#!/usr/bin/env bash
|
|
896
1211
|
echo "session=$PROJMUX_SESSION cwd=$PROJMUX_CWD kind=$PROJMUX_SESSION_KIND"
|
|
897
1212
|
tmux -L "$PROJMUX_SOCKET" set-option -p -t "$PROJMUX_PANE" @projmux_initialized 1
|
|
898
1213
|
```
|
|
899
1214
|
|
|
1215
|
+
Make it executable with `chmod +x ~/.local/bin/projmux-post-create`, then add
|
|
1216
|
+
this to `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/config.toml`:
|
|
1217
|
+
|
|
1218
|
+
```toml
|
|
1219
|
+
[hooks.post-create]
|
|
1220
|
+
run = "$HOME/.local/bin/projmux-post-create"
|
|
1221
|
+
```
|
|
1222
|
+
|
|
900
1223
|
### Project Startup Command
|
|
901
1224
|
|
|
902
1225
|
```toml
|
|
@@ -906,6 +1229,8 @@ run = "git status --short"
|
|
|
906
1229
|
|
|
907
1230
|
### Per Session GH_TOKEN By Repo
|
|
908
1231
|
|
|
1232
|
+
Save this as `~/.local/bin/projmux-gh-token`:
|
|
1233
|
+
|
|
909
1234
|
```bash
|
|
910
1235
|
#!/usr/bin/env bash
|
|
911
1236
|
set -euo pipefail
|
|
@@ -919,28 +1244,119 @@ esac
|
|
|
919
1244
|
tmux -L "$PROJMUX_SOCKET" set-environment -t "$PROJMUX_SESSION" GH_TOKEN "$token"
|
|
920
1245
|
```
|
|
921
1246
|
|
|
1247
|
+
Make it executable with `chmod +x ~/.local/bin/projmux-gh-token`, then add this
|
|
1248
|
+
to `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/config.toml`:
|
|
1249
|
+
|
|
1250
|
+
```toml
|
|
1251
|
+
[hooks.post-create]
|
|
1252
|
+
run = "$HOME/.local/bin/projmux-gh-token"
|
|
1253
|
+
```
|
|
1254
|
+
|
|
922
1255
|
`set-environment` only seeds the session env that newly-spawned panes inherit;
|
|
923
1256
|
it does not retroactively change the current shell. Open new panes via tmux
|
|
924
1257
|
(`Ctrl-b c`, `Ctrl-b "`, etc.) to pick up the value.
|
|
925
1258
|
|
|
1259
|
+
Both examples use `post-create`, and an event has one `run` line. To use both,
|
|
1260
|
+
call them from one line:
|
|
1261
|
+
|
|
1262
|
+
```toml
|
|
1263
|
+
[hooks.post-create]
|
|
1264
|
+
run = "$HOME/.local/bin/projmux-post-create; $HOME/.local/bin/projmux-gh-token"
|
|
1265
|
+
```
|
|
1266
|
+
|
|
926
1267
|
## Troubleshooting
|
|
927
1268
|
|
|
928
|
-
- **Nothing happens.**
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
-
|
|
934
|
-
|
|
1269
|
+
- **Nothing happens.** Work through these checks in order:
|
|
1270
|
+
1. Check that a `run` line exists for the event. `projmux hook list` shows
|
|
1271
|
+
the global and project entries. A project hook must be in
|
|
1272
|
+
`.projmux/config.toml` directly in the hook's `PROJMUX_CWD` directory,
|
|
1273
|
+
which is the session directory for `pre-create`, `post-create`, and
|
|
1274
|
+
`post-attach`. For `send-noti` it is the inherited `PROJMUX_CWD`,
|
|
1275
|
+
otherwise the nearest `.projmux` or `.git` root above the notifying
|
|
1276
|
+
process's working directory; see [Send Noti](#send-noti-working-directory).
|
|
1277
|
+
The runner does not look in parent directories. Without
|
|
1278
|
+
`PROJMUX_CWD`, `projmux hook list` uses `.projmux/config.toml` in the
|
|
1279
|
+
current directory, or walks up to the nearest `.projmux` or `.git`. When
|
|
1280
|
+
it shows a parent file, it says that sessions created in the current
|
|
1281
|
+
directory do not run it:
|
|
1282
|
+
`note: sessions created in <dir> do not run this file's pre-create, post-create, or post-attach hooks, [startup], or [env]; only sessions created in <root> do`.
|
|
1283
|
+
`projmux hook validate` prints the same note. `projmux hook trust` and
|
|
1284
|
+
`projmux hook untrust` without `<project>`, and `projmux hook edit
|
|
1285
|
+
<event>` without `--global` (inline or `--editor`), still act on the
|
|
1286
|
+
`<root>` file from such a directory and print the same note after their
|
|
1287
|
+
result line. The note is not printed from `<root>` itself, under
|
|
1288
|
+
`PROJMUX_CWD`, or when you name `<project>` or `--global`. It follows the
|
|
1289
|
+
UI locale; the text above is the `en-US` form. Move the file into the
|
|
1290
|
+
session directory, or create the session in `<root>`.
|
|
1291
|
+
2. Check for parse errors. If a file cannot be parsed, the whole file is
|
|
1292
|
+
ignored for that run and stderr shows
|
|
1293
|
+
`projmux: <event> hook: global config "<path>" could not be parsed: <reason>`
|
|
1294
|
+
or
|
|
1295
|
+
`projmux: <event> hook: project config ".projmux/config.toml" could not be parsed: <reason>`.
|
|
1296
|
+
`projmux hook validate` prints each file's status, for example
|
|
1297
|
+
`global <path> PARSE ERROR: line 2: value must be a quoted string`,
|
|
1298
|
+
and exits 1.
|
|
1299
|
+
3. Check that project hooks are on. `PROJMUX_PROJECT_HOOKS=off` or
|
|
1300
|
+
Settings > Labs > Project Hooks turns them off with no warning. Global
|
|
1301
|
+
hooks still run.
|
|
1302
|
+
4. Check trust. An untrusted or changed project config is skipped; see the
|
|
1303
|
+
trust item below.
|
|
1304
|
+
5. `post-attach` runs only when projmux switches a client from inside tmux.
|
|
1305
|
+
An attach from outside tmux does not run it.
|
|
1306
|
+
6. Check for legacy script files. Legacy files are never executed, whatever
|
|
1307
|
+
their execute bit: global
|
|
1308
|
+
`${XDG_CONFIG_HOME:-$HOME/.config}/projmux/hooks/<event>`, and project
|
|
1309
|
+
`.projmux/<event>` and `.projmux/hooks/<event>`. Most projmux commands,
|
|
1310
|
+
such as `projmux hook list`, scan them when they start. Help and
|
|
1311
|
+
read-only commands such as `doctor`, `get`, and `describe` do not:
|
|
1312
|
+
- A one-command script is moved into `[hooks.<event>] run` in the
|
|
1313
|
+
matching `config.toml` and renamed to `<path>.bak`. stderr shows
|
|
1314
|
+
`projmux: migrated legacy <global|project> hook <path> -> [hooks.<event>] run; original kept at <path>.bak`.
|
|
1315
|
+
An existing `run` for that event wins; the script is still renamed.
|
|
1316
|
+
- A multi-line script is left in place and not run. Every scanning
|
|
1317
|
+
command prints
|
|
1318
|
+
`projmux: legacy <global|project> hook <path> has <N> non-trivial lines; declarative migration skipped (multi-line scripts are no longer executed; rewrite manually as run = "bash -c '...'" or run = "./scripts/foo.sh")`.
|
|
1319
|
+
- A symlink is left in place and not run:
|
|
1320
|
+
`projmux: legacy <global|project> hook <path> is a symlink; declarative migration skipped (clean up via the source dotfiles repo)`.
|
|
1321
|
+
|
|
1322
|
+
The project scan runs only when the projmux process has `PROJMUX_CWD` set
|
|
1323
|
+
to the repository, for example
|
|
1324
|
+
`PROJMUX_CWD="$PWD" projmux hook list --project`. Without it, project
|
|
1325
|
+
legacy files are ignored silently. A migrated `.projmux/config.toml` has
|
|
1326
|
+
new content, so it needs trust again.
|
|
1327
|
+
7. Check the execute bit of the program named in `run`. It needs its own
|
|
1328
|
+
execute bit, or use `run = "bash /path/script"`. Without it, stderr shows
|
|
1329
|
+
`[<event>] sh: 1: <path>: Permission denied` and then
|
|
1330
|
+
`projmux: <event> hook: global config hook: exited with status 126`.
|
|
1331
|
+
`.projmux/config.toml` itself does not need an execute bit.
|
|
1332
|
+
- **`projmux: <event> hook: project config ".projmux/config.toml" requires trust; skipping in non-interactive context`**
|
|
1333
|
+
or
|
|
1334
|
+
**`projmux: <event> hook: project config ".projmux/config.toml" hash changed; trusted sha256=<old> current sha256=<new>; skipping in non-interactive context`.**
|
|
935
1335
|
Run the same projmux command from an interactive terminal to approve the file,
|
|
936
|
-
or
|
|
1336
|
+
or run `projmux hook trust [<project>]`, which prints `trusted <repo>` and
|
|
1337
|
+
the sha256. Without `<project>` from a subdirectory, it trusts the nearest
|
|
1338
|
+
project root and prints the scope note from check 1 of "Nothing happens".
|
|
1339
|
+
Set `PROJMUX_PROJECT_HOOKS=off` if project-local execution should be
|
|
937
1340
|
disabled.
|
|
938
1341
|
- **`projmux: <event> hook: ... timed out after 5s`.** Long-running work
|
|
939
1342
|
belongs in a backgrounded child (`(slow-thing &) >/dev/null 2>&1`). The hook
|
|
940
|
-
itself must return within 5s or projmux kills it.
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1343
|
+
itself must return within 5s or projmux kills it. projmux runs the hook in
|
|
1344
|
+
its own process group and on timeout sends SIGKILL to that whole group, so
|
|
1345
|
+
children and grandchildren started by the hook stop too; if projmux itself
|
|
1346
|
+
receives SIGINT, SIGTERM, or SIGHUP while a hook runs, it kills the hook's
|
|
1347
|
+
group the same way before exiting. A process that left the group or session
|
|
1348
|
+
(for example via `setsid` or by daemonizing) is not killed, and SIGKILL gives
|
|
1349
|
+
no chance to clean up, so residue such as a stale `.git/index.lock` can
|
|
1350
|
+
remain; remove it by hand. Because the hook runs outside the terminal's
|
|
1351
|
+
foreground process group, a hook that reads `/dev/tty` directly is stopped
|
|
1352
|
+
(SIGTTIN) until the timeout. For `pre-create`, creation stops and the command
|
|
1353
|
+
fails with `pre-create hook for tmux session "<name>": timed out after 5s`.
|
|
1354
|
+
- **`projmux: <event> hook: ... exited with status N`.** The hook returned
|
|
1355
|
+
non-zero. projmux logs once and moves on. A global hook shows
|
|
1356
|
+
`global config hook: exited with status N`; a project hook shows
|
|
1357
|
+
`project config ".projmux/config.toml": exited with status N`. For
|
|
1358
|
+
`pre-create`, this warning is not logged. Creation stops and the command
|
|
1359
|
+
fails with `pre-create hook for tmux session "<name>": exited with status N`.
|
|
1360
|
+
- **Lines appear with `[post-create] `, `[pre-create] `, `[post-attach] `, or
|
|
1361
|
+
`[send-noti] ` prefixes.** Expected; hook stdout/stderr are multiplexed into
|
|
1362
|
+
projmux's stderr stream.
|