claude-code-runner 0.2.10 → 0.2.12

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/README.md CHANGED
@@ -54,7 +54,7 @@ npm install -g claude-code-runner
54
54
  >
55
55
  > For the fastest setup with pre-built image, use the official image by setting `buildImage: false` in your config. The default image (`ghcr.io/yanranxiaoxi/claude-code-runner:latest`) will be used automatically.
56
56
 
57
- Simply run in any git repository:
57
+ Simply run in any directory:
58
58
 
59
59
  ```bash
60
60
  claude-run
@@ -67,6 +67,19 @@ This will:
67
67
  3. Launch a web UI at `http://localhost:3456`
68
68
  4. Open your browser automatically
69
69
 
70
+ > [!WARNING]
71
+ >
72
+ > **Non-Git Directory Support**
73
+ >
74
+ > If you run `claude-run` in a non-git directory, the tool will prompt you to initialize a git repository. If you agree, it will automatically run `git init`, create an initial commit, and start the container. **Important**: After initialization, you must manually set up a remote repository (e.g., on GitHub) and configure the upstream to save your changes. Use commands like:
75
+ >
76
+ > ```bash
77
+ > git remote add origin <your-repo-url>
78
+ > git push -u origin main
79
+ > ```
80
+ >
81
+ > Without setting up the upstream, your changes will only exist locally in the container.
82
+
70
83
  > [!NOTE]
71
84
  >
72
85
  > **Working with Passphrase-Protected SSH Keys**
@@ -83,6 +96,13 @@ This will:
83
96
 
84
97
  ### Commands
85
98
 
99
+ #### Shortcuts
100
+
101
+ The following commands are shortcuts for `claude-run`:
102
+
103
+ - `clauderun`
104
+ - `ccrun`
105
+
86
106
  #### `claude-run` (default)
87
107
 
88
108
  Start a new container with web UI (recommended):
@@ -227,6 +247,7 @@ Create a `claude-run.config.json` file (see `claude-run.config.example.json` for
227
247
  "forwardSshKeys": true,
228
248
  "forwardGpgKeys": true,
229
249
  "forwardSshAgent": true,
250
+ "forwardGpgAgent": false,
230
251
  "enableGpgSigning": false
231
252
  }
232
253
  ```
@@ -254,6 +275,7 @@ Create a `claude-run.config.json` file (see `claude-run.config.example.json` for
254
275
  - `forwardSshKeys`: Forward SSH keys from `~/.ssh` to container (default: true)
255
276
  - `forwardGpgKeys`: Forward GPG keys from `~/.gnupg` to container (default: true)
256
277
  - `forwardSshAgent`: Forward SSH agent for passphrase-protected keys (default: true)
278
+ - `forwardGpgAgent`: Forward GPG agent for passphrase-protected GPG keys (default: false, requires explicit enabling)
257
279
  - `enableGpgSigning`: Enable GPG commit signing in container (default: false)
258
280
 
259
281
  #### Mount Configuration
@@ -362,7 +384,9 @@ Claude Code Runner now supports Podman as an alternative to Docker. The tool aut
362
384
  - **Custom socket paths**: Use the `dockerSocketPath` configuration option to specify a custom socket
363
385
  - **Environment variable**: Set `DOCKER_HOST` to override socket detection
364
386
 
365
- > **Important**: If you're using Podman in rootless mode, you need to enable the Podman socket service:
387
+ > [!IMPORTANT]
388
+ >
389
+ > If you're using Podman in rootless mode, you need to enable the Podman socket service:
366
390
  >
367
391
  > ```bash
368
392
  > systemctl --user enable --now podman.socket
@@ -410,6 +434,8 @@ claude-run # SSH agent is forwarded to container
410
434
 
411
435
  The container will use your host's SSH agent, so you don't need to enter the passphrase again.
412
436
 
437
+ > [!NOTE]
438
+ >
413
439
  > **Troubleshooting: SSH Agent Connection Failed**
414
440
  >
415
441
  > If running `ssh-add -l` inside the container shows "communication with agent failed":
