From d10546526ae74ff7ddbe12eadeba588133cde030 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 14 Jun 2026 13:34:53 +0000 Subject: [PATCH 1/3] fix(ai-dev): stop entrypoint clobbering host ~/.local/bin The entrypoint force-symlinked $HOME/.local/bin/{claude,agy} and $HOME/.local/share/claude to their /home/linuxbrew install paths. Since 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 wrapper scripts with dangling links into the image. The tools are already on PATH via /home/linuxbrew/.local/bin, so the symlinks were redundant. Remove the symlink block entirely. https://claude.ai/code/session_01WQzgxRCuruWxbRv66shWeQ --- dot_files/ai-dev/ai-entrypoint.sh | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/dot_files/ai-dev/ai-entrypoint.sh b/dot_files/ai-dev/ai-entrypoint.sh index 8834b9c..25145fe 100644 --- a/dot_files/ai-dev/ai-entrypoint.sh +++ b/dot_files/ai-dev/ai-entrypoint.sh @@ -7,14 +7,12 @@ if [ -n "$HOME" ] && [ "$HOME" != "/root" ]; then sed -i "s|root:x:0:0:[^:]*:/root:|root:x:0:0:root:$HOME:|" /etc/passwd 2>/dev/null || true fi -# Symlink native install paths to where the installers expect them at runtime -# (installed under /home/linuxbrew at build time, but $HOME differs at runtime) -if [ -n "$HOME" ] && [ "$HOME" != "/home/linuxbrew" ]; then - mkdir -p "$HOME/.local/bin" "$HOME/.local/share" - ln -sf /home/linuxbrew/.local/bin/claude "$HOME/.local/bin/claude" 2>/dev/null || true - ln -sf /home/linuxbrew/.local/share/claude "$HOME/.local/share/claude" 2>/dev/null || true - ln -sf /home/linuxbrew/.local/bin/agy "$HOME/.local/bin/agy" 2>/dev/null || true -fi +# Note: claude/agy are installed under /home/linuxbrew/.local/bin at build time. +# They are resolved at runtime via PATH (set below), NOT via symlinks into $HOME. +# Symlinking into $HOME was unsafe: when the wrappers mount the current directory +# (-v "$(pwd):$(pwd):rw") and the tool is run from the host home directory, those +# symlinks were written straight through to the host, clobbering the real +# ~/.local/bin/claude and ~/.local/bin/agy wrapper scripts. # Ensure claude auth files are readable within the container chmod -R a+rX "$HOME/.claude" 2>/dev/null || true From 096eb4ae01788a4a35212f93d10cdab4661c841a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 14 Jun 2026 13:39:13 +0000 Subject: [PATCH 2/3] fix(ai-dev): resolve TERM=xterm-ghostty in 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, so programs never emit raw escape sequences https://claude.ai/code/session_01WQzgxRCuruWxbRv66shWeQ --- dot_files/ai-dev/Containerfile | 11 ++++++++++- dot_files/ai-dev/ai-entrypoint.sh | 8 ++++++++ dot_files/ai-dev/scripts/agy.sh | 1 + dot_files/ai-dev/scripts/claude.sh | 1 + dot_files/ai-dev/scripts/enter.sh | 1 + 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/dot_files/ai-dev/Containerfile b/dot_files/ai-dev/Containerfile index 35a9092..706be0c 100644 --- a/dot_files/ai-dev/Containerfile +++ b/dot_files/ai-dev/Containerfile @@ -21,9 +21,18 @@ RUN curl -fsSL https://claude.ai/install.sh | bash RUN curl -fsSL https://antigravity.google/cli/install.sh | bash # ============================================================================= -# LAYER 3: Entrypoint script (sets PATH, execs command) +# LAYER 3: Ghostty terminfo (xterm-ghostty) # ============================================================================= +# Inherited from nvim-dev, but install explicitly so ai-dev still resolves +# TERM=xterm-ghostty if the base image ever changes. Prevents awkward escape +# sequences in the Claude Code / Antigravity TUIs. USER root +RUN curl -fsSL https://raw.githubusercontent.com/ghostty-org/ghostty/main/src/terminfo/ghostty.terminfo \ + | tic -x -o /etc/terminfo - + +# ============================================================================= +# LAYER 4: Entrypoint script (sets PATH, execs command) +# ============================================================================= COPY ai-entrypoint.sh /usr/local/bin/ai-entrypoint.sh RUN chmod +x /usr/local/bin/ai-entrypoint.sh diff --git a/dot_files/ai-dev/ai-entrypoint.sh b/dot_files/ai-dev/ai-entrypoint.sh index 25145fe..5016b78 100644 --- a/dot_files/ai-dev/ai-entrypoint.sh +++ b/dot_files/ai-dev/ai-entrypoint.sh @@ -7,6 +7,14 @@ if [ -n "$HOME" ] && [ "$HOME" != "/root" ]; then sed -i "s|root:x:0:0:[^:]*:/root:|root:x:0:0:root:$HOME:|" /etc/passwd 2>/dev/null || true fi +# Make sure TERM resolves inside the container. The host usually runs Ghostty +# (TERM=xterm-ghostty); that terminfo is baked into the image. If an unknown or +# empty TERM is propagated, fall back to a sane default so programs don't emit +# raw/awkward escape sequences. +if [ -z "${TERM:-}" ] || ! infocmp "$TERM" >/dev/null 2>&1; then + export TERM=xterm-256color +fi + # Note: claude/agy are installed under /home/linuxbrew/.local/bin at build time. # They are resolved at runtime via PATH (set below), NOT via symlinks into $HOME. # Symlinking into $HOME was unsafe: when the wrappers mount the current directory diff --git a/dot_files/ai-dev/scripts/agy.sh b/dot_files/ai-dev/scripts/agy.sh index 5ad0e8c..794a071 100755 --- a/dot_files/ai-dev/scripts/agy.sh +++ b/dot_files/ai-dev/scripts/agy.sh @@ -14,6 +14,7 @@ exec podman run --rm -it --init \ --user 0:0 \ --security-opt label=disable \ -e HOME="$HOME" \ + -e TERM="${TERM:-}" \ $env_flags \ -v "$(pwd):$(pwd):rw" \ -v "$HOME/.gemini:$HOME/.gemini:rw" \ diff --git a/dot_files/ai-dev/scripts/claude.sh b/dot_files/ai-dev/scripts/claude.sh index 55fcfad..72cccf9 100755 --- a/dot_files/ai-dev/scripts/claude.sh +++ b/dot_files/ai-dev/scripts/claude.sh @@ -10,6 +10,7 @@ exec podman run --rm -it --init \ --user 0:0 \ --security-opt label=disable \ -e HOME="$HOME" \ + -e TERM="${TERM:-}" \ -v "$(pwd):$(pwd):rw" \ -v "$HOME/.claude:$HOME/.claude:rw" \ -v "$HOME/.claude.json:$HOME/.claude.json:rw" \ diff --git a/dot_files/ai-dev/scripts/enter.sh b/dot_files/ai-dev/scripts/enter.sh index a87c6d5..2afb709 100755 --- a/dot_files/ai-dev/scripts/enter.sh +++ b/dot_files/ai-dev/scripts/enter.sh @@ -15,6 +15,7 @@ exec podman run --rm -it --init \ --user 0:0 \ --security-opt label=disable \ -e HOME="$HOME" \ + -e TERM="${TERM:-}" \ $env_flags \ -v "$(pwd):$(pwd):rw" \ -v "$HOME/.claude:$HOME/.claude:rw" \ From d99fcc35ee3f7864c1ac6ea1a1ee6dc270464a92 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 14 Jun 2026 13:45:29 +0000 Subject: [PATCH 3/3] fix(nvim-dev): add xterm-ghostty TERM fallback safety net nvim-dev already bakes the xterm-ghostty terminfo and inherits TERM from the host via distrobox. Add a shell-init fallback (POSIX + fish) that degrades to xterm-256color when TERM has no terminfo entry, so interactive TUIs never emit raw/awkward escape sequences if xterm-ghostty is ever unavailable. Also clarify the terminfo layer comment. https://claude.ai/code/session_01WQzgxRCuruWxbRv66shWeQ --- dot_files/nvim/Containerfile | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/dot_files/nvim/Containerfile b/dot_files/nvim/Containerfile index 1bd0c9a..f74884d 100644 --- a/dot_files/nvim/Containerfile +++ b/dot_files/nvim/Containerfile @@ -190,8 +190,10 @@ USER root WORKDIR / # ============================================================================= -# LAYER 11: Terminal compatibility +# LAYER 11: Terminal compatibility (xterm-ghostty terminfo) # ============================================================================= +# Install Ghostty's terminfo so TERM=xterm-ghostty resolves inside the +# container and TUIs (nvim, lazygit, ...) don't emit awkward escape sequences. RUN curl -fsSL https://raw.githubusercontent.com/ghostty-org/ghostty/main/src/terminfo/ghostty.terminfo \ | tic -x -o /etc/terminfo - @@ -202,6 +204,27 @@ RUN curl -fsSL https://raw.githubusercontent.com/ghostty-org/ghostty/main/src/te # all tool directories are accessible RUN chmod -R 777 /home/linuxbrew +# ============================================================================= +# LAYER 13: TERM fallback safety net (xterm-ghostty) +# ============================================================================= +# distrobox shares the host TERM (usually xterm-ghostty) and the terminfo is +# installed above. As a safety net, if an unknown or empty TERM ever reaches an +# interactive shell, fall back to xterm-256color so programs don't emit raw +# escape sequences. Covers POSIX shells and fish (distrobox uses the host shell). +RUN mkdir -p /etc/profile.d /etc/fish/conf.d \ + && printf '%s\n' \ + '# Fall back to a known-good TERM if the current one has no terminfo entry.' \ + 'if [ -n "${TERM:-}" ] && ! infocmp "$TERM" >/dev/null 2>&1; then' \ + ' export TERM=xterm-256color' \ + 'fi' \ + > /etc/profile.d/10-term-fallback.sh \ + && printf '%s\n' \ + '# Fall back to a known-good TERM if the current one has no terminfo entry.' \ + 'if test -n "$TERM"; and not infocmp "$TERM" >/dev/null 2>&1' \ + ' set -gx TERM xterm-256color' \ + 'end' \ + > /etc/fish/conf.d/10-term-fallback.fish + # Labels for GitHub Container Registry LABEL org.opencontainers.image.source="https://github.com/binarypie/hypercube" LABEL org.opencontainers.image.description="Neovim development environment with LSPs, formatters, and tools"