Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 47 additions & 65 deletions plugins/temporal/skills/temporal-cloud-setup/SKILL.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ interface:
short_description: "Set up Temporal Cloud and run a sample Workflow"

policy:
allow_implicit_invocation: false
allow_implicit_invocation: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Failure Handling — `error_code` → remediation map

Read this when a `scripts/provision.sh` subcommand returns `status=error`. SKILL.md's
Failure Handling section points here; the Steps spine's "On-error" column is the index of
which codes each step can emit.

Stop and surface the problem (don't silently retry destructive or auth steps), staying in the calm output style — report the problem and fix plainly, without exposing reasoning. The script ops fail loudly with `status=error` + an `error_code`; **fix the cause the code names and re-run the same op — never improvise an alternate command, switch output formats, or poll.** Map the codes:

- **`brew-missing` / `manual-install`** (install-cli) → relay the script's message: install Homebrew from https://brew.sh, or download `temporal-cloud` from the releases page and put it on `PATH`. Do **not** auto-install Homebrew. Re-run install-cli.
- **`login-failed` / `not-authenticated`** (login, or any later op) → the browser sign-in didn't complete or the session expired. Ask the user to finish/redo the browser login, then re-run the op (create-namespace / create-key re-check auth themselves).
- **`cloud-unreachable`** (the post-login region pulse) → the Cloud API can't be reached. **This is a network / sandbox problem, NOT an auth problem — do NOT re-run `login`.** The classic case: `login` and `whoami` both succeed (they use the browser loopback / a cached token and run **offline**), yet the gRPC Cloud API is blocked by a sandbox or firewall, so the region pulse comes back empty. Tell the user to run the skill from an environment with **outbound internet and gRPC egress to the Temporal Cloud API** (`*.tmprl.cloud`); e.g. Codex's default sandbox blocks this. Re-running sign-in will only loop — the fix is connectivity. Once network is fixed, re-run the failed step.
- **`config-dir-unwritable`** (preflight) → the Temporal config directory can't be written. Fix its permissions, or set `TEMPORAL_CONFIG_FILE` to a writable path, then re-run. Caught **up front**, before any billable key is minted (writing `temporal.toml` is our op, so nothing else surfaces this).
- **`regions-empty`** (legacy code — no longer emitted) → the region step now maps an empty region list onto `cloud-unreachable` (see above), since an empty list means the Cloud API is unreachable, not that auth is missing. If you ever see `regions-empty` from an older build, treat it exactly as `cloud-unreachable`: check network/sandbox, not auth.
- **`create-rejected`** (start-namespace) → the namespace create was rejected on submit, usually region or name format; re-list regions, have the user pick an exact provider-prefixed value, then re-run `start-namespace`. Attribute this as a **namespace** failure, never as a downstream key error.
- **`namespace-timeout`** (await-namespace) → the namespace **appeared** (ACTIVATING) but didn't reach **ACTIVE** within the bound (`NS_AWAIT_MAX_SECS`, default 600s). It's provisioning lag, not a misconfig: re-run `await-namespace` (it resumes polling the exact `namespace list --name` filter until the namespace is ACTIVE); raise the bound with `NS_AWAIT_MAX_SECS=N` if needed. Don't switch to `namespace get`/other formats. (Waiting for ACTIVE here is what keeps `create-key`/`await-auth` from connecting to an endpoint that isn't serving yet — the cause of a "no children to pick from" stall.)
- **`namespace-not-provisioning`** (await-namespace / provision-and-scaffold) → the create was accepted but the namespace **never appeared** in the list within the phantom-grace window (`NS_PHANTOM_GRACE_SECS`, default 75s) — i.e. it's not provisioning at all, vs. just slow. Almost always an **unavailable region** (e.g. `azure-centralus`, whose provider reads `UNKNOWN` — see the region step's `unsupported_regions`). Don't re-run `await-namespace` on the same name; **re-run `start-namespace` with an AWS/GCP region**.
- **`handle-not-found`** (provision-and-scaffold) → the namespace was created but didn't reach **ACTIVE** within the retry bound (still provisioning). Re-run `provision-and-scaffold` or `await-namespace` to resume the wait. **Never** decode the API-key token or hunt the filesystem/config for the account-id — the exact `namespace list --name` filter is the source.
- **`clone-failed` / `unknown-sdk`** (scaffold) → the sample clone failed (network/repo) or the SDK has no repo mapping; confirm the SDK + connectivity, then re-run `scaffold`. (Independent of the namespace, which is already provisioning.)
- **`manager-not-found`** (scaffold / install-deps) → the chosen package manager isn't installed on this machine. Offer an **available** manager from the latest `detect-tools` `managers` list, or ask the user to install the missing one, then re-run with that `--manager`. Caught **before** the clone, so nothing was set up.
- **`unsupported-manager`** (scaffold / install-deps) → that manager isn't valid for this SDK's sample (e.g. `poetry` for Python, whose sample ships no `pyproject.toml`). Pick one of the supported managers named in the error / `detect-tools` and re-run.
- **`version-too-old`** (detect-tools `discrepancies`, **advisory**) → not a hard error and never blocks the run. Relay the remediation (upgrade the tool to the noted minimum) but you may proceed — the sample usually still works on the older version.
- **`no-json-parser`** (create-key) → install `jq` or `python3` (needed to capture the token safely), then re-run create-key.
- **`key-empty` / `key-create-failed`** (create-key) → almost always an expired login (not an output-format problem); the script re-checks `whoami` — redo the browser login if prompted, then re-run create-key once.
- **`key-limit-reached`** (create-key) → the account is at its **API-key cap**, so the mint was rejected at create time (not an auth or output problem — every run mints a fresh key, so a repeat tester accumulates them). Delete stale keys, then re-run create-key: list with `temporal cloud apikey list` and remove old `money-transfer-cloud-setup-*` keys with `temporal cloud apikey delete --key-id <id>`. Don't re-run login or switch output formats.
- **`config-write-failed`** (create-key) → the profile couldn't be written to `temporal.toml` (read-only dir or full disk); the key was minted but not saved. Fix directory permissions / free disk, then re-run create-key (it mints a fresh key and writes a clean profile).
- **API key lost** (only shown once) → re-run create-key to mint a fresh one (it rewrites the profile); don't try to recover the old value.
- **`temporal.toml` unparseable / duplicate `[profile.cloud-setup]` blocks** (e.g. from earlier partial runs) → run `scripts/provision.sh repair-config` (strips every `cloud-setup` block via awk, keeps `[profile.default]`, never reads the file into context), then re-run `create-key` to write one fresh profile. **Do not hand-edit, `cat`, or `awk` the file yourself** (the read-only-script rule and the secret carve-out) — `create-key` also strips any existing/duplicate `cloud-setup` blocks before writing, so it self-heals too.
- **`worker-unauthorized`** (run-workflow) → the Worker hit an auth error before it could poll — the just-minted key isn't accepted yet. Re-run `await-auth` (wait for `auth_ready=true`), then re-run `run-workflow`. Do **not** switch endpoints, re-mint the key, or edit the profile (it's readiness, not config).
- **`precompile-failed`** (run-workflow) → the Maven/dotnet build step failed before the Worker started. Java and .NET are pre-compiled once so the timing windows cover only Temporal operations. Check the output above; usually a missing Java/Maven/.NET SDK installation or a network issue fetching dependencies on the first build.
- **`worker-not-polling`** (run-workflow) → the Worker started but never registered as a poller within the bound (`WORKER_READY_MAX_SECS`, default 120s). Usually deps weren't installed or the Worker process crashed early. Confirm `scaffold` finished and `--dir` is the real `repo_path`, then re-run; raise the bound with `WORKER_READY_MAX_SECS=N` if needed. The script prints the Worker log tail to help.
- **`worker-start-failed`** (run-workflow) → the Worker process exited before polling (missing deps/venv, wrong dir, or a sample-app error in the log tail). Confirm deps installed and `--dir` is correct, then re-run.
- **`workflow-failed`** (run-workflow) → the starter exited non-zero / the Workflow didn't reach `COMPLETED`. Read the printed log tail: if it's an auth error, run `await-auth` and retry; otherwise surface the sample-app error. (Expected `FAILED` for the manual `DEMO_FAILURE=permanent` variant is **not** run through `run-workflow`.)
- **`workflow-not-submitted`** (run-workflow) → the starter exited **0 but never submitted a Workflow** within the settle window (`NOWF_SETTLE_SECS`, default 15s). Some sample clients catch their own connect/start error and still exit 0 (the .NET starter does this), so a clean exit code can hide a failed start. Read the printed log tail — it's almost always an auth/connection error: run `await-auth` (wait for `auth_ready=true`) and re-run `run-workflow`; if the profile points at a namespace that isn't ACTIVE, re-check `await-namespace` first.
- **`workflow-timeout`** (run-workflow) → the starter didn't finish within `--max-secs` (default 180s). The Cloud workflow is automatically terminated on timeout so it doesn't stay Running with no worker. If it still fires, raise with `--max-secs N` and re-run.
- **`Request unauthorized` / `Unavailable` on the first connect (right after setup)** → almost always **post-provision readiness**, not a misconfig: the just-created namespace + key need a moment to become connectable, and the parallel flow shortens that gap. **Wait ~10–15s and retry the connect, up to ~3 times.** Do **not** switch the address to a regional endpoint, re-mint the key, or rewrite the profile. The namespace endpoint (`<handle>.tmprl.cloud:7233`) is the correct, Temporal-recommended endpoint for API keys (temporalio/documentation#4733); regional is **not** the fix. If it still fails after retries, that's a Temporal Cloud issue to escalate — not a reason to change the skill's endpoint.
- **`key-expired`** (await-auth) → the poll came back with a **high-confidence permanent key failure** — a permanent qualifier (**expired / invalid / revoked / not found / disabled**) anchored to **jwt / api key / token** context (the real prerelease message is the Envoy JWT-filter desc `Unauthenticated desc = Jwt is expired`; see `references/unified-cli.md`), so await-auth **fast-failed** instead of spinning the full bound; the key won't clear by waiting. The classic cause: keys auto-expire in ~25h, so a next-day re-test polls a dead key. Fix: re-run `create-key` to mint a fresh key (it overwrites the `[profile.cloud-setup]` block). If you're at the API-key cap, delete stale keys first (see `key-limit-reached`), then re-run `create-key`. The message carries a **redacted** CLI stderr tail for context. (A bare, unqualified `Request unauthorized` during propagation is treated as **transient** — see below — so this never wrong-fast-fails a key that just needs a moment.) Don't switch endpoints or edit the profile.
- **`auth-timeout`** (await-auth) → the new API key still isn't accepted after the bound (`AUTH_READY_MAX_SECS`, default 90s), and the failures were **transient** — the usual post-provision `Request unauthorized` propagation, not the qualified permanent text that triggers `key-expired`. Wait longer and re-run `await-auth`; if it never clears, re-run `create-key` to mint a fresh key. The message now appends the **last captured, redacted** CLI stderr line so the timeout is diagnosable instead of blank — read it before deciding. Each poll is itself bounded by a per-call timeout (`AUTH_POLL_CALL_TIMEOUT`, default 15s) so a single wedged call can't hang the loop, and the ~`AUTH_READY_MAX_SECS` budget is counted in real wall-clock. Don't switch endpoints or edit the profile.
- **TLS / auth errors at connect** → confirm the profile's `[profile.cloud-setup.tls]` has `disabled = false` (TLS on), the address is the **namespace endpoint** (`<handle>.tmprl.cloud:7233`), and the `api_key` is set; re-run `verify-config`. (`create-key` now writes `disabled = false` explicitly.)
- **`temporal cloud …` commands suddenly fail auth (after deleting/expiring the key)** → a profile carrying an `api_key` overrides the login session. Remove the `[profile.cloud-setup]` block, or pass `--disable-config-file`. The setup always lives in the **named** `cloud-setup` profile (never `default`), so management commands keep using the login session.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

For the chosen SDK: clone the **`money-transfer-project-cloud-setup`** branch, install deps, then run the Worker and the starter. **The branch is pre-wired for Cloud — there is no connection edit.**

> **Note:** the clone + deps install are done by `scripts/provision.sh provision-and-scaffold` (PE-68), and the Worker + starter run is done by `scripts/provision.sh run-workflow --sdk <sdk> --dir <repo_path>` (PE-70) — a single synchronous call that starts the Worker, waits until it's polling (Temporal API, not `ps`/`pgrep`), runs the starter, and stops the Worker. The per-SDK repo, task queue, and run commands below are the **source of truth the script encodes** — they are reference, not commands you run by hand.
> **Note:** the clone + deps install are done by `scripts/provision.sh provision-and-scaffold`, and the Worker + starter run is done by `scripts/provision.sh run-workflow --sdk <sdk> --dir <repo_path>` — a single synchronous call that starts the Worker, waits until it's polling (Temporal API, not `ps`/`pgrep`), runs the starter, and stops the Worker. The per-SDK repo, task queue, and run commands below are the **source of truth the script encodes** — they are reference, not commands you run by hand.

How each branch connects: **all six SDKs load the named `cloud-setup` profile from `temporal.toml`** (env-config), so the key stays in the locked `0600` file — never in source, argv, or shell history. Step 6 writes that profile; nothing else is needed at run time. *(Verify the SDK's env-config symbol against current docs before relying on it.)*

Expand Down Expand Up @@ -88,7 +88,7 @@ Run: `ruby worker.rb` then `ruby starter.rb`.

---

## Package managers & minimum versions (PE-75 adaptation)
## Package managers & minimum versions

`scripts/provision.sh detect-tools --sdk <sdk>` reports which managers are **supported by the sample
AND installed**, picks a deterministic default (first available in preference order; the lockfile's
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,40 @@ To send the user to their **specific Workflow run** in the browser (preferred
https://cloud.temporal.io/namespaces/<namespace-handle>/workflows/<workflow-id>/<run-id>
```

The bare list URL (`…/workflows`) is a fallback only — surface the run-specific URL when you have the Workflow ID + Run ID (from the starter output or `workflow describe -o json`). `<namespace-handle>` is the namespace's full handle from `namespace create` — `<name>.<account-id>`, e.g. `quickstartai-go-20260617-143205.fmrip`.
The bare list URL (`…/workflows`) is a fallback only — surface the run-specific URL when you have the Workflow ID + Run ID (from the starter output or `workflow describe -o json`). `<namespace-handle>` is the namespace's full handle from `namespace create` — `<name>.<account-id>`, e.g. `quickstartai-go-20260617-143205.fmrip`.
## Auth-failure stderr wording (await-auth / workflow list)

`temporal --profile cloud-setup workflow list` is the auth-readiness poll. On failure
the Cloud API gateway (Envoy) returns a gRPC status whose `desc` is a **JWT-filter**
message — the wording is **JWT-anchored, never "api key"-anchored**. Captured against
the prerelease CLI:

| Condition | Exact stderr | Classification |
|---|---|---|
| Empty / missing key | `Error: failed reaching server: rpc error: code = Unauthenticated desc = Jwt is missing` | **transient** (can occur mid-propagation) |
| Bad / wrong-issuer key | `Error: failed reaching server: rpc error: code = Unauthenticated desc = Jwt issuer is not configured` | **transient** |
| Expired key | `Error: failed reaching server: rpc error: code = Unauthenticated desc = Jwt is expired` | **permanent → `key-expired` fast-fail** |
| mTLS-only namespace (wrong endpoint) | `Error: failed reaching server: connection error: desc = "error reading server preface: remote error: tls: certificate required"` | **transient** (TLS layer, not auth) |

Why only `expired` fast-fails: a valid key that is merely *propagating* has a **future
`exp`**, so it can never emit `Jwt is expired` — matching `expired` (anchored to
jwt/key/token) is safe against wrong-fast-failing a key that just needs a moment. The
other descs are ambiguous (can appear during propagation), so they stay transient and
resolve as `auth-timeout` with the redacted stderr line attached. This is the source of
truth for `await_auth_permanent()` in `scripts/provision.sh`.

> Note: `Jwt is missing` / `issuer is not configured` and the mTLS/TLS error were
> captured live. `Jwt is expired` is the standard Envoy JWT-filter default and the
> expected wording for a real expired key; it was not captured live (the prerelease
> `apikey create-for-me` emits the one-time secret only to a TTY, so a short-expiry
> key couldn't be minted+polled non-interactively). If a live capture ever differs,
> update the table and `await_auth_permanent()` together.

## API-key mint output drift (create-key)

The prerelease `apikey create-for-me -o json` does **not** put the one-time secret on
redirectable stdout: with stdout+stderr redirected (non-TTY) it returns **empty output
with exit 0** and, in that mode, may not persist a key at all. The secret is emitted to
the controlling **TTY** / as human text. This is why `cmd_create_key` captures BOTH
streams, falls back to a JWT-pattern scrape, and finally to a hidden `/dev/tty` paste —
and why the offline `key-empty` scenario (empty both streams, exit 0) is faithful.
20 changes: 0 additions & 20 deletions plugins/temporal/skills/temporal-cloud-setup/scripts/preview.sh

This file was deleted.

Loading