@@ -427,17 +453,35 @@ The container will use your host's SSH agent, so you don't need to enter the pas
427
453
 
428
454
  #### GPG Key Support
429
455
 
430
- GPG keys from `~/.gnupg` are also automatically forwarded to the container. However, **GPG commit signing is disabled by default** to avoid passphrase prompts in non-interactive environments.
456
+ GPG keys from `~/.gnupg` are automatically forwarded to the container. **GPG commit signing is disabled by default** to avoid passphrase prompts in non-interactive environments.
431
457
 
432
- **To enable GPG commit signing**, add this to your `claude-run.config.json`:
458
+ **Three ways to use GPG:**
433
459
 
434
- ```json
435
- {
436
- "enableGpgSigning": true
437
- }
438
- ```
460
+ 1. **Direct GPG key usage (recommended for keys without passphrase)**
461
+ - GPG key files are automatically copied to the container
462
+ - No additional configuration needed
463
+ - Only works for keys without a passphrase
439
464
 
440
- > **Note**: GPG signing requires a GPG key without a passphrase, or proper GPG agent configuration. For security, consider using SSH commit signing instead.
465
+ 2. **GPG agent forwarding (recommended for password-protected keys)**
466
+ - Enable in your config:
467
+ ```json
468
+ {
469
+ "forwardGpgAgent": true,
470
+ "enableGpgSigning": true
471
+ }
472
+ ```
473
+ - Host GPG agent is automatically forwarded when container starts
474
+ - Supports password-protected keys (no need to enter passphrase in container)
475
+ - Requires `gpg-agent` running on your host machine
476
+
477
+ 3. **Completely disable GPG signing**
478
+ - Default behavior (if you don't set `enableGpgSigning`)
479
+ - GPG signing will be disabled even if your host `.gitconfig` has `commit.gpgsign = true`
480
+ - This prevents signing failures in containerized environments where `/dev/tty` is not accessible
481
+
482
+ > [!NOTE]
483
+ >
484
+ > For security, consider using SSH commit signing (if your Git server supports it). This way you don't need to configure GPG at all.
441
485
 
442
486
  #### Disabling SSH/GPG Forwarding
443
487
 
@@ -447,7 +491,8 @@ If you don't want to forward your keys, you can disable this feature:
447
491
  {
448
492
  "forwardSshKeys": false,
449
493
  "forwardGpgKeys": false,
450
- "forwardSshAgent": false
494
+ "forwardSshAgent": false,
495
+ "forwardGpgAgent": false
451
496
  }
452
497
  ```
453
498
 
package/README.zh-Hans.md CHANGED
@@ -52,7 +52,7 @@ npm install -g claude-code-runner
52
52
 
53
53
  > **提示**:为了最快的启动速度,使用预构建的官方镜像:在配置中设置 `buildImage: false`。默认镜像(`ghcr.io/yanranxiaoxi/claude-code-runner:latest`)会自动使用。
54
54
 
55
- 只需在任何 Git 仓库文件夹中运行:
55
+ 只需在任何目录中运行:
56
56
 
57
57
  ```bash
58
58
  claude-run
@@ -65,6 +65,19 @@ claude-run
65
65
  3. 在 `http://localhost:3456` 上启动 Web UI
66
66
  4. 自动打开浏览器
67
67
 
68
+ > [!WARNING]
69
+ >
70
+ > **非 Git 目录支持**
71
+ >
72
+ > 如果你在非 Git 目录中运行 `claude-run`,工具会提示你初始化 Git 仓库。如果你同意,它会自动运行 `git init`,创建初始提交,并启动容器。**重要**:初始化后,你必须手动设置远程仓库(例如在 GitHub 上)并配置上游以保存更改。使用如下命令:
73
+ >
74
+ > ```bash
75
+ > git remote add origin <你的仓库 URL>
76
+ > git push -u origin main
77
+ > ```
78
+ >
79
+ > 如果不设置上游,你的更改将仅存在于容器内的本地。
80
+
68
81
  > [!NOTE]
69
82
  >
70
83
  > **使用带密码的 SSH 密钥**
@@ -101,6 +114,13 @@ claude-run
101
114
 
102
115
  ### 命令
103
116
 
117
+ #### 快捷命令
118
+
119
+ 以下命令是 `claude-run` 的快捷方式:
120
+
121
+ - `clauderun`
122
+ - `ccrun`
123
+
104
124
  #### `claude-run` (默认)
105
125
 
106
126
  使用 Web UI 启动新容器(推荐):
@@ -247,6 +267,7 @@ claud-run update # 别名
247
267
  "forwardSshKeys": true,
248
268
  "forwardGpgKeys": true,
249
269
  "forwardSshAgent": true,
270
+ "forwardGpgAgent": false,
250
271
  "enableGpgSigning": false
251
272
  }
