Skip to content

Commit 3c70fb8

Browse files
binarypieclaude
andauthored
fix(ai-dev): stop entrypoint clobbering host ~/.local/bin and resolve TERM=xterm-ghostty (#182)
## Summary Fixes to the `ai-dev` and `nvim-dev` development container environments. ### 1. Stop the ai-dev entrypoint clobbering host `~/.local/bin/{claude,agy}` `ai-entrypoint.sh` force-symlinked `$HOME/.local/bin/{claude,agy}` (and `$HOME/.local/share/claude`) to their `/home/linuxbrew` install paths. Because the wrappers mount the current directory at its real path (`-v "$(pwd):$(pwd):rw"`) and `$HOME` is the host home, running `claude`/`agy` from the home directory wrote those symlinks straight to the host — replacing the real `~/.local/bin/claude` and `~/.local/bin/agy` wrapper scripts with dangling links into the image (which looked like Homebrew packages because `linuxbrew` is the build user, though both tools are actually curl-installed). The tools are already on `PATH` via `/home/linuxbrew/.local/bin`, so the symlinks were redundant. Removed the symlink block entirely. ### 2. Resolve `TERM=xterm-ghostty` in the ai-dev container The ai-dev wrappers never propagated `TERM`, so podman defaulted it to a generic value and Ghostty's terminal capabilities were lost, producing awkward escape sequences in the Claude Code / Antigravity TUIs. - Pass `TERM` through to the container in `claude.sh` / `agy.sh` / `enter.sh` - Install the `xterm-ghostty` terminfo explicitly in the ai-dev image so it is self-sufficient even if the nvim-dev base changes - Add an entrypoint fallback to `xterm-256color` when `TERM` is empty or has no terminfo entry ### 3. `xterm-ghostty` TERM fallback safety net in nvim-dev nvim-dev already bakes the `xterm-ghostty` terminfo and inherits `TERM` from the host via distrobox. Added a shell-init fallback (POSIX `/etc/profile.d` + fish `conf.d`) that degrades to `xterm-256color` when `TERM` has no terminfo entry, so interactive TUIs never emit raw escape sequences if `xterm-ghostty` is ever unavailable. ## Recovery for already-broken ai-dev installs ```bash rm -f ~/.local/bin/claude ~/.local/bin/agy ~/.local/share/claude just ai-setup # re-copies the wrapper scripts into ~/.local/bin ``` ## Notes Changes under `dot_files/ai-dev/**` and `dot_files/nvim/**` trigger the `build-ai-dev.yml` / `build-nvim-dev.yml` workflows; the image fixes take effect after a rebuild + `just ai-upgrade` / re-create of the distrobox container. https://claude.ai/code/session_01WQzgxRCuruWxbRv66shWeQ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 063d256 commit 3c70fb8

6 files changed

Lines changed: 50 additions & 9 deletions

File tree

dot_files/ai-dev/Containerfile

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,18 @@ RUN curl -fsSL https://claude.ai/install.sh | bash
2121
RUN curl -fsSL https://antigravity.google/cli/install.sh | bash
2222

2323
# =============================================================================
24-
# LAYER 3: Entrypoint script (sets PATH, execs command)
24+
# LAYER 3: Ghostty terminfo (xterm-ghostty)
2525
# =============================================================================
26+
# Inherited from nvim-dev, but install explicitly so ai-dev still resolves
27+
# TERM=xterm-ghostty if the base image ever changes. Prevents awkward escape
28+
# sequences in the Claude Code / Antigravity TUIs.
2629
USER root
30+
RUN curl -fsSL https://raw.githubusercontent.com/ghostty-org/ghostty/main/src/terminfo/ghostty.terminfo \
31+
| tic -x -o /etc/terminfo -
32+
33+
# =============================================================================
34+
# LAYER 4: Entrypoint script (sets PATH, execs command)
35+
# =============================================================================
2736
COPY ai-entrypoint.sh /usr/local/bin/ai-entrypoint.sh
2837
RUN chmod +x /usr/local/bin/ai-entrypoint.sh
2938

dot_files/ai-dev/ai-entrypoint.sh

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,21 @@ if [ -n "$HOME" ] && [ "$HOME" != "/root" ]; then
77
sed -i "s|root:x:0:0:[^:]*:/root:|root:x:0:0:root:$HOME:|" /etc/passwd 2>/dev/null || true
88
fi
99

10-
# Symlink native install paths to where the installers expect them at runtime
11-
# (installed under /home/linuxbrew at build time, but $HOME differs at runtime)
12-
if [ -n "$HOME" ] && [ "$HOME" != "/home/linuxbrew" ]; then
13-
mkdir -p "$HOME/.local/bin" "$HOME/.local/share"
14-
ln -sf /home/linuxbrew/.local/bin/claude "$HOME/.local/bin/claude" 2>/dev/null || true
15-
ln -sf /home/linuxbrew/.local/share/claude "$HOME/.local/share/claude" 2>/dev/null || true
16-
ln -sf /home/linuxbrew/.local/bin/agy "$HOME/.local/bin/agy" 2>/dev/null || true
10+
# Make sure TERM resolves inside the container. The host usually runs Ghostty
11+
# (TERM=xterm-ghostty); that terminfo is baked into the image. If an unknown or
12+
# empty TERM is propagated, fall back to a sane default so programs don't emit
13+
# raw/awkward escape sequences.
14+
if [ -z "${TERM:-}" ] || ! infocmp "$TERM" >/dev/null 2>&1; then
15+
export TERM=xterm-256color
1716
fi
1817

18+
# Note: claude/agy are installed under /home/linuxbrew/.local/bin at build time.
19+
# They are resolved at runtime via PATH (set below), NOT via symlinks into $HOME.
20+
# Symlinking into $HOME was unsafe: when the wrappers mount the current directory
21+
# (-v "$(pwd):$(pwd):rw") and the tool is run from the host home directory, those
22+
# symlinks were written straight through to the host, clobbering the real
23+
# ~/.local/bin/claude and ~/.local/bin/agy wrapper scripts.
24+
1925
# Ensure claude auth files are readable within the container
2026
chmod -R a+rX "$HOME/.claude" 2>/dev/null || true
2127
chmod a+rw "$HOME/.claude.json" 2>/dev/null || true

dot_files/ai-dev/scripts/agy.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ exec podman run --rm -it --init \
1414
--user 0:0 \
1515
--security-opt label=disable \
1616
-e HOME="$HOME" \
17+
-e TERM="${TERM:-}" \
1718
$env_flags \
1819
-v "$(pwd):$(pwd):rw" \
1920
-v "$HOME/.gemini:$HOME/.gemini:rw" \

dot_files/ai-dev/scripts/claude.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ exec podman run --rm -it --init \
1010
--user 0:0 \
1111
--security-opt label=disable \
1212
-e HOME="$HOME" \
13+
-e TERM="${TERM:-}" \
1314
-v "$(pwd):$(pwd):rw" \
1415
-v "$HOME/.claude:$HOME/.claude:rw" \
1516
-v "$HOME/.claude.json:$HOME/.claude.json:rw" \

dot_files/ai-dev/scripts/enter.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ exec podman run --rm -it --init \
1515
--user 0:0 \
1616
--security-opt label=disable \
1717
-e HOME="$HOME" \
18+
-e TERM="${TERM:-}" \
1819
$env_flags \
1920
-v "$(pwd):$(pwd):rw" \
2021
-v "$HOME/.claude:$HOME/.claude:rw" \

dot_files/nvim/Containerfile

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,10 @@ USER root
190190
WORKDIR /
191191

192192
# =============================================================================
193-
# LAYER 11: Terminal compatibility
193+
# LAYER 11: Terminal compatibility (xterm-ghostty terminfo)
194194
# =============================================================================
195+
# Install Ghostty's terminfo so TERM=xterm-ghostty resolves inside the
196+
# container and TUIs (nvim, lazygit, ...) don't emit awkward escape sequences.
195197
RUN curl -fsSL https://raw.githubusercontent.com/ghostty-org/ghostty/main/src/terminfo/ghostty.terminfo \
196198
| tic -x -o /etc/terminfo -
197199

@@ -202,6 +204,27 @@ RUN curl -fsSL https://raw.githubusercontent.com/ghostty-org/ghostty/main/src/te
202204
# all tool directories are accessible
203205
RUN chmod -R 777 /home/linuxbrew
204206

207+
# =============================================================================
208+
# LAYER 13: TERM fallback safety net (xterm-ghostty)
209+
# =============================================================================
210+
# distrobox shares the host TERM (usually xterm-ghostty) and the terminfo is
211+
# installed above. As a safety net, if an unknown or empty TERM ever reaches an
212+
# interactive shell, fall back to xterm-256color so programs don't emit raw
213+
# escape sequences. Covers POSIX shells and fish (distrobox uses the host shell).
214+
RUN mkdir -p /etc/profile.d /etc/fish/conf.d \
215+
&& printf '%s\n' \
216+
'# Fall back to a known-good TERM if the current one has no terminfo entry.' \
217+
'if [ -n "${TERM:-}" ] && ! infocmp "$TERM" >/dev/null 2>&1; then' \
218+
' export TERM=xterm-256color' \
219+
'fi' \
220+
> /etc/profile.d/10-term-fallback.sh \
221+
&& printf '%s\n' \
222+
'# Fall back to a known-good TERM if the current one has no terminfo entry.' \
223+
'if test -n "$TERM"; and not infocmp "$TERM" >/dev/null 2>&1' \
224+
' set -gx TERM xterm-256color' \
225+
'end' \
226+
> /etc/fish/conf.d/10-term-fallback.fish
227+
205228
# Labels for GitHub Container Registry
206229
LABEL org.opencontainers.image.source="https://github.com/binarypie/hypercube"
207230
LABEL org.opencontainers.image.description="Neovim development environment with LSPs, formatters, and tools"

0 commit comments

Comments
 (0)