HuggingClip / Dockerfile
somratpro's picture
fix: bypass gemini CLI relaunch mechanism using GEMINI_CLI_NO_RELAUNCH environment variable
4e5f895
raw
history blame
5.32 kB
# Stage 1: Build Paperclip from source
FROM node:lts-trixie-slim AS paperclip-builder
WORKDIR /build
RUN apt-get update && apt-get install -y \
git \
&& rm -rf /var/lib/apt/lists/* \
&& corepack enable
# Clone Paperclip (depth=1 for speed, uses repo's default branch)
RUN git clone --depth=1 https://github.com/paperclipai/paperclip.git .
# Copy lock files early for cache efficiency: lock changes don't re-clone
RUN ls -la pnpm-lock.yaml package.json 2>/dev/null || true
# Install dependencies (corepack picks correct pnpm version from packageManager field)
RUN pnpm install
# Apply both patches in a single layer (reduces layer count, cleaner git history)
# Patch 1: React Router basename for /app path handling
# Patch 2: Recovery chain depth cap at 500 to prevent stack overflow
RUN sed -i 's|<BrowserRouter>|<BrowserRouter basename="/app">|' ui/src/main.tsx && \
grep -q 'basename="/app"' ui/src/main.tsx || (echo "PATCH 1 FAILED: React Router basename not applied" && exit 1) && \
PATCH_FILE=server/src/services/recovery/issue-graph-liveness.ts && \
test -f "$PATCH_FILE" || (echo "PATCH 2 FAILED: File not found: $PATCH_FILE" && exit 1) && \
sed -i 's/seen\.has(current\.id)/(seen.size > 500 || seen.has(current.id))/' "$PATCH_FILE" && \
grep -q "seen.size > 500" "$PATCH_FILE" || (echo "PATCH 2 FAILED: Chain depth cap not applied" && exit 1)
# Build Paperclip (match official Dockerfile order)
RUN pnpm --filter @paperclipai/ui build
RUN pnpm --filter @paperclipai/plugin-sdk build
RUN pnpm --filter @paperclipai/server build
# Stage 2: Runtime
FROM node:lts-trixie-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
curl \
postgresql-client \
postgresql \
postgresql-contrib \
python3 \
python3-pip \
git \
&& rm -rf /var/lib/apt/lists/*
# Create PostgreSQL runtime directories
RUN mkdir -p /var/run/postgresql && chown postgres:postgres /var/run/postgresql
# Install health-server Node dependencies locally in /app
RUN npm init -y && npm install express@4 cors morgan
# Install agent CLIs globally
RUN npm install -g @google/gemini-cli @anthropic-ai/claude-code @openai/codex
# Wrap agent CLIs so they:
# 1. Drop cloudflare-proxy.js NODE_OPTIONS (would conflict with their HTTP)
# 2. Pre-set --max-old-space-size=4096 so gemini doesn't trigger heap-size
# self-relaunch (the spawn fails in HF Spaces containers)
RUN for cmd in claude codex; do \
if [ -e /usr/local/bin/$cmd ]; then \
mv /usr/local/bin/$cmd /usr/local/bin/${cmd}-real && \
printf '#!/bin/sh\nunset NODE_OPTIONS\nexport NODE_OPTIONS="--max-old-space-size=4096 --no-deprecation --no-warnings"\nexec /usr/local/bin/%s-real "$@"\n' "$cmd" > /usr/local/bin/$cmd && \
chmod +x /usr/local/bin/$cmd; \
fi; \
done
# Gemini wrapper β€” fix for "Failed to relaunch the CLI process":
#
# ROOT CAUSE: relaunch.ts::relaunchAppInChildProcess() spawns a child process
# with stdio IPC. That spawn fails when Paperclip pipes gemini's stdio.
# Source: packages/cli/src/utils/relaunch.ts
#
# FIX: GEMINI_CLI_NO_RELAUNCH=1 β€” documented kill-switch in relaunch.ts that
# makes relaunchAppInChildProcess() return early (skip spawn entirely).
# The process then runs as the main process without a child.
#
# Also bake in other headless vars so they survive even if Paperclip spawns
# gemini with a custom env object:
# GEMINI_SANDBOX=false β€” skip Docker-sandbox attempt
# GEMINI_CLI_TRUST_WORKSPACE=true β€” skip interactive workspace-trust prompt
RUN mv /usr/local/bin/gemini /usr/local/bin/gemini-real && \
printf '#!/bin/sh\nunset NODE_OPTIONS\nexport NODE_OPTIONS="--max-old-space-size=4096 --no-deprecation --no-warnings"\nexport GEMINI_CLI_NO_RELAUNCH=1\nexport GEMINI_SANDBOX=false\nexport GEMINI_CLI_TRUST_WORKSPACE=true\nexec /usr/local/bin/gemini-real "$@"\n' \
> /usr/local/bin/gemini && \
chmod +x /usr/local/bin/gemini && \
echo "=== gemini wrapper ===" && cat /usr/local/bin/gemini
# Install Python dependencies for sync
RUN pip install --no-cache-dir --break-system-packages huggingface_hub PyYAML
# Copy full Paperclip build (including node_modules for runtime)
COPY --from=paperclip-builder /build /app/paperclip
# Ensure pnpm is available in runtime stage
RUN corepack enable
# Copy orchestration files
COPY start.sh /app/
COPY health-server.js /app/
COPY paperclip-sync.py /app/
COPY cloudflare-proxy.js /app/
COPY cloudflare-proxy-setup.py /app/
COPY cloudflare-worker.js /app/
COPY setup-uptimerobot.sh /app/
RUN chmod +x /app/start.sh /app/setup-uptimerobot.sh
# Create non-root user for running Paperclip + agent CLIs
# Claude Code refuses --dangerously-skip-permissions when running as root
# Note: /app files stay root-owned (644/755 defaults = readable by all).
# /paperclip runtime dir is chowned to paperclip in start.sh after restore.
RUN useradd -m -u 1001 -s /bin/bash paperclip && \
mkdir -p /paperclip /var/lib/postgresql/data && \
chown -R postgres:postgres /var/lib/postgresql/data && \
chown paperclip:paperclip /paperclip
EXPOSE 7861
HEALTHCHECK --interval=30s --timeout=10s --start-period=120s --retries=3 \
CMD curl -f http://localhost:7861/health || exit 1
CMD ["/app/start.sh"]