252
273
  ```
@@ -274,6 +295,7 @@ claud-run update # 别名
274
295
  - `forwardSshKeys`: 将 `~/.ssh` 中的 SSH 密钥转发到容器(默认:true)
275
296
  - `forwardGpgKeys`: 将 `~/.gnupg` 中的 GPG 密钥转发到容器(默认:true)
276
297
  - `forwardSshAgent`: 转发 SSH agent 以支持带密码的密钥(默认:true)
298
+ - `forwardGpgAgent`: 转发 GPG agent 以支持带密码的 GPG 密钥(默认:false,需显式启用)
277
299
  - `enableGpgSigning`: 在容器中启用 GPG 提交签名(默认:false)
278
300
 
279
301
  #### 挂载配置
@@ -382,7 +404,9 @@ Claude Code Runner 还支持 Podman 作为 Docker 的替代方案。该工具通
382
404
  - **自定义套接字路径**: 使用 `dockerSocketPath` 配置选项指定自定义套接字
383
405
  - **环境变量**: 设置 `DOCKER_HOST` 来覆盖套接字检测
384
406
 
385
- > **重要提示**:如果你使用 Podman 的 rootless(无根)模式,需要启用 Podman socket 服务:
407
+ > [!IMPORTANT]
408
+ >
409
+ > 如果你使用 Podman 的 rootless(无根)模式,需要启用 Podman socket 服务:
386
410
  >
387
411
  > ```bash
388
412
  > systemctl --user enable --now podman.socket
@@ -430,6 +454,8 @@ claude-run # SSH agent 会被转发到容器
430
454
 
431
455
  容器将使用宿主机的 SSH agent,因此你无需再次输入密码。
432
456
 
457
+ > [!NOTE]
458
+ >
433
459
  > **故障排除:SSH Agent 无法连接**
434
460
  >
435
461
  > 如果在容器内运行 `ssh-add -l` 显示 "communication with agent failed":
@@ -447,17 +473,35 @@ claude-run # SSH agent 会被转发到容器
447
473
 
448
474
  #### GPG 密钥支持
449
475
 
450
- 来自 `~/.gnupg` 的 GPG 密钥也会自动转发到容器。但是,**GPG 提交签名默认是禁用的**,以避免在非交互式环境中出现密码提示。
476
+ 来自 `~/.gnupg` 的 GPG 密钥会自动转发到容器。**GPG 提交签名默认是禁用的**,以避免在非交互式环境中出现密码提示。
451
477
 
452
- **要启用 GPG 提交签名**,在 `claude-run.config.json` 中添加:
478
+ **三种使用方式:**
453
479
 
454
- ```json
455
- {
456
- "enableGpgSigning": true
457
- }
458
- ```
480
+ 1. **直接使用 GPG 密钥(推荐用于无密码密钥)**
481
+ - GPG 密钥文件自动复制到容器
482
+ - 无需额外配置
483
+ - 仅适用于没有密码的 GPG 密钥
459
484
 
460
- > **注意**:GPG 签名需要一个没有密码的 GPG 密钥,或正确配置 GPG agent。为了安全起见,建议考虑使用 SSH 提交签名。
485
+ 2. **通过 GPG agent 转发(推荐用于密码保护的密钥)**
486
+ - 需要在配置中启用:
487
+ ```json
488
+ {
489
+ "forwardGpgAgent": true,
490
+ "enableGpgSigning": true
491
+ }
492
+ ```
493
+ - 宿主机 GPG agent 会在容器启动时自动转发
494
+ - 支持密码保护的密钥(无需在容器内输入密码)
495
+ - 需要宿主机 `gpg-agent` 正在运行
496
+
497
+ 3. **完全禁用 GPG 签名**
498
+ - 默认行为(如果不配置 `enableGpgSigning`)
499
+ - 即使宿主机配置了 `commit.gpgsign = true`,容器内也会禁用签名
500
+ - 这是为了避免在无法访问 `/dev/tty` 的容器环境中出现签名失败
501
+
502
+ > [!NOTE]
503
+ >
504
+ > 为了安全起见,建议使用 SSH 提交签名(如果 Git 服务器支持)。这样不需要配置 GPG 就能实现签名。
461
505
 
462
506
  #### 禁用 SSH/GPG 转发
463
507
 
@@ -467,7 +511,8 @@ claude-run # SSH agent 会被转发到容器
467
511
  {
468
512
  "forwardSshKeys": false,
469
513
  "forwardGpgKeys": false,
470
- "forwardSshAgent": false
514
+ "forwardSshAgent": false,
515
+ "forwardGpgAgent": false
471
516
  }
472
517
  ```
473
518
 
package/dist/cli.js CHANGED
@@ -17,7 +17,7 @@ const docker_config_1 = require("./docker-config");
17
17
  const index_1 = require("./index");
18
18
  const web_server_1 = require("./web-server");
19
19
  // Package info - injected at build time
20
- const currentVersion = '0.2.10';
20
+ const currentVersion = '0.2.12';
21
21
  const packageName = 'claude-code-runner';
22
22
  // Check for updates (non-blocking)
23
23
  async function checkForUpdates() {
@@ -9,7 +9,6 @@ export declare class ContainerManager {
9
9
  private ensureImage;
10
10
  private pullImage;
11
11
  private formatBytes;
12
- private buildDefaultImage;
13
12
  private buildImage;
14
13
  private createContainer;
15
14
  private prepareEnvironment;
@@ -1 +1 @@
1
- {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAe,aAAa,EAAE,MAAM,SAAS,CAAC;AAU1D,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,UAAU,CAA4C;gBAElD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa;IAK3C,KAAK,CAAC,eAAe,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC;YA+DpC,WAAW;YAiDX,SAAS;IA6FvB,OAAO,CAAC,WAAW;YASL,iBAAiB;YAqIjB,UAAU;YAkCV,eAAe;IAoC7B,OAAO,CAAC,kBAAkB;IA4J1B,OAAO,CAAC,cAAc;YA2GR,qBAAqB;YAwIrB,iBAAiB;YAqMjB,cAAc;YAgFd,wBAAwB;YAqJxB,gBAAgB;IAuExB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAY9B"}
1
+ {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAe,aAAa,EAAE,MAAM,SAAS,CAAC;AAU1D,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,UAAU,CAA4C;gBAElD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa;IAK3C,KAAK,CAAC,eAAe,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC;YA+DpC,WAAW;YAgDX,SAAS;IA6FvB,OAAO,CAAC,WAAW;YASL,UAAU;YAkCV,eAAe;IAoC7B,OAAO,CAAC,kBAAkB;IAsK1B,OAAO,CAAC,cAAc;YAyHR,qBAAqB;YAwIrB,iBAAiB;YAqMjB,cAAc;YAgFd,wBAAwB;YA4IxB,gBAAgB;IAuExB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAY9B"}
package/dist/container.js CHANGED
@@ -138,8 +138,7 @@ class ContainerManager {
138
138
  await this.pullImage(imageName);
139
139
  }
140
140
  catch (error) {
141
- console.log(chalk_1.default.yellow('⚠ Failed to pull image, using inline Dockerfile'));
142
- await this.buildDefaultImage(imageName);
141
+ throw new Error(`Failed to pull image '${imageName}' and no Dockerfile available. Please ensure docker/Dockerfile exists or use a pre-built image.`);
143
142
  }
144
143
  }
145
144
  }
@@ -222,130 +221,6 @@ class ContainerManager {
222
221
  const i = Math.floor(Math.log(bytes) / Math.log(k));
223
222
  return `${(bytes / k ** i).toFixed(1)} ${sizes[i]}`;
224
223
  }
225
- async buildDefaultImage(imageName) {
226
- const dockerfile = `
227
- FROM docker.io/library/almalinux:10
228
-
229
- # Install system dependencies
230
- RUN dnf install -y epel-release && dnf install -y \
231
- curl \\
232
- git \\
233
- openssh-clients \\
234
- python3 \\
235
- python3-pip \\
236
- gcc \\
237
- gcc-c++ \\
238
- make \\
239
- sudo \\
240
- vim \\
241
- jq \\
242
- ca-certificates \\
243
- gnupg2 \\
244
- inotify-tools \\
245
- rsync \\
246
- && dnf clean all
247
-
248
- # Install GitHub CLI
249
- RUN dnf install -y 'dnf-command(config-manager)' \\
250
- && dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo \\
251
- && dnf install -y gh
252
-
253
- # Install Claude Code using native installer
254
- RUN curl -fsSL https://claude.ai/install.sh | bash
255
-
256
- # Ensure claude is in PATH for all users
257
- ENV PATH="/root/.local/bin:\${PATH}"
258
-
259
- # Create a non-root user with sudo privileges
260
- RUN useradd -m -s /bin/bash claude && \\
261
- echo 'claude ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \\
262
- usermod -aG wheel claude
263
-
264
- # Switch to claude user to install Claude Code
265
- USER claude
266
-
267
- # Install Claude Code for claude user
268
- RUN curl -fsSL https://claude.ai/install.sh | bash
269
-
270
- # Ensure claude is in PATH for claude user
271
- RUN echo 'export PATH="$HOME/.local/bin:$PATH"' >> /home/claude/.bashrc
272
-
273
- # Switch back to root for remaining setup
274
- USER root
275
-
276
- # Create workspace directory and set ownership
277
- RUN mkdir -p /workspace && \\
278
- chown -R claude:claude /workspace
279
-
280
- # Switch to non-root user
281
- USER claude
282
- WORKDIR /workspace
283
-
284
- # Set up entrypoint
285
- ENTRYPOINT ["/bin/bash", "-c"]
286
- `;
287
- /*
288
- RUN echo '#!/bin/bash\\n\\
289
- # Allow the initial branch creation\\n\\
290
- if [ ! -f /tmp/.branch-created ]; then\\n\\
291
- /usr/bin/git "$@"\\n\\
292
- if [[ "$1" == "checkout" ]] && [[ "$2" == "-b" ]]; then\\n\\
293
- touch /tmp/.branch-created\\n\\
294
- fi\\n\\
295
- else\\n\\
296
- # After initial branch creation, prevent switching\\n\\
297
- if [[ "$1" == "checkout" ]] && [[ "$2" != "-b" ]]; then\\n\\
298
- echo "Branch switching is disabled in claude-code-runner"\\n\\
299
- exit 1\\n\\
300
- fi\\n\\
301
- if [[ "$1" == "switch" ]]; then\\n\\
302
- echo "Branch switching is disabled in claude-code-runner"\\n\\
303
- exit 1\\n\\
304
- fi\\n\\
305
- /usr/bin/git "$@"\\n\\
306
- fi' > /usr/local/bin/git && \\
307
- chmod +x /usr/local/bin/git
308
- # Create startup script
309
- RUN echo '#!/bin/bash\\n\\
310
- echo "Waiting for attachment..."\\n\\
311
- sleep 2\\n\\
312
- cd /workspace\\n\\
313
- git checkout -b "$1"\\n\\
314
- echo "Starting Claude Code on branch $1..."\\n\\
315
- exec claude --dangerously-skip-permissions' > /start-claude.sh && \\
316
- chmod +x /start-claude.sh */
317
- // Build image from string
318
- const pack = tar_stream_1.default.pack();
319
- // Add Dockerfile to tar
320
- pack.entry({ name: 'Dockerfile' }, dockerfile, (err) => {
321
- if (err)
322
- throw err;
323
- pack.finalize();
324
- });
325
- // Convert to buffer for docker
326
- const chunks = [];
327
- pack.on('data', (chunk) => chunks.push(chunk));
328
- await new Promise((resolve) => {
329
- pack.on('end', resolve);
330
- });
331
- const tarBuffer = node_buffer_1.Buffer.concat(chunks);
332
- const buildStream = await this.docker.buildImage(tarBuffer, {
333
- t: imageName,
334
- });
335
- // Wait for build to complete
336
- await new Promise((resolve, reject) => {
337
- this.docker.modem.followProgress(buildStream, (err, res) => {
338
- if (err)
339
- reject(err);
340
- else
341
- resolve(res);
342
- }, (event) => {
343
- if (event.stream) {
344
- node_process_1.default.stdout.write(event.stream);
345
- }
346
- });
347
- });
348
- }
349
224
  async buildImage(dockerfilePath, imageName) {
350
225
  const buildContext = node_path_1.default.dirname(dockerfilePath);
351
226
  const buildStream = await this.docker.buildImage({
@@ -493,8 +368,18 @@ exec claude --dangerously-skip-permissions' > /start-claude.sh && \\
493
368
  if (sshAuthSock && fs.existsSync(sshAuthSock)) {
494
369
  env.push('SSH_AUTH_SOCK=/tmp/.ssh-agent-sock');
495
370
  }
496
- // GPG agent - disable TTY requirement for non-interactive use
497
- env.push('GPG_TTY=');
371
+ // GPG agent forwarding - if enabled and socket is available
372
+ const gpgAgentSocket = node_path_1.default.join(os.homedir(), '.gnupg', 'S.gpg-agent');
373
+ if (this.config.forwardGpgAgent === true && fs.existsSync(gpgAgentSocket)) {
374
+ // Point to the container's GPG agent socket relay
375
+ env.push('GPG_TTY=/dev/tty');
376
+ // Set GNUPGHOME to use the forwarded agent
377
+ env.push('GNUPGHOME=/home/claude/.gnupg');
378
+ }
379
+ else {
380
+ // Disable TTY requirement for non-interactive use (no agent available)
381
+ env.push('GPG_TTY=');
382
+ }
498
383
  // Pass GPG signing preference to container
499
384
  if (this.config.enableGpgSigning) {
500
385
  env.push('GIT_COMMIT_GPG_SIGN=true');
@@ -541,6 +426,19 @@ exec claude --dangerously-skip-permissions' > /start-claude.sh && \\
541
426
  volumes.push(`${sshAuthSock}:/tmp/.ssh-agent-sock:rw`);
542
427
  console.log(chalk_1.default.blue('✓ SSH agent forwarding enabled'));
543
428
  }
429
+ // Mount GPG agent socket if available and explicitly enabled (for GPG signing)
430
+ const forwardGpgAgent = this.config.forwardGpgAgent === true; // Default: false - must be explicit
431
+ if (forwardGpgAgent) {
432
+ // GPG agent socket is typically at ~/.gnupg/S.gpg-agent
433
+ const gpgAgentSocket = node_path_1.default.join(os.homedir(), '.gnupg', 'S.gpg-agent');
434
+ if (fs.existsSync(gpgAgentSocket)) {
435
+ volumes.push(`${gpgAgentSocket}:/tmp/.gpg-agent-sock:rw`);
436
+ console.log(chalk_1.default.blue('✓ GPG agent forwarding enabled'));
437
+ }
438
+ else {
439
+ console.log(chalk_1.default.yellow('⚠ GPG agent socket not found - GPG signing may not work'));
440
+ }
441
+ }
544
442
  // Add custom volumes (legacy format)
545
443
  if (this.config.volumes) {
546
444
  volumes.push(...this.config.volumes);
@@ -949,11 +847,16 @@ exec /bin/bash`;
949
847
  git config --global --add safe.directory /workspace &&
