mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 05:59:43 +02:00
chore: version packages
This commit is contained in:
+4
-2
@@ -68,6 +68,7 @@ import {
|
||||
getClaudeCliVersion,
|
||||
getClaudeBinaryPath,
|
||||
spawnPtyWithHelperRepair,
|
||||
resolveLocalShell,
|
||||
} from './utils/index.js';
|
||||
import {
|
||||
MAX_TERMINAL_BUFFER_SIZE,
|
||||
@@ -1898,8 +1899,9 @@ export class Session extends EventEmitter {
|
||||
|
||||
this._resetBuffers();
|
||||
|
||||
// Use user's default shell or bash
|
||||
const shell = process.env.SHELL || '/bin/bash';
|
||||
// Use user's default shell, falling back to a shell that actually exists.
|
||||
// Shared with the tmux pane command so both paths launch the same binary.
|
||||
const shell = resolveLocalShell();
|
||||
console.log(
|
||||
'[Session] Starting shell session with:',
|
||||
shell + (this._useMux ? ` (with ${this._mux!.backend})` : '')
|
||||
|
||||
+8
-1
@@ -71,6 +71,7 @@ import {
|
||||
resolveCodexDir,
|
||||
resolveGeminiDir,
|
||||
resolveAntigravityDir,
|
||||
resolveLocalShell,
|
||||
} from './utils/index.js';
|
||||
import type {
|
||||
TerminalMultiplexer,
|
||||
@@ -773,7 +774,13 @@ export function buildSpawnCommand(options: {
|
||||
if (options.mode === 'antigravity') {
|
||||
return buildAntigravityCommand(options.antigravityConfig);
|
||||
}
|
||||
return '$SHELL';
|
||||
// #208: NOT the literal '$SHELL'. This string is embedded in the `bash -c "…"`
|
||||
// argument of the respawn-pane line, which execSync runs through `/bin/sh -c`,
|
||||
// so a `$SHELL` here is expanded by the SERVER process's shell against the
|
||||
// SERVER process's env — empty in containers and system systemd units, leaving
|
||||
// the pane command ending in a dangling `&&` ("syntax error: unexpected end of
|
||||
// file", pane dead on arrival). Resolve it in Node and quote the result.
|
||||
return shellescape(resolveLocalShell());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,6 +26,7 @@ export { isSafePushEndpoint } from './push-endpoint-validation.js';
|
||||
export { stringSimilarity, fuzzyPhraseMatch, todoContentHash } from './string-similarity.js';
|
||||
export { assertNever } from './type-safety.js';
|
||||
export { wrapWithNice } from './nice-wrapper.js';
|
||||
export { resolveLocalShell } from './shell-resolver.js';
|
||||
export { findClaudeDir, getAugmentedPath, getClaudeCliVersion, getClaudeBinaryPath } from './claude-cli-resolver.js';
|
||||
export { spawnPtyWithHelperRepair } from './node-pty-repair.js';
|
||||
export { resolveOpenCodeDir } from './opencode-cli-resolver.js';
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @fileoverview Resolve a real, launchable login shell for `mode: 'shell'` sessions.
|
||||
*
|
||||
* The tmux pane command for a local shell session used to be the literal string
|
||||
* `$SHELL`. That string is embedded in the `bash -c "…"` argument of the
|
||||
* `respawn-pane` line, which `execSync` hands to `/bin/sh -c` — so `$SHELL` was
|
||||
* expanded by the SERVER process's shell (not the pane's), against the SERVER
|
||||
* process's env. Containers and system-level systemd units do not set `SHELL`,
|
||||
* so the expansion produced an empty string and the pane command ended in a
|
||||
* dangling `&&`:
|
||||
*
|
||||
* bash -c "cd \"/case\" && ulimit … && export … && "
|
||||
* -> bash: -c: line 1: syntax error: unexpected end of file
|
||||
*
|
||||
* The pane then died instantly (status 2) while tmux creation itself reported
|
||||
* success, which is exactly what issue #208 saw. Resolving the shell HERE, in
|
||||
* Node, removes the shell-expansion layer entirely and guarantees a non-empty
|
||||
* absolute path.
|
||||
*
|
||||
* @module utils/shell-resolver
|
||||
*/
|
||||
|
||||
import { accessSync, constants } from 'node:fs';
|
||||
import { userInfo } from 'node:os';
|
||||
|
||||
/** Last-resort shells, in preference order. `/bin/sh` exists on every POSIX host. */
|
||||
const FALLBACK_SHELLS = ['/bin/bash', '/bin/zsh', '/bin/sh'];
|
||||
|
||||
/**
|
||||
* Shells that exist and are executable but immediately exit — a service account's
|
||||
* passwd entry commonly points at one, which would look identical to the crash
|
||||
* this module exists to prevent.
|
||||
*/
|
||||
const NON_INTERACTIVE_SHELLS = new Set(['nologin', 'false', 'true', 'sync']);
|
||||
|
||||
function isUsableShell(candidate: string): boolean {
|
||||
if (!candidate.startsWith('/')) return false;
|
||||
const base = candidate.slice(candidate.lastIndexOf('/') + 1);
|
||||
if (NON_INTERACTIVE_SHELLS.has(base)) return false;
|
||||
try {
|
||||
accessSync(candidate, constants.X_OK);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an absolute path to an interactive shell, preferring the user's own.
|
||||
*
|
||||
* Order: `$SHELL` -> the passwd entry -> `/bin/bash` -> `/bin/zsh` -> `/bin/sh`.
|
||||
* Every candidate must be an absolute path to an executable that is not a
|
||||
* nologin-style stub. Always returns a non-empty string.
|
||||
*/
|
||||
export function resolveLocalShell(): string {
|
||||
const candidates: string[] = [];
|
||||
|
||||
const envShell = process.env.SHELL?.trim();
|
||||
if (envShell) candidates.push(envShell);
|
||||
|
||||
try {
|
||||
// Throws when the uid has no /etc/passwd entry (common for `--user` containers).
|
||||
const passwdShell = userInfo().shell?.trim();
|
||||
if (passwdShell) candidates.push(passwdShell);
|
||||
} catch {
|
||||
/* no passwd entry — fall through to the static fallbacks */
|
||||
}
|
||||
|
||||
candidates.push(...FALLBACK_SHELLS);
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (isUsableShell(candidate)) return candidate;
|
||||
}
|
||||
|
||||
// Nothing was verifiable (exotic/read-restricted image). /bin/sh is still the
|
||||
// best guess and is far better than emitting an empty command.
|
||||
return '/bin/sh';
|
||||
}
|
||||
Reference in New Issue
Block a user