Skip to content

feat(ephemeral): nan run --ephemeral, nan ephemeral usage/settings - #58

Draft
sre-helmcode wants to merge 10 commits into
mainfrom
feat/ephemeral-e2-cli
Draft

sre-helmcode wants to merge 10 commits into
mainfrom
feat/ephemeral-e2-cli

Conversation

@sre-helmcode

@sre-helmcode sre-helmcode commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

What

Ephemeral runs (E2, NaN community) in the CLI. An ephemeral run is an agent in a fresh, throwaway container with no workspace. The container clones the GitHub repo you name, streams like any run and is destroyed at the end. It is billed per started minute by size.

  • nan run --ephemeral --repo OWNER/NAME --model ID [--size s|m|l] [--agent ...] [--timeout ...] [--byok-secret ...]
    • Sends executor: "ephemeral", size (only when given; the platform defaults to s) and github_repo, with no workspace. The workspace lookup is skipped.
    • The run streams, detaches and cancels exactly like a workspace run.
    • The CLI checks the flags locally and exits 64 with a clear message for these mistakes:
      • --repo is missing.
      • --repo is a URL. The message suggests the OWNER/NAME form.
      • --model is missing.
      • --size is not s, m or l.
      • --ws, --cwd or --worktree/--no-worktree are given with --ephemeral.
      • --repo or --size are given without --ephemeral.
    • Refusals:
      • 402 ephemeral_cap_reached exits 69. The message says to raise the cap in the browser Settings or wait, and points to nan ephemeral usage. Only this code reads as the cap; any other 402 keeps the generic mapping.
      • 402 payment_method_required exits 69 and points to "Add a payment method" in Settings → Ephemeral runs (free minutes included). 402 currency_migration_required exits 69: the subscription bills in USD and ephemeral runs bill in EUR, so use "Switch billing to EUR" in Settings → Ephemeral runs (also offered in the New run dialog). Both codes come with E3 (cloud-api-eph-e3).
      • 403 token_scope_denied on executor exits 65. The message says to use the session or a token with runs:ephemeral, and that sk- keys can't start these runs.
      • 400 on executor (flag off or unknown executor) exits 65 with "ephemeral runs are not available for your account".
    • The same mapping applies to apiExit and writeExit, so nan automations run and create/edit read the same.
    • When the run ends, stderr shows cost: €0.018 for 3 started minutes of size M, before your free minutes (nan ephemeral usage).
  • nan runs ls shows ephemeral M in the WORKSPACE column. A COST column appears only when an ephemeral run is listed: €x, pending, or - for workspace runs.
  • nan runs show shows these rows for an ephemeral run: executor with size and specs, cost, and repository. It hides the workspace, cwd and worktree rows.
    • For a session, it asks /api/ephemeral/usage (best effort, 3 s bound) and adds "not charged yet: billing is not active".
    • For tokens it adds a conditional note and makes no /api/ephemeral call.
  • Automations: executor: ephemeral and size: s|m|l work in create/edit files.
    • The CLI checks them before sending: unknown executor, unknown size, and size on a workspace executor.
    • On create it also checks: no workspace, checkout: fresh and github_repo set, no cwd, and a model (or the BYOK model).
    • automations ls/show show the executor and size.
    • The create/edit help has an ephemeral example.
  • nan automations run on an ephemeral automation announces queued in an ephemeral container (size M) for acme/api. Before, it printed queued on with an empty workspace name.
  • Wording changes for workspace runs: the announcement separator is now , Ctrl-C to detach everywhere (it used to be — in nan run and ; in nan automations run). The detach and stream-lost messages say "goes on at nan.builders" instead of "goes on in the workspace", which is true for both executors.
  • nan ephemeral usage shows this month's minutes, the cost after free minutes, and "not charged yet" while billing_enabled is false. It also shows the free S-minutes left (with the M and L equivalents), the cap, and the prices per size with how many S-minutes one minute uses.
  • nan ephemeral settings shows the cap, the concurrency limit and the billing state. It is read only and says writes happen in the browser Settings.
    • Without a membership, usage says "none: free minutes come with a membership". When the platform flags payment_method_required or currency_migration_required (E3 fields; absent means false), usage lists what to do before the next run.
    • The help says that free minutes come with a membership and that a payment method is always required, free minutes included.
    • Both commands use the session only (the platform refuses tokens on /api/ephemeral), have --json, and map 404 to "not available".
  • The help explains S-minutes wherever free minutes appear: nan ephemeral, nan ephemeral usage, nan run --help, nan automations create --help and the usage output. The text is: "Free minutes are S-minutes: one M minute uses 2, one L minute 4 (100 free minutes = 100 minutes of S, 50 of M or 25 of L)", split over two lines so the help stays within 80 columns (a test enforces this). Nothing claims "EU only".
  • runs.RunURL sends an ephemeral run to /runs/<id>, which matches the backend notification links in cloud-api-eph-e2-links.
  • README: notes that the COST column is conditional and that scripts should use nan runs ls --json. Also flags, an ephemeral section with the size/price table, and updated exit codes.

Why