950
848
  # Clean up macOS resource fork files in git pack directory
951
849
  find .git/objects/pack -name "._pack-*.idx" -type f -delete 2>/dev/null || true &&
952
- # Configure git commit signing - disable GPG signing by default to avoid passphrase prompts
953
- # Users can enable it by setting GIT_COMMIT_GPG_SIGN=true in environment
954
- if [ -z "$GIT_COMMIT_GPG_SIGN" ] || [ "$GIT_COMMIT_GPG_SIGN" != "true" ]; then
850
+ # Configure git commit signing
851
+ # Disable GPG signing by default to avoid passphrase prompts in non-interactive environments
852
+ # Even if host .gitconfig has commit.gpgsign=true, override it unless explicitly enabled
853
+ ENABLE_GPG_SIGNING="${this.config.enableGpgSigning === true ? 'true' : 'false'}" &&
854
+ if [ "$ENABLE_GPG_SIGNING" = "true" ]; then
855
+ git config --global commit.gpgsign true
856
+ echo "✓ GPG signing enabled"
857
+ else
955
858
  git config --global commit.gpgsign false
956
- echo "✓ GPG signing disabled (set GIT_COMMIT_GPG_SIGN=true to enable)"
859
+ echo "✓ GPG signing disabled (enable with enableGpgSigning: true in config)"
957
860
  fi &&
