1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
//! History-safe `GH_TOKEN`/`GH_USER` delivery for managed Claude Code spawns
//! (#3025 review follow-up).
//!
//! Why: the original implementation embedded `GH_TOKEN='<value>'` literally
//! in the `env …` command line sent to the tmux pane via `send-keys`. That
//! command line is TYPED into the pane's login shell, so the secret landed in
//! `~/.zsh_history`/`~/.bash_history` on disk, and was transiently visible in
//! `ps` output for the `env` process itself (its own assignment argv). A
//! managed session's `gh` identity must be deterministic without leaving the
//! token readable outside the process environment.
//! What: [`write_gh_env_file`] writes one `export NAME='value'` line per
//! resolved `(name, value)` pair to a fresh, mode-0600 (owner-only) temp
//! file — never world- or group-readable, even momentarily (mirrors the
//! established `secure_write` pattern already used by
//! `core::oauth_token`/`session_manager::snapshot` for the same
//! create-with-restrictive-mode-atomically requirement). [`gh_env_source_prefix`]
//! renders the shell fragment that `.` (source)s the file INTO THE CURRENT
//! SHELL — so the plain `env` invocation that follows inherits `GH_TOKEN`/
//! `GH_USER` automatically, with no literal value ever appearing in the typed
//! command or any process's argv — then immediately `rm -f`s it, so the
//! value touches disk only for the instant between write and source. An
//! empty `gh_env` slice writes nothing and yields an empty prefix, keeping an
//! unconfigured project's spawn command byte-identical to pre-#3025 behaviour.
//! Test: `write_gh_env_file_writes_export_lines_in_order`, `write_gh_env_file_empty_is_none`,
//! `write_gh_env_file_is_mode_0600` (unix only), `gh_env_source_prefix_sources_and_deletes`,
//! `gh_env_source_prefix_none_is_empty` (`claude_code_gh_env_tests.rs`).
use ;
use shell_single_quote;
/// Write `data` to `path` with Unix mode 0600, race-free (mirrors
/// `core::oauth_token::secure_write` / `session_manager::snapshot::secure_write`
/// — a third small private copy rather than a new cross-module export, per
/// this crate's existing precedent for this exact primitive).
/// Write a resolved `gh_env` (`GH_TOKEN`/`GH_USER`) to a mode-0600 temp file
/// of `export NAME='value'` lines, or `None` when there is nothing to write.
///
/// Why: isolates the file-write step so `ClaudeCodeAdapter::spawn`/
/// `spawn_resume` stay a one-line call; testable without touching a real
/// tmux pane.
/// What: `gh_env.is_empty()` → `None` (no file, no regression for the common
/// unconfigured case). Otherwise writes one line per pair, single-quoting
/// each value via the shared [`shell_single_quote`], to
/// `<tmp>/trusty-mpm-gh-env-<uuid>.sh`; returns `None` (logged) on any write
/// failure so the spawn proceeds without `GH_TOKEN` rather than failing.
/// Test: `write_gh_env_file_writes_export_lines_in_order`, `write_gh_env_file_empty_is_none`,
/// `write_gh_env_file_is_mode_0600`.
pub
/// Render the `. '<path>'; rm -f '<path>'; ` shell prefix that sources
/// `file` into the current shell and immediately deletes it, or an empty
/// string when `file` is `None`.
///
/// Why: `.` (POSIX `source`) runs the file's `export` statements in the
/// CURRENT shell process, so every command that follows on the same command
/// line — including the `env …` invocation `env_bin_prefix` builds — inherits
/// `GH_TOKEN`/`GH_USER` from the process environment with no literal value in
/// its own argv. Placed BEFORE `env_bin_prefix` in [`super::spawn_command`]/
/// [`super::resume_command`]'s body, so sourcing always happens before the
/// value is needed.
/// What: `None` → `""` (no regression for a project with no pinned
/// `gh_account`). `Some(path)` → the source-and-delete fragment,
/// single-quoted via [`shell_single_quote`].
/// Test: `gh_env_source_prefix_sources_and_deletes`, `gh_env_source_prefix_none_is_empty`.
pub