E1 is live in prod for the owner (flag EPHEMERAL_ENABLED, billing off). E2 adds the member-facing surfaces. The API shapes were checked against cloud-api origin/main: internal/handlers/ephemeral.go, runs.go, automations.go, internal/runs/validate.go and internal/automations/validate.go.

Tests

Each new behavior has a test. The golden JSON in internal/api/testdata/ephemeral/ was rendered by cloud-api's own handlers (renderRun, renderAutomation, EphemeralHandler) through a throwaway test on origin/main. The CLI decodes exactly what the platform sends.

go test -race -shuffle=on -count=1 ./... (as CI runs it) passes: 416 top-level tests, 558 including subtests, 0 failures, 1 skip that was already there.

  • 21 new tests in cmd/ephemeral_test.go:
    • the request shape
    • the default size
    • detach
    • BYOK combined with ephemeral
    • 12 flag mistakes
    • size normalization
    • 6 refusal paths
    • apiExit/writeExit parity
    • golden views (runs show/ls, automations show/ls, usage, settings)
    • billing note: on, off, token, and the lookup failing
    • the COST column appearing only with ephemeral runs
    • usage facts
    • settings being read only
    • 404 and 403
    • the session-only credential
    • the automation-file checks (18 cases)
    • create/edit sending nothing on a bad file
    • formatEUR and started minutes
    • the S-minutes help text
    • the command tree
  • 7 new tests in internal/api/ephemeral_test.go, and 1 in internal/runs.
  • The --json contract test covers nan ephemeral usage/settings and nan run --ephemeral --json.

gofmt -l . is clean, go vet ./... is clean, and cross-builds for linux/windows/darwin succeed. The binary was smoke-tested for the usage errors, nan ephemeral --help, and usage without a session (exit 65).

Release (Lead: what to do to publish)

This PR bumps internal/tui.Version 0.1.34 → 0.1.35 in its own chore: 0.1.35 commit. That is the release process from CONTRIBUTING.md "Releases". Nothing is tagged or published by this PR.

  1. When the blocking reviews pass, take the PR out of draft and merge it into main.
  2. CI on main must go green. Then .github/workflows/tag.yml creates the tag v0.1.35 and calls release.yml, which builds, signs and publishes the archives. There is no manual tag step.
  3. Check gh release view v0.1.35 -R helmcode/nan-cli, then curl -fsSL https://nan.builders/install | bash && nan --version (expect nan 0.1.35).
  4. Version collision: other open nan-cli branches (writeapi-cli, notif-cli) may also bump to 0.1.35. Whichever merges second must re-bump to the next patch, or no release happens for it (the tag already exists). Read Version on main right before merging.

Gaps / notes

  • Docs link: the ephemeral help (nan ephemeral, nan ephemeral usage/settings, the "Ephemeral runs" part of nan run --help) and the README's ephemeral section link to https://nan.builders/docs/ephemeral-runs, added by docs: ephemeral runs (EN/ES) nan#198. General run docs stay at /docs/runs. Merge #198 before this PR, or at about the same time, so the link resolves.
  • No secrets on ad-hoc runs: POST /v1/runs has no secrets field, so a CLI ephemeral run can only clone a public repo. The help says so and points to ephemeral automations with an agent GITHUB_TOKEN.
  • No checkout ref flags: --sha, --pr and --base are not exposed. The server accepts checkout{sha,pr_number,base_ref}, so this can be a follow-up.
  • Prices in help are fixed: the help quotes the confirmed prices (€0.003/0.006/0.012) as static text. nan ephemeral usage always shows the live prices.
  • Billing note needs a session: the note under a run's cost asks /api/ephemeral/usage, which only answers sessions. A token sees the conditional wording instead.

🤖 Generated with Claude Code

barckcode and others added 10 commits October 19, 2026 10:36
Run carries executor, size and cost; CreateRunRequest can ask for an
ephemeral run (no workspace); Automation carries executor, size and
checkout; features.ephemeral; GET /api/ephemeral/usage and settings.
Golden responses rendered by cloud-api's own handlers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… README

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… run goes

Only ephemeral_cap_reached reads as the cap; payment_method_required and
currency_migration_required say what to do; any other 402 keeps the usual
mapping. nan automations run of an ephemeral automation names the container
instead of an empty workspace. Free minutes come with a membership and a
payment method is required (help, usage). Help within 80 columns, one
Ctrl-C separator, settings range in euros, README note on --json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Add a payment method" and "Switch billing to EUR" live in Settings →
Ephemeral runs (the EUR switch is also offered in the New run dialog).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…59)

nan run --ephemeral --repo OWNER/NAME --github-token-secret NAME names one
of the member's secrets holding a GitHub token. The platform uses it only
to clone the private repository and never gives it to the agent
(helmcode/nan-cloud-api#382).

- Usage error (64) without --ephemeral, without --repo, for an empty value
  or a value that is not a secret name; a pasted GitHub token is never
  echoed and the member is told to revoke it.
- The platform's 404 not_found and 400 on github_token_secret map to clear
  usage errors instead of "runs are not enabled".
- nan run --help and README no longer say an ephemeral run started from the
  CLI has to use a public repository.

Co-authored-by: barckcode <cristian@helmcode.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants