Shell setup¶
You can run Claude Code on MAG or on the ALCF Inference Endpoints straight from your shell. Two small files in your home directory do it: one holds your tokens, the other defines the commands. The setup works the same in bash and zsh, so you can use it on any Linux or macOS machine, including the login nodes of supercomputers at ALCF, OLCF and NERSC.
Prefer an isolated setup? Use the container
The tutorial container has the same claude-amsc and claude-alcf commands (plus Codex
and OpenCode versions) built in, and the agent sees only the project folder you mount. It needs Podman or
Docker, which shared systems such as supercomputers often do not provide. There, and on any machine where
you already work in a shell, use the setup on this page.
The MAG settings come from the AmSC guide
Claude Code with MAG, which also
defines a claude-amsc shell function. This page uses the same variables and adds a way to switch back and forth
with ALCF. If AmSC changes its recommendations, follow the AmSC guide.
Check the AI policy first
Before you use a coding agent on a shared system, read your facility's user policy and your institution's guidance on AI tools. Find out which models and services you may use, and what data may leave the machine. The agent sends your prompts, the files it reads and the command output it sees to the model provider. Do not point it at export-controlled, proprietary, personal or otherwise restricted data unless you have permission. MAG users must also follow the AmSC policies.
The setup has five steps. Each one ends with a check.
- Install Claude Code (and, for ALCF,
alcf-tokens). - Put your MAG token in
~/.bash_secrets. - Add the agent commands to
~/.bash_functions. - Load both files in your shell startup file, and in the current shell.
- Run
claude-amscorclaude-alcf, or switch the shell withuse-amsc,use-alcfanduse-default.
The file names are only a convention; zsh users can keep them. Steps 2 to 4 add to files you may already have. They never overwrite them.
1. Install the tools¶
You do not need administrator rights. The installers put their programs in ~/.local/bin.
curl -fsSL https://claude.ai/install.sh | bash # Claude Code
For ALCF you also need uv and alcf-tokens. Skip this if you only use MAG:
curl -LsSf https://astral.sh/uv/install.sh | sh # uv
Open a new terminal so it finds uv, then run:
uv tool install alcf-tokens # ALCF login helper
Check: claude --version (and alcf-tokens --help for ALCF) prints output. If you see
command not found, open a new terminal. If it still fails, add export PATH="$HOME/.local/bin:$PATH" to
~/.bashrc or ~/.zshrc.
2. Store your MAG token in ~/.bash_secrets¶
Only MAG needs a stored token. Get a MAG personal access token (PAT) from the myAmSC dashboard: select your Genesis Mission project, then Personal Access Tokens › Generate PAT. The token is shown only once, so copy it then. The AmSC page Get started with MAG covers requesting access and creating tokens.
Create the file if it does not exist, make it readable only by you, and open it in an editor:
touch ~/.bash_secrets && chmod 600 ~/.bash_secrets
nano ~/.bash_secrets # or vim, emacs, code
Add this line at the end, with your PAT between the quotes. If the file already has an AMSC_TOKEN line,
replace that line instead.
export AMSC_TOKEN='<your MAG PAT>'
Use an editor rather than echo ... >> ~/.bash_secrets. A command line with the token in it ends up in your
shell history.
Check: ls -l ~/.bash_secrets starts with -rw-------.
PATs are short-lived; see When a token expires for how to replace it.
For ALCF there is nothing to store. alcf-tokens keeps your login in ~/.globus and gives out fresh tokens when
asked. Log in once, and again when ALCF asks (every 30 days):
alcf-tokens login inference
The command prints a Globus link. Open it in a browser on any computer, log in with your ALCF account, and paste the code back into the terminal.
3. Add the commands to ~/.bash_functions¶
First check that you have no older commands with the same names. The AmSC guide, for example, has you define a
claude-amsc function:
type claude-amsc claude-alcf use-amsc use-alcf 2>/dev/null
If this prints function or alias for a name, delete that older definition from your startup files first. A
file path, such as this repository's bin/claude-amsc, can stay.
Then copy this whole block into your terminal. It appends the commands to ~/.bash_functions and creates the
file if needed. Whatever the file already contains stays. Run it only once; to change the commands later, see
Update or remove.
cat >> ~/.bash_functions <<'EOF'
# >>> agent backends >>>
# Claude Code on MAG or the ALCF Inference Endpoints. Works in bash and zsh.
# use-amsc | use-alcf | use-default switch this shell; then run plain `claude`
# claude-amsc | claude-alcf [args] run Claude Code once on that backend; this shell is unchanged
# agent-backend show which backend this shell uses
# agent-status --check ask MAG and ALCF whether your tokens work
# agent-status --models | --hot list MAG and ALCF models | only the ALCF models loaded now
_MAG_URL=https://mag.genesis.americansciencecloud.org
_ALCF_URL=https://inference-api.alcf.anl.gov/resource_server/minerva/api # Claude Code adds /v1/messages
_ALCF_API=https://inference-api.alcf.anl.gov/resource_server
use-default() {
unset ANTHROPIC_AUTH_TOKEN ANTHROPIC_BASE_URL ANTHROPIC_MODEL \
ANTHROPIC_DEFAULT_OPUS_MODEL ANTHROPIC_DEFAULT_SONNET_MODEL ANTHROPIC_DEFAULT_HAIKU_MODEL \
CLAUDE_CODE_SUBAGENT_MODEL CLAUDE_CODE_MAX_OUTPUT_TOKENS CLAUDE_CODE_AUTO_COMPACT_WINDOW \
DISABLE_NON_ESSENTIAL_MODEL_CALLS DISABLE_TELEMETRY CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS \
CLAUDE_CODE_NO_FLICKER
# Bring back an Anthropic API key that use-amsc or use-alcf set aside.
if [ -n "${_SAVED_ANTHROPIC_API_KEY:-}" ]; then
export ANTHROPIC_API_KEY="$_SAVED_ANTHROPIC_API_KEY"
unset _SAVED_ANTHROPIC_API_KEY
fi
export AGENT_BACKEND=default
}
# Set aside ANTHROPIC_API_KEY: Claude Code would send it instead of the MAG or ALCF token.
_agent_switch() {
use-default
if [ -n "${ANTHROPIC_API_KEY:-}" ]; then
export _SAVED_ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY"
unset ANTHROPIC_API_KEY
fi
export DISABLE_NON_ESSENTIAL_MODEL_CALLS=1 DISABLE_TELEMETRY=1 CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1
}
use-amsc() {
if [ -z "${AMSC_TOKEN:-}" ]; then
echo "use-amsc: AMSC_TOKEN is not set. Add it to ~/.bash_secrets and open a new terminal." >&2
return 1
fi
_agent_switch
# Settings from https://docs.amsc.energy.gov/client-applications/coding-tools/claude-code/
# No model variables: Claude Code picks current Claude models; use /model or --model to change.
export ANTHROPIC_AUTH_TOKEN="$AMSC_TOKEN" ANTHROPIC_BASE_URL="$_MAG_URL" CLAUDE_CODE_NO_FLICKER=1
# Smaller outputs avoid throttling; earlier compaction keeps long-context prices down (100000 for Haiku).
export CLAUDE_CODE_MAX_OUTPUT_TOKENS=16384 CLAUDE_CODE_AUTO_COMPACT_WINDOW=200000
export AGENT_BACKEND=amsc
}
use-alcf() {
local token model="${ALCF_CLAUDE_MODEL:-inkling-bf16}"
if ! command -v alcf-tokens >/dev/null 2>&1; then
echo "use-alcf: alcf-tokens not found. Install it: uv tool install alcf-tokens" >&2
return 1
fi
token="$(alcf-tokens get-token inference 2>/dev/null)"
if [ -z "$token" ]; then
echo "use-alcf: no ALCF token. Run: alcf-tokens login inference" >&2
return 1
fi
_agent_switch
export ALCF_AI_TOKEN="$token" ANTHROPIC_AUTH_TOKEN="$token" ANTHROPIC_BASE_URL="$_ALCF_URL"
export ANTHROPIC_MODEL="$model" ANTHROPIC_DEFAULT_OPUS_MODEL="$model" \
ANTHROPIC_DEFAULT_SONNET_MODEL="$model" ANTHROPIC_DEFAULT_HAIKU_MODEL="$model" \
CLAUDE_CODE_SUBAGENT_MODEL="$model"
export AGENT_BACKEND=alcf
}
# The ( ) body runs in a subshell, so the switch ends when Claude Code exits.
claude-amsc() ( use-amsc && command claude "$@" )
claude-alcf() ( use-alcf && command claude "$@" )
agent-backend() {
echo "${AGENT_BACKEND:-default} (${ANTHROPIC_BASE_URL:-Anthropic, with your own login or API key})"
}
# HTTP status for a token: 200 works, 401 or 403 rejected, 000 no answer.
# The header goes in on stdin, so the token does not show up in the process list.
# With a third argument the body is saved to that file.
_agent_http() {
local limit=10
[ -n "${3:-}" ] && limit=20
printf 'Authorization: Bearer %s\n' "$1" | curl -s -o "${3:-/dev/null}" -w '%{http_code}' -m "$limit" -H @- "$2" || true
}
_agent_report() {
case "$2" in
200) echo "$1: valid" ;;
401|403) echo "$1: rejected (HTTP $2), expired or invalid. Renew: $3" ;;
000) echo "$1: no answer (network, proxy or service down)" ;;
*) echo "$1: unexpected answer (HTTP $2)" ;;
esac
[ "$2" = 200 ]
}
# MAG answers /v1/models in the OpenAI format.
_AGENT_MAG_PY='
import json, sys
try:
models = sorted(m["id"] for m in json.load(sys.stdin).get("data") or [])
except (ValueError, KeyError, TypeError, AttributeError):
sys.exit("MAG: unexpected answer to /v1/models (not a model list)")
print("MAG:")
for model in models:
print(" " + model)
'
# ALCF: list-endpoints names the models; <cluster>/jobs says which are running (hot or starting) or queued.
_AGENT_ALCF_PY='
import json, os, re, sys
folder, mode = sys.argv[1], sys.argv[2]
def load(name):
try:
with open(os.path.join(folder, name)) as f:
return json.load(f)
except (IOError, ValueError):
return None
rank = {"hot": 0, "starting": 1, "queued": 2, "offline": 3, "unknown": 4}
def names(value):
items = value if isinstance(value, list) else str(value or "").split(",")
return [str(m).strip() for m in items if str(m).strip()]
def table(value):
return value if isinstance(value, dict) else {}
endpoints = load("endpoints.json")
clusters = endpoints.get("clusters") if isinstance(endpoints, dict) else None
# "clusters" prints the names for the shell to fetch <cluster>/jobs; it stays quiet, the listing reports errors.
if mode == "clusters":
print(" ".join(c for c in table(clusters) if re.match(r"^[A-Za-z0-9_-]+$", c)))
sys.exit()
if not isinstance(clusters, dict):
sys.exit("ALCF: unexpected answer to list-endpoints (no cluster list)")
print("ALCF (hot: loaded now; starting or queued: loading; offline: loads on the first request, which can take minutes; unknown: no live status):")
for cluster in sorted(clusters):
jobs = load("jobs-" + cluster + ".json")
known = isinstance(jobs, dict)
# Keyed by (framework, model); a job that names no framework counts for every framework.
state = {}
for key in ("running", "queued"):
for job in (jobs if known else {}).get(key) or []:
if not isinstance(job, dict):
continue
status = "queued"
if key == "running":
ready = str(job.get("Model Status") or "").lower()
status = "hot" if ready in ("ready", "running", "live") else "starting"
framework = str(job.get("Framework") or "").lower()
for model in names(job.get("Models")):
if rank[status] < rank[state.get((framework, model), "offline")]:
state[(framework, model)] = status
note = "" if known else ", live status unavailable"
for framework, info in sorted(table(table(clusters[cluster]).get("frameworks")).items()):
def status_of(m):
if not known:
return "unknown"
return min(state.get((framework.lower(), m), "offline"), state.get(("", m), "offline"), key=rank.get)
models = sorted(set(table(info).get("models") or []), key=lambda m: (rank[status_of(m)], m.lower()))
hot = [m for m in models if status_of(m) == "hot"]
if mode == "hot":
print("%s (%s): %d hot%s" % (cluster, framework, len(hot), note))
models = hot
else:
print("%s (%s): %d model%s, %d hot%s" % (cluster, framework, len(models), "" if len(models) == 1 else "s", len(hot), note))
for model in models:
print(" %-8s %s" % (status_of(model), model))
'
# $1 is "all" (MAG and ALCF) or "hot" (only ALCF models that are loaded now).
# The ( ) body runs in a subshell, so the trap removes the temporary folder even after Ctrl-C.
_agent_models() (
local folder code token cluster rc=0
if ! command -v python3 >/dev/null 2>&1; then
echo "agent-status: listing models needs python3" >&2
return 1
fi
folder="$(mktemp -d)" || return 1
trap 'rm -rf "$folder"' EXIT
if [ "$1" = all ]; then
if [ -z "${AMSC_TOKEN:-}" ]; then
echo "MAG: no AMSC_TOKEN. Add it to ~/.bash_secrets"
else
code="$(_agent_http "$AMSC_TOKEN" "$_MAG_URL/v1/models" "$folder/mag.json")"
if [ "$code" = 200 ]; then
python3 -c "$_AGENT_MAG_PY" < "$folder/mag.json" || rc=1
else
_agent_report MAG "$code" "read -rs AMSC_TOKEN && export AMSC_TOKEN, then update ~/.bash_secrets" || rc=1
fi
fi
fi
if ! command -v alcf-tokens >/dev/null 2>&1; then
echo "ALCF: alcf-tokens not installed (uv tool install alcf-tokens)"
rc=1
elif ! token="$(alcf-tokens get-token inference 2>/dev/null)" || [ -z "$token" ]; then
echo "ALCF: not logged in. Log in: alcf-tokens login inference"
rc=1
else
code="$(_agent_http "$token" "$_ALCF_API/list-endpoints" "$folder/endpoints.json")"
if [ "$code" != 200 ]; then
_agent_report ALCF "$code" "alcf-tokens login inference" || rc=1
else
for cluster in $(python3 -c "$_AGENT_ALCF_PY" "$folder" clusters); do
# All clusters at once, so a slow service costs one timeout rather than one per cluster.
{ [ "$(_agent_http "$token" "$_ALCF_API/$cluster/jobs" "$folder/jobs-$cluster.json")" = 200 ] ||
rm -f "$folder/jobs-$cluster.json"; } &
done
wait
python3 -c "$_AGENT_ALCF_PY" "$folder" "$1" || rc=1
fi
fi
return "$rc"
)
agent-status() {
local token rc=0
case "${1:-}" in
"")
agent-backend
echo "Test your tokens: agent-status --check. List models: agent-status --models" ;;
--check)
if [ -n "${AMSC_TOKEN:-}" ]; then
_agent_report MAG "$(_agent_http "$AMSC_TOKEN" "$_MAG_URL/v1/models")" \
"read -rs AMSC_TOKEN && export AMSC_TOKEN, then update ~/.bash_secrets" || rc=1
else
echo "MAG: no AMSC_TOKEN. Add it to ~/.bash_secrets"
fi
if ! command -v alcf-tokens >/dev/null 2>&1; then
echo "ALCF: alcf-tokens not installed (uv tool install alcf-tokens)"
elif token="$(alcf-tokens get-token inference 2>/dev/null)" && [ -n "$token" ]; then
_agent_report ALCF "$(_agent_http "$token" "$_ALCF_API/list-endpoints")" "alcf-tokens login inference" || rc=1
else
echo "ALCF: not logged in. Log in: alcf-tokens login inference"
fi
return "$rc" ;;
--models) _agent_models all ;;
--hot) _agent_models hot ;;
*) echo "usage: agent-status [--check | --models | --hot]" >&2; return 2 ;;
esac
}
# <<< agent backends <<<
EOF
Check: grep -c '>>> agent backends' ~/.bash_functions prints 1. If it prints 2, you ran the block twice;
see Update or remove to remove both and add one again.
4. Load both files¶
Your shell reads its startup file only when it starts, so this step has two parts.
Load them in every new shell. Pick your shell. The command adds a line for each file to your startup file, unless the line is already there:
for f in bash_secrets bash_functions; do
grep -q "\. ~/\.$f" ~/.bashrc 2>/dev/null || echo "[ -f ~/.$f ] && . ~/.$f" >> ~/.bashrc
done
for f in bash_secrets bash_functions; do
grep -q "\. ~/\.$f" ~/.zshrc 2>/dev/null || echo "[ -f ~/.$f ] && . ~/.$f" >> ~/.zshrc
done
The added lines look like [ -f ~/.bash_functions ] && . ~/.bash_functions: load the file if it exists. If your
startup file already loads these files in some other way, the extra line is harmless.
Load them in the shell you have open now. Without this, the commands exist only in new terminals:
source ~/.bash_secrets
source ~/.bash_functions
Opening a new terminal does the same thing. Do it again whenever you edit either file.
Check: agent-backend prints default (Anthropic, with your own login or API key), and
echo ${AMSC_TOKEN:+AMSC_TOKEN is set} prints AMSC_TOKEN is set.
Login shells on clusters
When you connect with SSH you get a login shell. A bash login shell reads ~/.bash_profile and not
~/.bashrc. Most sites source ~/.bashrc from their default ~/.bash_profile. If agent-backend is
command not found after you log in, add [ -f ~/.bashrc ] && . ~/.bashrc to ~/.bash_profile.
5. Use and switch¶
Run once on a backend. Claude Code options work as usual:
cd my-project
claude-amsc # Claude Code on MAG
claude-amsc --model claude-haiku # a cheaper MAG model; run /autocompact 100000 in the session
claude-alcf # Claude Code on ALCF (inkling-bf16)
ALCF_CLAUDE_MODEL=nemotron-3-ultra claude-alcf # another Minerva model
Or switch the current shell, so that plain claude uses that backend. Other programs you start from this shell
use it too.
use-alcf && claude # this shell now uses ALCF
agent-backend # alcf (https://inference-api.alcf.anl.gov/...)
use-amsc # now MAG
use-default # back to your own Claude subscription or API key
To see what a backend sets, run type claude-amsc or
env | grep -E '^(ANTHROPIC_|CLAUDE_CODE_|DISABLE_)', as the AmSC guide suggests.
MAG usage is charged to your project
The AmSC guide notes that Claude Code can easily use more than $100 a day, and that this counts against your project allocation. Watch your spend on the MAG dashboard, and use cheaper models for routine work (see AmSC's Model Recommendations).
Which models you can use¶
agent-status --models # MAG and ALCF models
agent-status --hot # only the ALCF models that are loaded now
ALCF (hot: loaded now; starting or queued: loading; offline: loads on the first request, which can take minutes; unknown: no live status):
minerva (api): 3 models, 1 hot
hot inkling-bf16
offline gpt-oss-120b
offline nemotron-3-ultra
sophia (vllm): ...
ALCF serves a model from its own nodes only while it is loaded. A model marked offline still works, but the first
request starts it and can wait minutes in the queue. Pick a hot one with ALCF_CLAUDE_MODEL=<model> claude-alcf.
claude-alcf and use-alcf use Minerva, so pick from its list. For MAG, pass a listed name to --model.
These commands need python3; the raw data is also available from alcf-ai ls-endpoints and
alcf-ai ls-jobs <cluster>.
When a token expires¶
MAG PATs are short-lived, ALCF access tokens last 48 hours, and the ALCF login itself lasts 30 days. To see whether your tokens still work:
agent-status --check
MAG: rejected (HTTP 401), expired or invalid. Renew: read -rs AMSC_TOKEN && export AMSC_TOKEN, then update ~/.bash_secrets
ALCF: valid
It asks MAG and ALCF directly and never prints the tokens.
New MAG PAT. Create one on the myAmSC dashboard and copy it. Then:
-
Use it in the shell you have open now. Run the line below, paste the PAT (nothing appears while you paste) and press Enter:
read -rs AMSC_TOKEN && export AMSC_TOKEN use-amsc # only if this shell was switched to MAGreadkeeps the PAT out of your shell history;export AMSC_TOKEN=<PAT>works too, but is saved there. -
Keep it for new terminals: replace the
AMSC_TOKENline in~/.bash_secrets(step 2).
ALCF. Usually nothing to do: claude-alcf gets a fresh token every time it starts, and so does use-alcf
(run it again in a shell that has been on ALCF for 48 hours). When the 30-day login runs out, log in again:
alcf-tokens login inference
Things that override these settings
enventries in~/.claude/settings.jsonor in a project's.claude/settings.jsontake precedence over your shell. For example,alcf-ai agent configure claudewrites such entries. Remove them if you want these commands to choose the backend.- If the repository's
binfolder is also on yourPATH, the functions win, because the shell looks up functions before programs. Both versions do the same job. Thebincommands also cover Codex and OpenCode (see Shortcut commands). - The AmSC guide documents an optional provider header. To use it, export
ANTHROPIC_CUSTOM_HEADERS="X-AmSC-MAG-Provider: amazon"before runningclaude-amsc; these functions do not touch it.
Update or remove¶
The commands sit between the # >>> agent backends >>> and # <<< agent backends <<< lines in
~/.bash_functions. This deletes that block and nothing else, and keeps a copy in ~/.bash_functions.bak:
sed -i.bak '/^# >>> agent backends >>>$/,/^# <<< agent backends <<<$/d' ~/.bash_functions
- To update: run it, copy the current block from step 3, then open a new terminal.
- To remove: run it, then delete the
AMSC_TOKENline from~/.bash_secrets. Delete the two lines that step 4 added to~/.bashrcor~/.zshrcif nothing else uses those files.
On supercomputers (ALCF, OLCF, NERSC)¶
The same steps work on the login nodes of Polaris and Aurora (ALCF), Frontier (OLCF), Perlmutter (NERSC) and other Linux clusters. Follow steps 1 to 4 on the cluster as on your own machine; on a login node, use the bash tab in step 4.
- Read the policy first. See the warning at the top of this page. Facility policies differ, and project or allocation terms can add limits on top of them.
- Run the agent on a login node. It needs outbound HTTPS to MAG or ALCF. Compute nodes often have no outbound network, or reach it only through a site proxy. Check your facility's documentation before running there.
- Respect login-node limits. Claude Code itself uses little CPU, but the commands it runs (builds, tests, analysis) count against login-node usage rules. Ask it to submit batch jobs for real work, and review those scripts before they run.
- Keep the token private. Home directories on clusters are on shared file systems, so check that
ls -l ~/.bash_secretsshows-rw-------. Do not keep it in a dotfiles repository, and do not type it into a command line, where other users can see it in the process list. - Mind your home quota. Claude Code keeps sessions in
~/.claude, and uv keeps a cache in~/.cache/uv. If your home space is small, clean these up from time to time.
AmSC and ALCF documentation¶
This page builds on the AmSC MAG documentation. Check it for current settings, models and policies:
- Claude Code with MAG: the
source of the MAG variables used here, and the original
claude-amscfunction - Coding tools: MAG setup for Codex, OpenCode and other tools
- Model Access Gateway and Get started with MAG
- Model names and options and Model Recommendations
- MAG dashboard, Troubleshooting, FAQ and Support
- AmSC policies
For ALCF, see the ALCF Inference Endpoints documentation and its coding agents guide.