Skip to content

Commit aef76ed

Browse files
sijieclaude
andauthored
fix: catch a refused provider key in Lab 0, and three other first-run fixes (#8)
* fix: check in Lab 0 that the model provider accepts the engine's key `local/engine.sh --check` and the doctor both passed with a key the provider refuses: the engine check only looked for a key in the gateway's environment. The first sign was Lab 1, where the agent retried its first turn for three minutes and then stopped with `upstream returned 401`. The check now asks the provider. The harness lists models with the key it was started with, so the key stays in the engine and the request costs nothing. A refused key now fails Lab 0, step 2, within seconds and with its fix. A provider that cannot be reached, or that answers with another status, is reported as that, not as a bad key. The gateway cannot be asked instead: its only Anthropic route is `/v1/messages`, every call to it needs a session token, and its image has no HTTP client. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Nq3n2MeC9XrC31wYfdjZv * fix: remove the unnamed volumes the local stacks left behind `docker compose down` keeps the unnamed volumes that the Kafka and RustFS images declare, and the next `up` creates new ones. Each `local/down.sh` and restart left four more empty volumes, and each `local/engine.sh` one more: nine after one pass through the course. `local/down.sh` now removes the containers with `rm --volumes` before `down`, and `local/engine.sh` removes the engine's stopped containers with `--volumes`. Named volumes hold the data and are not touched: after a stop and a start, the topic, the view and the table are still there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Nq3n2MeC9XrC31wYfdjZv * docs: say what Apple's Python 3.9 does to the install command On a Mac with no other Python, `python3` is 3.9.6. `runorca` needs 3.10 or newer, so the lab's `pip install -r requirements.txt` stops at `No matching distribution found for runorca==0.3.0`, which does not mention Python. Both courses now say to check `python3 --version` first, and both troubleshooting pages list the symptom. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Nq3n2MeC9XrC31wYfdjZv * docs: name the ork release archive for a Mac without Homebrew The pages gave only `brew install orca-ae/tap/ork`. A new Mac has no Homebrew, and installing it takes an administrator password. The release archive needs neither. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Nq3n2MeC9XrC31wYfdjZv --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 1f5192b commit aef76ed

10 files changed

Lines changed: 244 additions & 20 deletions

File tree

‎docs/before-you-arrive.md‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ Everyone also needs:
1616
- `git` and a terminal that runs `bash`. On Windows that is WSL or Git Bash, on
1717
every path: the checks in the labs are `bash` commands.
1818
- [`ork`](https://github.com/orca-ae/orca-cli) (the Orca CLI), v0.6.0 or newer:
19-
`brew install orca-ae/tap/ork`. All three paths use it for the first MCP login
20-
in Lab 3, and for the checks in every lab. Check
19+
`brew install orca-ae/tap/ork`, or a
20+
[release archive](https://github.com/orca-ae/orca-cli/releases) unpacked onto
21+
your `PATH`. All three paths use it for the first MCP login in Lab 3, and for
22+
the checks in every lab. Check
2123
`ork agent vaults credentials create --help` for `--oauth-issuer` and
2224
`--oauth-allow-issuer-mismatch`: a build with those flags handles
2325
StreamNative's same-domain proxy issuer aliases and dynamic client
@@ -45,6 +47,11 @@ source .venv/bin/activate # Git Bash on Windows: source .venv/Scripts/act
4547
pip install -r requirements.txt
4648
```
4749

50+
`python3 --version` has to say 3.11 or newer. On macOS, Apple's own `python3`
51+
is 3.9, and with it `pip` stops at
52+
`No matching distribution found for runorca`. Install a newer Python and name it
53+
in the second line, for example `python3.13 -m venv .venv`.
54+
4855
**TypeScript**
4956

5057
```bash

‎labs/cloud/00-set-up.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ source .venv/bin/activate # Git Bash on Windows: source .venv/Scripts/act
4545
pip install -r requirements.txt
4646
```
4747

48+
`python3 --version` has to say 3.11 or newer. On macOS, Apple's own `python3`
49+
is 3.9, and with it `pip` stops at
50+
`No matching distribution found for runorca`. Install a newer Python and name it
51+
in the second line, for example `python3.13 -m venv .venv`.
52+
4853
**TypeScript** (Node.js 20 or newer)
4954

5055
```bash

‎labs/cloud/troubleshooting.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Still stuck after two tries? Raise your hand.
1313

1414
| Symptom | Fix |
1515
|---|---|
16+
| `pip install -r requirements.txt`: `No matching distribution found for runorca==0.3.0` | The `python3` that made your virtual environment is older than the course needs: on macOS, Apple's own is 3.9. Install Python 3.11 or newer, then make the environment again with it. In `python/`: `rm -rf .venv`, then the install commands from Lab 0 with that Python's name in place of `python3`, for example `python3.13 -m venv .venv`. |
1617
| Doctor: `Agent Engine HTTP 401/403` | The key was rejected. A key created before its permissions must be re-created: ask a facilitator. |
1718
| Doctor: `Kafka ... authentication` | `SN_SERVICE_ACCOUNT` must be the full principal, `<name>@<org>.auth.streamnative.cloud`; `SN_API_KEY` is the raw key. |
1819
| Doctor: `Kafka security.login_events: not found` | The topic is not there yet. Create it and load it: Lab 0, step 3. |

‎labs/local/00-set-up.md‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,21 @@ part of the stack answers and the topic holds 246 logins.
1010

1111
- **Docker** is running, with Compose v2 (`docker compose version`).
1212
- You have [`ork`](https://github.com/orca-ae/orca-cli) v0.6.0 or newer
13-
(`brew install orca-ae/tap/ork`), and [`jq`](https://jqlang.org/download/).
13+
(`brew install orca-ae/tap/ork`, or a
14+
[release archive](https://github.com/orca-ae/orca-cli/releases) unpacked onto
15+
your `PATH`), and [`jq`](https://jqlang.org/download/).
1416
- You have an **Anthropic API key**.
1517
- You cloned this repository, and installed **one** path:
1618

1719
| Python (3.11 or newer) | TypeScript (Node.js 20 or newer) |
1820
|---|---|
1921
| `cd python && python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt` | `cd typescript && npm install` |
2022

23+
For Python, `python3 --version` has to say 3.11 or newer. On macOS, Apple's
24+
own `python3` is 3.9, and with it `pip` stops at
25+
`No matching distribution found for runorca`. Install a newer Python and name
26+
it in the command, for example `python3.13 -m venv .venv`.
27+
2128
The CLI path needs only `ork` and `jq` for the labs, plus one of the two
2229
above for the doctor, the seeder, and the injector.
2330
- Keep two terminals open: one in your path's folder (`cli/`, `python/` or
@@ -92,9 +99,9 @@ engine needs, usually 8080: see [Troubleshooting](troubleshooting.md).
9299

93100
### Check
94101

95-
The engine is up, has a provider key, and can reach the MCP server. The script
96-
ends with these lines, and `--check` prints them again without starting
97-
anything.
102+
The engine is up, has a provider key that the model provider accepts, and can
103+
reach the MCP server. The script ends with these lines, and `--check` prints
104+
them again without starting anything.
98105

99106
```bash
100107
local/engine.sh --check
@@ -103,12 +110,17 @@ local/engine.sh --check
103110
```text
104111
PASS the AI Gateway is running
105112
PASS the gateway has a provider key
113+
PASS the model provider accepts that key
106114
PASS the gateway allows the MCP host risingwave-mcp
107115
PASS the MCP server answers at http://risingwave-mcp:8000/mcp on the engine's network
108116
109117
The Agent Engine is up and can reach your MCP server.
110118
```
111119

120+
A line that says `FAIL` prints its fix under it. While the third line fails,
121+
your agent cannot answer. Usually the provider refused the key you exported:
122+
export one that works and run `local/engine.sh` again.
123+
112124
## Step 3: Write your `.env`
113125

114126
Every script in this repository reads `.env` in the repository root.

‎labs/local/01-hello-agent.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ As for what I can see right now — nothing live just yet! The next step connect
3535
```
3636

3737
This is the first time your stack calls the model. If you see
38-
`[error] ... (retrying)` lines and no reply, the model provider rejected the key
39-
the engine started with: press Ctrl-C and see
38+
`[error] ... (retrying)` lines and no reply, the model provider is refusing the
39+
request: press Ctrl-C and see
4040
[Troubleshooting](troubleshooting.md#the-model-does-not-answer).
4141

4242
### Check

‎labs/local/troubleshooting.md‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Two commands tell you what is wrong. Run both first.
44

55
```bash
6-
local/engine.sh --check # the Agent Engine, and its link to the MCP server
6+
local/engine.sh --check # the Agent Engine, its provider key, and its link to the MCP server
77
(cd python && .venv/bin/python doctor.py) # or: npm --prefix typescript run doctor
88
```
99

@@ -15,7 +15,9 @@ Each failed line prints its fix.
1515
|---|---|
1616
| `docker compose ... up` fails with a port already in use | Another program has one of the ports the stack publishes on `127.0.0.1`: 29092 (Kafka), 18081 (schema registry), 4566 and 5691 (RisingWave), 8000 (MCP). Stop that program, run `local/down.sh`, then run the `up` command again. Running it again without `local/down.sh` is not enough: Docker brings the container whose port was taken back without its network. The ports are fixed: the broker tells its clients to come back to `127.0.0.1:29092`, and `local/write-env.sh` writes these ports into `.env`. |
1717
| `local/engine.sh` fails because a port is taken (`Bind for 0.0.0.0:8080 failed: port is already allocated`) | Another program has a port the engine publishes on `127.0.0.1`: 8080 (the registry) or 18082. If it is a container, `docker ps` shows which: look for `:8080->` under PORTS. Stop that program and run `local/engine.sh` again. If the port is 8080 and you want to keep that program running, move the registry instead: `export ORCA_LOCAL_REGISTRY_PORT=18080`, run `local/engine.sh` again, then `local/write-env.sh` so `.env` has the new address. Export it in every terminal you run `local/engine.sh` from: a run without it goes back to 8080. |
18+
| `pip install -r requirements.txt`: `No matching distribution found for runorca==0.3.0` | The `python3` that made your virtual environment is older than the course needs: on macOS, Apple's own is 3.9. Install Python 3.11 or newer, then make the environment again with it. In `python/`: `rm -rf .venv`, then the install command from Lab 0 with that Python's name in place of `python3`, for example `python3.13 -m venv .venv`. |
1819
| `local/engine.sh`: `ANTHROPIC_API_KEY is not set in this shell` | `export ANTHROPIC_API_KEY=<your key>` in the terminal where you run the script. The engine reads the key only when it starts. |
20+
| `local/engine.sh`: `FAIL the model provider accepts that key` | The fix under that line says why. `the provider answered 401` (or `403`): the provider refuses the key the engine started with. `export ANTHROPIC_API_KEY=<a key that works>`, then `local/engine.sh`. `the engine cannot reach https://api.anthropic.com`: the engine has no way out to the provider. Check your network, then `local/engine.sh --check`. |
1921
| `local/engine.sh`: `bootstrap refused: an organization already exists` | The engine's volumes exist but its keys in `.lab/ork` are gone. Start over: `local/down.sh --reset`, then Lab 0. |
2022
| CLI path: `.venv/bin/python: No such file or directory` | The Python path is not installed. If you installed the TypeScript path, use the `npm` command the lab gives beside the Python one. Otherwise install one of the two: Lab 0, "Before you start". |
2123
| Doctor: `Agent Engine HTTP 401` | The key in `.env` is not the running engine's key. Run `local/write-env.sh`. If it still fails, the engine's volumes and keys are out of step: `local/down.sh --reset`, then Lab 0. |
@@ -52,9 +54,11 @@ You do not have to wait for it: press Ctrl-C. That stops your script, not the
5254
engine. The engine keeps retrying that turn for the rest of the three minutes,
5355
and a turn you start meanwhile waits behind it. The usual cause is the key:
5456

55-
1. Check that the key works at all, for example in the
56-
[Anthropic console](https://console.anthropic.com/).
57-
2. Export the working key and restart the engine, which reads the key only at
57+
1. Ask the provider about it: `local/engine.sh --check`. If
58+
`the model provider accepts that key` says `FAIL`, the fix under it says
59+
why. Most often the provider refuses the key the engine started with, for
60+
example one that was revoked since Lab 0.
61+
2. Export a key that works and restart the engine, which reads the key only at
5862
start:
5963

6064
```bash
@@ -64,8 +68,8 @@ and a turn you start meanwhile waits behind it. The usual cause is the key:
6468

6569
3. Run the lab script again.
6670

67-
If the key is fine, check that `ORCA_MODEL` in `.env` is a model your key can
68-
use.
71+
If that line says `PASS`, the key is fine: check that `ORCA_MODEL` in `.env` is
72+
a model your key can use.
6973

7074
## Start over
7175

‎local/down.sh‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,22 @@ if [ -n "$mcp" ]; then
2929
fi
3030

3131
# The engine. `down` by project name also removes the gateway, and needs no file.
32+
# Without --reset, `rm --volumes` goes first: `down` alone keeps the unnamed
33+
# volumes that some images declare, and the next start makes new ones beside
34+
# them. Your data is in named volumes, which `rm --volumes` does not touch. It
35+
# is quiet and may fail: `down` stops whatever it left.
3236
if $reset; then
3337
docker compose --progress quiet --project-name "$project" down -v --remove-orphans
3438
else
39+
docker compose --progress quiet --project-name "$project" rm --stop --force --volumes >/dev/null 2>&1 || true
3540
docker compose --progress quiet --project-name "$project" down --remove-orphans
3641
fi
3742

3843
# The streaming stack.
3944
if $reset; then
4045
streaming down -v --remove-orphans
4146
else
47+
streaming rm --stop --force --volumes >/dev/null 2>&1 || true
4248
streaming down --remove-orphans
4349
fi
4450

‎local/engine.sh‎

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#
55
# export ANTHROPIC_API_KEY=... # the engine reads your provider key when it starts
66
# local/engine.sh # start (or restart) the engine, then link it
7-
# local/engine.sh --check # only check the link
7+
# local/engine.sh --check # only run the checks
88
#
99
# What it runs, in order:
1010
#
@@ -21,6 +21,10 @@
2121
# restarts the gateway, and attaches the MCP server's container to the
2222
# engine's Docker network under that name.
2323
#
24+
# 3. The checks, which `--check` runs alone. One of them asks the model
25+
# provider whether it accepts your key. The harness makes that request, so
26+
# the key stays in the engine; it lists models, which costs nothing.
27+
#
2428
# Safe to run again at any time. Run it again after anything restarts the engine.
2529
set -euo pipefail
2630
# shellcheck source=lib.sh
@@ -38,6 +42,22 @@ curl_on() { # curl_on <network> <curl args...>
3842
docker run --rm --network "$1" --entrypoint curl "$CURL_IMAGE" "${@:2}"
3943
}
4044

45+
# Ask the model provider about the engine's key: the HTTP status of a request
46+
# that lists models, or "unreachable". `ork local start` hands the harness and
47+
# the gateway the same key, and of the two images only the harness's has an HTTP
48+
# client (node), so the harness asks.
49+
provider_status() {
50+
local harness
51+
harness=$(engine_container harness)
52+
[ -n "$harness" ] || return 0
53+
docker exec -e PROVIDER_URL="$PROVIDER_URL" "$harness" node -e '
54+
fetch(process.env.PROVIDER_URL + "/v1/models?limit=1", {
55+
headers: { "x-api-key": process.env.ANTHROPIC_API_KEY, "anthropic-version": "2023-06-01" },
56+
signal: AbortSignal.timeout(15000),
57+
}).then((response) => console.log(response.status), () => console.log("unreachable"));
58+
' 2>/dev/null || true
59+
}
60+
4161
start_engine() {
4262
command -v ork >/dev/null || die "ork (the Orca CLI) is not installed. See labs/local/00-set-up.md."
4363
[ -n "${ANTHROPIC_API_KEY:-}" ] ||
@@ -49,10 +69,12 @@ start_engine() {
4969
# Replace what is not running. A container whose port could not be bound
5070
# (another program had it) stays cut off from its network: Docker starts it
5171
# with loopback only from then on, even once the port is free (seen with
52-
# Docker Engine 29.2). The engine's data is in volumes, so nothing is lost.
72+
# Docker Engine 29.2). The engine's data is in named volumes, so nothing is
73+
# lost: `--volumes` takes only the unnamed ones an image declares, which would
74+
# otherwise be left behind at every start.
5375
local name
5476
while IFS= read -r name; do
55-
[ -z "$name" ] || docker rm "$name" >/dev/null
77+
[ -z "$name" ] || docker rm --volumes "$name" >/dev/null
5678
done < <(engine_stopped_containers)
5779
ork local --data-dir "$ORK_DIR" start --with-gateway
5880
}
@@ -82,7 +104,7 @@ link() {
82104
}
83105

84106
check() {
85-
local gateway network mcp
107+
local gateway network mcp fix
86108
gateway=$(engine_container ai-gateway)
87109
mcp=$(mcp_container)
88110

@@ -94,6 +116,14 @@ check() {
94116

95117
if docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' "$gateway" | grep -q '^ANTHROPIC_API_KEY=.'; then
96118
pass "the gateway has a provider key"
119+
# A key can be there and still be refused. Without this, the first sign is
120+
# an agent that retries its first turn for three minutes.
121+
fix=$(provider_fix "$(provider_status)")
122+
if [ -z "$fix" ]; then
123+
pass "the model provider accepts that key"
124+
else
125+
fail "the model provider accepts that key" "$fix"
126+
fi
97127
else
98128
fail "the gateway has a provider key" "export ANTHROPIC_API_KEY=<your key>, then local/engine.sh"
99129
fi

‎local/lib.sh‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ MCP_HOST=risingwave-mcp
1111
# The object store's image ships a curl. Both stacks already use the image.
1212
# shellcheck disable=SC2034 # read by engine.sh
1313
CURL_IMAGE=rustfs/rustfs:1.0.0
14+
# Where the gateway that `ork local` configures sends your agent's model calls.
15+
PROVIDER_URL=https://api.anthropic.com
1416

1517
die() {
1618
printf '\n%s\n' "$1" >&2
@@ -82,3 +84,21 @@ This script was written for ork v0.6.0; your ork may write a different gateway c
8284
cat "$file.tmp" >"$file"
8385
rm -f "$file.tmp"
8486
}
87+
88+
# What the model provider's answer about the engine's key calls for: nothing
89+
# when it accepts the key, otherwise the fix.
90+
provider_fix() { # provider_fix <HTTP status, "unreachable", or nothing>
91+
case "$1" in
92+
200) ;;
93+
401 | 403)
94+
printf 'the provider answered %s. export ANTHROPIC_API_KEY=<a key it accepts>, then local/engine.sh' "$1"
95+
;;
96+
unreachable)
97+
printf 'the engine cannot reach %s. Check your network, then local/engine.sh --check' "$PROVIDER_URL"
98+
;;
99+
[0-9][0-9][0-9])
100+
printf 'the provider answered %s, which is not about your key. Try again: local/engine.sh --check' "$1"
101+
;;
102+
*) printf 'the harness could not ask the provider. Start the engine again: local/engine.sh' ;;
103+
esac
104+
}

0 commit comments

Comments
 (0)