All images, scripts and skills

Claude Code: sign in without a browser (credentials)

sh

Drops a ready-made Claude Code login into a workspace so you don't have to authenticate in every new container. Supply it either way: **paste** the contents of `~/.claude/.credentials.json` from a machine where you're signed in (the whole file, `{` to `}`), or **point at a file** — back a secret with `/home/dev/.claude/.credentials.json` in a workspace that is already signed in, and pick that secret. It is read fresh on every run, and Claude Code rewrites that file as its tokens roll over, so a file-backed login doesn't go stale. The value lands in `~/.claude/.credentials.json` (mode 600), and `hasCompletedOnboarding` is set in `~/.claude.json` to skip the first-run prompt. An existing credentials file is backed up to `.credentials.json.bak`; `jq` is installed when possible, and without it an existing `~/.claude.json` isn't merged. After it runs: start `claude` — it should come up already signed in.

What it asks for

Claude Code credentials JSONrequired
The full contents of ~/.claude/.credentials.json from a machine where you're signed in (`cat ~/.claude/.credentials.json`) — or a secret backed by that file in a workspace that is already signed in, which stays current without re-pasting. Redacted from run logs and never persisted into the container's env profile.
Overwrite existing login
Choose 'no' to keep a login that is already in the container and only do the rest of the setup.

What it runs

The whole script, exactly as it will execute in your workspace. Nothing is hidden — read it before you run it.

#!/usr/bin/env sh
set -eu
umask 077   # anything this script creates is owner-only by default

log()  { printf '[claude-setup] %s\n' "$*"; }
warn() { printf '[claude-setup] WARNING: %s\n' "$*" >&2; }
die()  { printf '[claude-setup] ERROR: %s\n' "$*" >&2; exit 1; }

CREDS_JSON="${CLAUDE_CODE_CREDENTIALS_JSON:-}"
OVERWRITE="${OVERWRITE_EXISTING_CREDENTIALS:-yes}"

[ -n "$CREDS_JSON" ] || die "no credentials JSON was supplied"
command -v claude >/dev/null 2>&1 \
    || die "the 'claude' CLI is not on PATH — run this in a workspace that has Claude Code installed"

if [ "$(id -u)" -eq 0 ]; then SUDO=""
elif command -v sudo >/dev/null 2>&1; then SUDO="sudo -n"
else SUDO=""
fi

# jq is optional: it only merges an existing ~/.claude.json and sanity-checks the
# credentials. Never fail the run over it — the login matters more.
# Note: this script body is piped to `sh -s` on stdin, so stdin IS the remaining
# script. Every installer gets </dev/null or a prompt would eat the script.
if ! command -v jq >/dev/null 2>&1; then
    log "jq not found; attempting install (optional)"
    export DEBIAN_FRONTEND=noninteractive
    if   command -v apt-get >/dev/null 2>&1; then $SUDO apt-get update -qq </dev/null && $SUDO apt-get install -y -qq jq </dev/null || true
    elif command -v dnf     >/dev/null 2>&1; then $SUDO dnf install -y jq </dev/null || true
    elif command -v yum     >/dev/null 2>&1; then $SUDO yum install -y jq </dev/null || true
    elif command -v apk     >/dev/null 2>&1; then $SUDO apk add --no-cache jq </dev/null || true
    elif command -v pacman  >/dev/null 2>&1; then $SUDO pacman -Sy --noconfirm jq </dev/null || true
    elif command -v zypper  >/dev/null 2>&1; then $SUDO zypper install -y jq </dev/null || true
    elif command -v brew    >/dev/null 2>&1; then brew install jq </dev/null || true
    fi
    command -v jq >/dev/null 2>&1 || warn "continuing without jq"
fi

# -- credentials ---------------------------------------------------------------
mkdir -p "$HOME/.claude"
CREDS="$HOME/.claude/.credentials.json"

if command -v jq >/dev/null 2>&1; then
    printf '%s' "$CREDS_JSON" | jq empty >/dev/null 2>&1 \
        || die "the supplied value is not valid JSON — paste the whole file contents, from '{' to '}'"
fi

if [ -f "$CREDS" ] && [ "$OVERWRITE" != "yes" ]; then
    log "$CREDS already exists and overwrite is off — leaving it alone"
else
    if [ -f "$CREDS" ]; then
        cp -p "$CREDS" "$CREDS.bak"
        log "backed up previous credentials to $CREDS.bak"
    fi
    # printf, not a heredoc: the value must land verbatim, with no shell expansion.
    printf '%s\n' "$CREDS_JSON" > "$CREDS"
    chmod 600 "$CREDS"
    log "wrote $CREDS (mode 600)"
fi

# -- skip the onboarding prompt ------------------------------------------------
CLAUDE_JSON="$HOME/.claude.json"
if [ ! -f "$CLAUDE_JSON" ]; then
    printf '%s\n' '{"hasCompletedOnboarding":true}' > "$CLAUDE_JSON"
    log "created $CLAUDE_JSON"
elif command -v jq >/dev/null 2>&1; then
    tmp="$CLAUDE_JSON.devgrail.tmp"   # same directory: atomic rename, no cross-device copy
    trap 'rm -f "$tmp"' EXIT
    jq '.hasCompletedOnboarding = true' "$CLAUDE_JSON" > "$tmp" \
        || die "jq failed to process $CLAUDE_JSON"
    [ -s "$tmp" ] || die "jq produced empty output for $CLAUDE_JSON — aborting to avoid data loss"
    cp -p "$CLAUDE_JSON" "$CLAUDE_JSON.bak"
    mv "$tmp" "$CLAUDE_JSON"
    trap - EXIT
    log "updated $CLAUDE_JSON (backup: $CLAUDE_JSON.bak)"
else
    warn "jq unavailable and $CLAUDE_JSON exists — left untouched; if Claude Code shows the onboarding prompt, set \"hasCompletedOnboarding\": true there by hand"
fi

log "done. Run 'claude' — it should start without asking you to log in."

How to get it

Open your DevGrail workspace, go to Scripts → Official scripts, and choose Download. It is copied into your own library, where you can edit it, run it, or set it as a workspace default — the copy is yours, and later changes here do not touch it.