Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions scripts/build-patched-zsh.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/usr/bin/env bash
set -euo pipefail

# Build the Codex-patched zsh used by shell-tool-mcp.
#
# Defaults are intentionally override-friendly:
# CODEX_REPO Codex repo root containing shell-tool-mcp/patches
# ZSH_SRC_DIR Working clone location for zsh source
# INSTALL_PREFIX Install prefix for make install
# JOBS Parallel jobs for make
# ZSH_REMOTE Upstream zsh remote
# INSTALL_DOCS Set to 1 to run full `make install` (includes manpages)

ZSH_COMMIT="77045ef899e53b9598bebc5a41db93a548a40ca6"
ZSH_REMOTE="${ZSH_REMOTE:-https://git.code.sf.net/p/zsh/code}"
INSTALL_PREFIX="${INSTALL_PREFIX:-$HOME/.local/codex-zsh-${ZSH_COMMIT:0:7}}"

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"

resolve_codex_repo() {
if [[ -n "${CODEX_REPO:-}" ]]; then
printf '%s\n' "$CODEX_REPO"
return
fi

# Most reliable when this script is run from inside the codex repo.
local from_script
from_script="$(cd -- "$SCRIPT_DIR/.." && pwd)"
if [[ -f "$from_script/shell-tool-mcp/patches/zsh-exec-wrapper.patch" ]]; then
printf '%s\n' "$from_script"
return
fi

# Common local workspace layouts.
if [[ -f "$HOME/repos/codex/shell-tool-mcp/patches/zsh-exec-wrapper.patch" ]]; then
printf '%s\n' "$HOME/repos/codex"
return
fi

if [[ -f "$HOME/code/codex/shell-tool-mcp/patches/zsh-exec-wrapper.patch" ]]; then
printf '%s\n' "$HOME/code/codex"
return
fi

echo "Could not locate codex repo. Set CODEX_REPO=/path/to/codex." >&2
exit 1
}

resolve_zsh_src_dir() {
if [[ -n "${ZSH_SRC_DIR:-}" ]]; then
printf '%s\n' "$ZSH_SRC_DIR"
return
fi

if [[ -d "$HOME/repos/zsh/.git" ]]; then
printf '%s\n' "$HOME/repos/zsh"
return
fi

if [[ -d "$HOME/code/zsh/.git" ]]; then
printf '%s\n' "$HOME/code/zsh"
return
fi

# Fallback for users without an existing clone.
printf '%s\n' "$HOME/src/zsh-code"
}

CODEX_REPO="$(resolve_codex_repo)"
ZSH_SRC_DIR="$(resolve_zsh_src_dir)"
PATCH_FILE="$CODEX_REPO/shell-tool-mcp/patches/zsh-exec-wrapper.patch"

if [[ ! -f "$PATCH_FILE" ]]; then
echo "Patch file not found: $PATCH_FILE" >&2
exit 1
fi

if [[ -z "${JOBS:-}" ]]; then
if command -v nproc >/dev/null 2>&1; then
JOBS="$(nproc)"
elif command -v sysctl >/dev/null 2>&1; then
JOBS="$(sysctl -n hw.ncpu)"
else
JOBS=4
fi
fi

mkdir -p "$(dirname -- "$ZSH_SRC_DIR")"

if [[ ! -d "$ZSH_SRC_DIR/.git" ]]; then
git clone "$ZSH_REMOTE" "$ZSH_SRC_DIR"
fi

git -C "$ZSH_SRC_DIR" fetch --depth 1 origin "$ZSH_COMMIT"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor ZSH_REMOTE when fetching existing clones

The script advertises ZSH_REMOTE as an override, but the fetch step is hardcoded to origin. If ZSH_SRC_DIR already exists and origin points elsewhere (or lacks the pinned commit), the build fails or fetches from the wrong upstream. This makes the documented override ineffective for common existing-clone workflows.

Useful? React with 👍 / 👎.

git -C "$ZSH_SRC_DIR" checkout --detach -f "$ZSH_COMMIT"
git -C "$ZSH_SRC_DIR" reset --hard "$ZSH_COMMIT"

if git -C "$ZSH_SRC_DIR" apply --reverse --check "$PATCH_FILE" >/dev/null 2>&1; then
echo "Patch already applied: $PATCH_FILE"
else
git -C "$ZSH_SRC_DIR" apply --check "$PATCH_FILE"
git -C "$ZSH_SRC_DIR" apply "$PATCH_FILE"
fi

(
cd "$ZSH_SRC_DIR"
./Util/preconfig
./configure --prefix="$INSTALL_PREFIX"
make -j"$JOBS"
if [[ "${INSTALL_DOCS:-0}" == "1" ]]; then
make install
else
make install.bin
fi
)

cat <<OUT

Built patched zsh successfully.
Binary:
$INSTALL_PREFIX/bin/zsh