958
861
  # Start SSH agent if not already running and we have keys
959
862
  if [ -d "/home/claude/.ssh" ] && [ -z "$SSH_AUTH_SOCK" ]; then
@@ -976,33 +879,17 @@ exec /bin/bash`;
976
879
  if [ -n "${prFetchRef || ''}" ]; then
977
880
  echo "• Fetching PR branch..." &&
978
881
  git fetch origin ${prFetchRef} &&
979
- if git show-ref --verify --quiet refs/heads/"${branchName}"; then
980
- git checkout "${branchName}" &&
981
- echo "✓ Switched to existing PR branch: ${branchName}"
982
- else
983
- git checkout "${branchName}" &&
984
- echo "✓ Checked out PR branch: ${branchName}"
985
- fi
882
+ git checkout -B "${branchName}" FETCH_HEAD &&
883
+ echo "✓ Checked out PR branch: ${branchName}"
986
884
  elif [ -n "${remoteFetchRef || ''}" ]; then
987
885
  echo "• Fetching remote branch..." &&
988
886
  git fetch origin &&
989
- if git show-ref --verify --quiet refs/heads/"${branchName}"; then
990
- git checkout "${branchName}" &&
991
- git pull origin "${branchName}" &&
992
- echo "✓ Switched to existing remote branch: ${branchName}"
993
- else
994
- git checkout -b "${branchName}" "${remoteFetchRef}" &&
995
- echo "✓ Created local branch from remote: ${branchName}"
996
- fi
887
+ git checkout -B "${branchName}" "${remoteFetchRef}" &&
888
+ echo "✓ Checked out remote branch: ${branchName}"
997
889
  else
998
890
  # Regular branch creation
999
- if git show-ref --verify --quiet refs/heads/"${branchName}"; then
1000
- git checkout "${branchName}" &&
1001
- echo "✓ Switched to existing branch: ${branchName}"
1002
- else
1003
- git checkout -b "${branchName}" &&
1004
- echo "✓ Created new branch: ${branchName}"
1005
- fi
891
+ git checkout -B "${branchName}" &&
892
+ echo "✓ Checked out branch: ${branchName}"
1006
893
  fi &&
1007
894
  cat > /home/claude/start-session.sh << 'EOF'
1008
895
  ${startupScript}
@@ -1028,6 +915,8 @@ EOF
1028
915
  || output.includes('✓ Switched to existing remote branch')
1029
916
  || output.includes('✓ Switched to existing PR branch')
1030
917
  || output.includes('✓ Checked out PR branch')
918
+ || output.includes('✓ Checked out remote branch')
919
+ || output.includes('✓ Checked out branch')
1031
920
  || output.includes('✓ Created local branch from remote'))
1032
921
  && output.includes('✓ Startup script created')) {
1033
922
  resolve();