Quick checks:
$INSTALL_PREFIX/bin/zsh --version
$INSTALL_PREFIX/bin/zsh -fc '/bin/echo smoke-zsh'
OUT
204 changes: 199 additions & 5 deletions shell-tool-mcp/patches/zsh-exec-wrapper.patch
Original file line number Diff line number Diff line change
@@ -1,34 +1,228 @@
diff --git a/Src/exec.c b/Src/exec.c
index 27bca11..baea760 100644
index 27bca110c..4e5000be3 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -507,7 +507,9 @@ zexecve(char *pth, char **argv, char **newenvp)
@@ -507,7 +507,12 @@ zexecve(char *pth, char **argv, char **newenvp)
{
int eno;
static char buf[PATH_MAX * 2+1];
- char **eep;
+ char **eep, **exec_argv;
+ char **eep, **exec_argv, **wrapper_envp;
+ char *orig_pth = pth;
+ char *exec_wrapper;
+ char wrapper_origin_buf[64];
+ int wrapper_origin_len;
+ int wrapper_envc, wrapper_origin_idx;

unmetafy(pth, NULL);
for (eep = argv; *eep; eep++)
@@ -526,8 +528,17 @@ zexecve(char *pth, char **argv, char **newenvp)
@@ -526,8 +531,51 @@ zexecve(char *pth, char **argv, char **newenvp)

if (newenvp == NULL)
newenvp = environ;
+ exec_argv = argv;
+ /* Use the command env by default; wrapper mode may replace this with an
+ * explicitly rebuilt envp so origin metadata is guaranteed to be present
+ * even when execve does not use the process-global environ. */
+ wrapper_envp = newenvp;
+ if ((exec_wrapper = getenv("EXEC_WRAPPER")) &&
+ *exec_wrapper && !inblank(*exec_wrapper)) {
+ /* zexecve callers provide spare argv slots before argv[0] for
+ * interpreter dispatch; reuse those slots to trampoline through the
+ * wrapper binary while preserving the original target path. */
+ exec_argv = argv - 2;
+ exec_argv[0] = exec_wrapper;
+ exec_argv[1] = orig_pth;
+ wrapper_origin_len = sprintf(wrapper_origin_buf,
+ "CODEX_ZSH_EXEC_BRIDGE_WRAPPER_ORIGIN=%d",
+ zsh_exec_wrapper_origin);
+ if (wrapper_origin_len > 0 &&
+ wrapper_origin_len < (int)sizeof(wrapper_origin_buf)) {
+ wrapper_envc = 0;
+ wrapper_origin_idx = -1;
+ for (eep = newenvp; *eep; eep++) {
+ if (strncmp(*eep, "CODEX_ZSH_EXEC_BRIDGE_WRAPPER_ORIGIN=",
+ sizeof("CODEX_ZSH_EXEC_BRIDGE_WRAPPER_ORIGIN=") - 1) == 0)
+ wrapper_origin_idx = wrapper_envc;
+ wrapper_envc++;
+ }
+ wrapper_envp = zalloc((wrapper_envc + 2) * sizeof(char *));
+ if (wrapper_origin_idx >= 0) {
+ for (wrapper_envc = 0; newenvp[wrapper_envc]; wrapper_envc++)
+ wrapper_envp[wrapper_envc] =
+ wrapper_envc == wrapper_origin_idx ?
+ wrapper_origin_buf : newenvp[wrapper_envc];
+ wrapper_envp[wrapper_envc] = NULL;
+ } else {
+ for (wrapper_envc = 0; newenvp[wrapper_envc]; wrapper_envc++)
+ wrapper_envp[wrapper_envc] = newenvp[wrapper_envc];
+ wrapper_envp[wrapper_envc++] = wrapper_origin_buf;
+ wrapper_envp[wrapper_envc] = NULL;
+ }
+ }
+ pth = exec_wrapper;
+ }
winch_unblock();
- execve(pth, argv, newenvp);
+ execve(pth, exec_argv, newenvp);
+ execve(pth, exec_argv, wrapper_envp);
+ pth = orig_pth;

/* If the execve returns (which in general shouldn't happen), *
* then check for an errno equal to ENOEXEC. This errno is set *
diff --git a/Src/init.c b/Src/init.c
index 20b8cc7fd..b6d5c9c9a 100644
--- a/Src/init.c
+++ b/Src/init.c
@@ -59,6 +59,9 @@ int underscoreused;
/**/
int sourcelevel;

+/**/
+int zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_USER_COMMAND;
+
/* the shell tty fd */

/**/
@@ -1450,14 +1453,25 @@ init_signals(void)
void
run_init_scripts(void)
{
+ int old_origin;
+
noerrexit = NOERREXIT_EXIT | NOERREXIT_RETURN | NOERREXIT_SIGNAL;
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_USER_COMMAND;

if (EMULATION(EMULATE_KSH|EMULATE_SH)) {
- if (islogin)
+ if (islogin) {
+ old_origin = zsh_exec_wrapper_origin;
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_LOGIN_STARTUP;
source("/etc/profile");
+ zsh_exec_wrapper_origin = old_origin;
+ }
if (unset(PRIVILEGED)) {
- if (islogin)
+ if (islogin) {
+ old_origin = zsh_exec_wrapper_origin;
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_LOGIN_STARTUP;
sourcehome(".profile");
+ zsh_exec_wrapper_origin = old_origin;
+ }

if (interact) {
noerrs = 2;
@@ -1467,16 +1481,26 @@ run_init_scripts(void)
if (!parsestr(&s)) {
singsub(&s);
noerrs = 0;
+ old_origin = zsh_exec_wrapper_origin;
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_RC_STARTUP;
source(s);
+ zsh_exec_wrapper_origin = old_origin;
}
}
noerrs = 0;
}
- } else
+ } else {
+ old_origin = zsh_exec_wrapper_origin;
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_LOGIN_STARTUP;
source("/etc/suid_profile");
+ zsh_exec_wrapper_origin = old_origin;
+ }
} else {
#ifdef GLOBAL_ZSHENV
+ old_origin = zsh_exec_wrapper_origin;
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_RC_STARTUP;
source(GLOBAL_ZSHENV);
+ zsh_exec_wrapper_origin = old_origin;
#endif

if (isset(RCS) && unset(PRIVILEGED))
@@ -1492,33 +1516,61 @@ run_init_scripts(void)
}
}

+ old_origin = zsh_exec_wrapper_origin;
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_RC_STARTUP;
sourcehome(".zshenv");
+ zsh_exec_wrapper_origin = old_origin;
}
if (islogin) {
#ifdef GLOBAL_ZPROFILE
- if (isset(RCS) && isset(GLOBALRCS))
+ if (isset(RCS) && isset(GLOBALRCS)) {
+ old_origin = zsh_exec_wrapper_origin;
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_LOGIN_STARTUP;
source(GLOBAL_ZPROFILE);
+ zsh_exec_wrapper_origin = old_origin;
+ }
#endif
- if (isset(RCS) && unset(PRIVILEGED))
+ if (isset(RCS) && unset(PRIVILEGED)) {
+ old_origin = zsh_exec_wrapper_origin;
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_LOGIN_STARTUP;
sourcehome(".zprofile");
+ zsh_exec_wrapper_origin = old_origin;
+ }
}
if (interact) {
#ifdef GLOBAL_ZSHRC
- if (isset(RCS) && isset(GLOBALRCS))
+ if (isset(RCS) && isset(GLOBALRCS)) {
+ old_origin = zsh_exec_wrapper_origin;
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_RC_STARTUP;
source(GLOBAL_ZSHRC);
+ zsh_exec_wrapper_origin = old_origin;
+ }
#endif
- if (isset(RCS) && unset(PRIVILEGED))
+ if (isset(RCS) && unset(PRIVILEGED)) {
+ old_origin = zsh_exec_wrapper_origin;
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_RC_STARTUP;
sourcehome(".zshrc");
+ zsh_exec_wrapper_origin = old_origin;
+ }
}
if (islogin) {
#ifdef GLOBAL_ZLOGIN
- if (isset(RCS) && isset(GLOBALRCS))
+ if (isset(RCS) && isset(GLOBALRCS)) {
+ old_origin = zsh_exec_wrapper_origin;
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_LOGIN_STARTUP;
source(GLOBAL_ZLOGIN);
+ zsh_exec_wrapper_origin = old_origin;
+ }
#endif
- if (isset(RCS) && unset(PRIVILEGED))
+ if (isset(RCS) && unset(PRIVILEGED)) {
+ old_origin = zsh_exec_wrapper_origin;
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_LOGIN_STARTUP;
sourcehome(".zlogin");
+ zsh_exec_wrapper_origin = old_origin;
+ }
}
}
+ zsh_exec_wrapper_origin = EXEC_WRAPPER_ORIGIN_USER_COMMAND;
noerrexit = 0;
nohistsave = 0;
}
diff --git a/Src/zsh.h b/Src/zsh.h
index 5bda04e88..c0750cf45 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -2215,6 +2215,14 @@ enum source_return {
SOURCE_ERROR = 2
};

+enum exec_wrapper_origin {
+ EXEC_WRAPPER_ORIGIN_USER_COMMAND = 0,
+ EXEC_WRAPPER_ORIGIN_LOGIN_STARTUP = 1,
+ EXEC_WRAPPER_ORIGIN_RC_STARTUP = 2
+};
+
+extern int zsh_exec_wrapper_origin;
+
enum noerrexit_bits {
/* Suppress ERR_EXIT and traps: global */
NOERREXIT_EXIT = 1,
Loading