Skip to content

Clarify limitations of anonymous workspaces and transition guidance#100

Open
k-taro56 wants to merge 10 commits into
mainfrom
eng-353
Open

Clarify limitations of anonymous workspaces and transition guidance#100
k-taro56 wants to merge 10 commits into
mainfrom
eng-353

Conversation

@k-taro56
Copy link
Copy Markdown
Contributor

@k-taro56 k-taro56 commented May 2, 2026

This pull request updates both English and Japanese documentation to clarify the single-device limitation of anonymous accounts and improves the error handling and user guidance around authentication failures. The changes ensure users are explicitly informed that anonymous workspaces are tied to the originating machine, and provide actionable messages when authentication issues occur, especially around multi-device usage and anonymous account expiration.

Anonymous account limitations and user guidance:

  • All docs now clearly state that anonymous accounts/workspaces are strictly single-device; copying credentials to another machine will result in authentication errors, and users are advised to use arkor login --oauth for multi-device access. [1] [2] [3] [4] [5] [6] [7]
  • Both English and Japanese docs surface this limitation at the point of anonymous workspace creation and in relevant CLI output, ensuring consistent messaging before users encounter errors. [1] [2]

Authentication error handling improvements:

  • Documentation now describes new structured error codes (anonymous_token_single_device, anonymous_account_not_found) returned by the cloud API, and how the CLI turns these into actionable, user-facing guidance (with exit code 1), both in English and Japanese. [1] [2] [3] [4] [5]
  • The old generic "Token may be expired" message is removed; unknown errors now show the upstream error message verbatim, reducing confusion and improving transparency. [1] [2] [3]

Other clarifications:

  • Documentation for anonymous token expiration and refresh behavior is updated, noting the 90-day TTL and that auto-refresh is not yet implemented. [1] [2]
  • CLI guidance for attaching anonymous workspaces to an account and the implications for multi-device workflows is made more explicit in both languages. [1] [2] [3] [4]

These changes improve user understanding of anonymous workspace constraints and make authentication failures easier to diagnose and resolve.Updated the documentation to emphasize that anonymous workspaces are single-device only, and provided clear instructions for users on how to transition to an OAuth account for multi-device access. This includes updates to the README and CLI documentation, ensuring users are aware of the constraints and the necessary steps to retain their work across devices.

Updated the documentation to emphasize that anonymous workspaces are single-device only, and provided clear instructions for users on how to transition to an OAuth account for multi-device access. This includes updates to the README and CLI documentation, ensuring users are aware of the constraints and the necessary steps to retain their work across devices.
@k-taro56 k-taro56 self-assigned this May 2, 2026
Copilot AI review requested due to automatic review settings May 2, 2026 14:33
@mintlify
Copy link
Copy Markdown

mintlify Bot commented May 2, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
arkor-92aeef0e 🟢 Ready View Preview May 2, 2026, 2:33 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 2, 2026

Codecov Report

❌ Patch coverage is 96.55172% with 2 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
packages/arkor/src/cli/main.ts 88.23% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 80adb17491

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +160 to +161
ui.log.info(
"Note: anonymous accounts work on this machine only. Run `arkor login --oauth` to sign up for multi-device access.",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate OAuth signup hint when OAuth availability is unknown

This message is emitted even when runAnonymousLogin is called with oauthAvailable: false or undefined (e.g. explicit arkor login --anonymous or anon-only deployments), so users are told to run arkor login --oauth in environments where that command is expected to fail. That regresses the existing gating behavior documented in cli/anonymous.ts and produces a dead-end recovery path for a real deployment mode.

Useful? React with 👍 / 👎.

Comment thread packages/arkor/src/cli/commands/dev.ts Outdated
Comment on lines +159 to +160
ui.log.info(
"Note: anonymous accounts work on this machine only. Run `arkor login --oauth` to sign up for multi-device access.",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Suppress OAuth-only guidance on anonymous-only deployments

In ensureCredentialsForStudio, oauthAvailable can be false (deployment explicitly does not advertise Auth0), but this unconditional note still instructs the user to run arkor login --oauth. On anon-only deployments this points users to a command that cannot succeed, which is especially confusing during first-run arkor dev onboarding where this path is common.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Arkor’s CLI and documentation around anonymous authentication, aiming to make the single-device limitation explicit and to improve how anonymous auth failures are surfaced to users. It touches both runtime messaging and English/Japanese docs so the guidance is more consistent across the product surface.

Changes:

  • Adds structured anonymous-auth error handling in the CLI via CloudApiError.code and a formatter for known anonymous failure modes.
  • Surfaces new single-device guidance in anonymous login/dev/whoami flows and updates template/readme/docs copy accordingly.
  • Expands English and Japanese auth/dev documentation to describe new error codes, anonymous-workspace constraints, and recovery guidance.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
packages/cli-internal/src/templates.ts Updates scaffolded quickstart text about anonymous-account limitations and upgrade path.
packages/arkor/src/core/client.ts Extends CloudApiError with structured code support and exports the error builder.
packages/arkor/src/core/anonymous-auth-error.ts Adds formatter/helpers for anonymous auth dead-end errors.
packages/arkor/src/core/anonymous-auth-error.test.ts Adds unit tests for anonymous auth error formatting helpers.
packages/arkor/src/cli/main.ts Intercepts known anonymous auth errors at top level and prints friendly guidance.
packages/arkor/src/cli/commands/whoami.ts Reworks non-2xx handling and adds anonymous-account guidance on success.
packages/arkor/src/cli/commands/whoami.test.ts Updates tests for thrown CloudApiError behavior from whoami.
packages/arkor/src/cli/commands/login.ts Adds post-anonymous-login note about the single-device limitation.
packages/arkor/src/cli/commands/dev.ts Adds matching anonymous-account guidance during Studio credential bootstrap.
packages/arkor/README.md Documents anonymous accounts as single-device and explains limitations.
docs/ja/cli/dev.mdx Japanese dev docs updated with new anonymous-account messaging.
docs/ja/cli/auth.mdx Japanese auth docs updated for new error codes and anonymous-account behavior.
docs/cli/dev.mdx English dev docs updated with single-device and bootstrap guidance.
docs/cli/auth.mdx English auth docs updated with structured error-code behavior and expiry guidance.
README.md Root README quickstart and feature bullets now mention single-device anonymous workspaces.
README.ja.md Japanese root README mirrors the new anonymous-workspace guidance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/cli/auth.mdx Outdated
- `code: "anonymous_token_single_device"` → ``Anonymous credentials were rejected as single-device. Anonymous accounts only work on one machine. Sign up for an account that supports multiple devices: arkor login --oauth`` (exit `1`).
- `code: "anonymous_account_not_found"` → ``Your anonymous credentials are no longer valid. Sign up to continue: arkor login --oauth`` (exit `1`).

Errors without a known `code` propagate as commander's default error message (the upstream `error` body), so the user sees the cloud-api's reason verbatim.
Comment on lines +71 to +75
// second machine. `arkor login --oauth` is the explicit upgrade
// path; phrasing matches the auth-error formatter so users see the
// same advice from both surfaces.
process.stdout.write(
"\nNote: anonymous accounts work on this machine only. Run `arkor login --oauth` to sign up for multi-device access.\n",
Comment thread docs/cli/dev.mdx Outdated
| `No credentials on file — requesting an anonymous token.` | Same as above on anon-only deployments (no Auth0 advertised in `/v1/auth/cli/config`). The CLI omits the `arkor login --oauth` hint because that command would fail there. | Nothing required. |
| ``Anonymous id: <id> — Arkor Cloud uses this id to recognise this client across sessions. Keep `<home>/.arkor/credentials.json` to stay signed in as the same anonymous identity.`` | Informational follow-up after the anonymous bootstrap completes — surfaces the cloud-side identifier and where it lives (the path is the resolved `credentialsPath()`, typically `~/.arkor/credentials.json` on Linux and macOS). | Nothing required. Back up the credentials file if you want to keep using the same anonymous identity from another machine. |
| ``Anonymous sessions aren't guaranteed to persist — sign in with `arkor login --oauth` to tie future work to your Arkor Cloud account.`` | Persistence nudge fired alongside the success message when the deployment is known to support OAuth. Anonymous work has no SLA on the cloud-api side, so the CLI surfaces the upgrade path before you invest real work. Suppressed on anon-only deployments. | Optional: run `arkor login --oauth` to tie future work to your account. Existing anonymous work stays under its current id; there is no migration path today. |
| ``Note: anonymous accounts work on this machine only. Run `arkor login --oauth` to sign up for multi-device access.`` | Informational note fired alongside the bootstrap success line on every deployment. Anonymous accounts are bound to the issuing machine via the cloud-api's single-device guard, so the CLI surfaces the limitation up front rather than waiting for the first 401 on a second machine. | Optional. If you need multi-device access, run `arkor login --oauth` for an OAuth account. Existing anonymous work cannot be migrated; the OAuth account starts fresh. |
Comment thread docs/ja/cli/dev.mdx Outdated
| `No credentials on file — requesting an anonymous token.`(認証情報ファイルがありません。匿名トークンを要求します) | 同上だが匿名専用デプロイの場合(`/v1/auth/cli/config` で Auth0 がアドバタイズされていない)。`arkor login --oauth` は失敗するので OAuth ヒントは省かれる。 | 何もしなくてよい。 |
| ``Anonymous id: <id> — Arkor Cloud uses this id to recognise this client across sessions. Keep `<home>/.arkor/credentials.json` to stay signed in as the same anonymous identity.``(匿名 id: `<id>`。Arkor Cloud はこの id でセッション間でこのクライアントを識別します。同じ匿名 identity を維持するには認証情報ファイルを保持してください。パスは `credentialsPath()` の解決結果で、Linux と macOS では通常 `~/.arkor/credentials.json`) | 匿名ブートストラップ完了後の情報行。クラウド側の識別子と、それを保持しているファイルの場所を明示する。 | 何もしなくてよい。別マシーンから同じ匿名 identity を使いたいなら認証情報ファイルをバックアップ。 |
| ``Anonymous sessions aren't guaranteed to persist — sign in with `arkor login --oauth` to tie future work to your Arkor Cloud account.``(匿名セッションは永続性が保証されないので、今後の作業を Arkor Cloud アカウントに紐付けたいなら `arkor login --oauth` でサインインしてください) | デプロイが OAuth をサポートすると分かっているときに、成功メッセージと並んで出る永続性ナッジ。匿名作業はクラウド API 側で SLA がないので、本格的に作業する前にアップグレード経路を提示している。匿名専用デプロイでは抑制される。 | 任意。今後の作業をアカウントに紐付けたいなら `arkor login --oauth`。既存の匿名作業はその id に残り、現状マイグレーションパスはない。 |
| ``Note: anonymous accounts work on this machine only. Run `arkor login --oauth` to sign up for multi-device access.``(注意: 匿名アカウントはこのマシーンでのみ動作します。複数端末で使うには `arkor login --oauth` でサインアップしてください) | 匿名ブートストラップ成功行と並んで全デプロイで出る情報行。匿名アカウントは発行マシーンに対して単一端末ガードでバインドされるので、別端末での 401 を待たず最初に制約を提示する。 | 任意。複数端末でのアクセスが必要なら OAuth アカウント用に `arkor login --oauth`。既存の匿名作業はマイグレーションできず、OAuth アカウントは新規スタート。 |
Comment thread README.md Outdated
`arkor dev` opens **Studio**, a local web UI at `http://localhost:4000`. On first launch it provisions a throwaway anonymous workspace so you can fire off a real training run right away.

Run `arkor login --oauth` later if you want to claim your work under an account.
Anonymous workspaces are tied to this machine. They only work where you ran `arkor dev` first. Run `arkor login --oauth` later to attach your work to an account that follows you across devices.
Comment on lines +68 to +75
if (isAnonymous) {
// Anonymous accounts are single-device on purpose, so surface the
// limitation here so users discover it before hitting a 401 on a
// second machine. `arkor login --oauth` is the explicit upgrade
// path; phrasing matches the auth-error formatter so users see the
// same advice from both surfaces.
process.stdout.write(
"\nNote: anonymous accounts work on this machine only. Run `arkor login --oauth` to sign up for multi-device access.\n",
Comment thread docs/ja/cli/auth.mdx Outdated
- `code: "anonymous_token_single_device"` → ``Anonymous credentials were rejected as single-device. Anonymous accounts only work on one machine. Sign up for an account that supports multiple devices: arkor login --oauth``(和訳: 匿名認証情報が単一端末ポリシー違反として拒否されました。複数端末で使うにはアカウントへサインアップしてください)。終了コード `1`。
- `code: "anonymous_account_not_found"` → ``Your anonymous credentials are no longer valid. Sign up to continue: arkor login --oauth``(和訳: 匿名認証情報はもう有効ではありません。続けるにはサインアップしてください)。終了コード `1`。

既知の `code` を持たないエラーは commander のデフォルトとして上流の `error` 本文をそのまま表示するので、ユーザーはクラウド API の理由を一字一句見ます。
Comment on lines +158 to +161
// machine. The wording aligns with `formatAnonymousAuthError` so the
// hint they see now matches the error they'd see later.
ui.log.info(
"Note: anonymous accounts work on this machine only. Run `arkor login --oauth` to sign up for multi-device access.",
Comment on lines +159 to +161
ui.log.info(
"Note: anonymous accounts work on this machine only. Run `arkor login --oauth` to sign up for multi-device access.",
);
Comment thread packages/arkor/src/cli/main.ts Outdated
Comment on lines +153 to +165
} catch (err) {
// Intercept the structured anonymous-auth-state errors before
// commander's default handler converts them into a noisy stack
// trace. The helper returns a CLI-shaped string for the two known
// dead-end codes (`anonymous_token_single_device`,
// `anonymous_account_not_found`); everything else rethrows so
// commander still surfaces it. Setting `process.exitCode` (rather
// than calling `process.exit` directly) keeps the deprecation +
// telemetry-shutdown step in the `finally` block reachable.
const friendly = formatAnonymousAuthError(err);
if (friendly !== null) {
process.stderr.write(`${friendly}\n`);
process.exitCode = 1;
…ansition

Updated documentation across multiple files to clarify that anonymous workspaces are strictly single-device and that switching to OAuth does not migrate existing anonymous work. Clear guidance is provided for users on how to transition to an OAuth account for multi-device access, ensuring they understand the implications of their workspace choices.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +60 to +62
typeof body.user === "object" &&
body.user !== null &&
(body.user as { kind?: unknown }).kind === "anonymous";
Comment thread docs/cli/dev.mdx Outdated
@@ -60,6 +60,7 @@ This means `arkor dev` is safe on a shared dev machine: another tab cannot read
| `No credentials on file — requesting an anonymous token.` | Same as above on anon-only deployments (no Auth0 advertised in `/v1/auth/cli/config`). The CLI omits the `arkor login --oauth` hint because that command would fail there. | Nothing required. |
| ``Anonymous id: <id> — Arkor Cloud uses this id to recognise this client across sessions. Keep `<home>/.arkor/credentials.json` to stay signed in as the same anonymous identity.`` | Informational follow-up after the anonymous bootstrap completes — surfaces the cloud-side identifier and where it lives (the path is the resolved `credentialsPath()`, typically `~/.arkor/credentials.json` on Linux and macOS). | Nothing required. Back up the credentials file if you want to keep using the same anonymous identity from another machine. |
Comment thread docs/ja/cli/dev.mdx
| `No credentials on file — requesting an anonymous token.`(認証情報ファイルがありません。匿名トークンを要求します) | 同上だが匿名専用デプロイの場合(`/v1/auth/cli/config` で Auth0 がアドバタイズされていない)。`arkor login --oauth` は失敗するので OAuth ヒントは省かれる。 | 何もしなくてよい。 |
| ``Anonymous id: <id> — Arkor Cloud uses this id to recognise this client across sessions. Keep `<home>/.arkor/credentials.json` to stay signed in as the same anonymous identity.``(匿名 id: `<id>`。Arkor Cloud はこの id でセッション間でこのクライアントを識別します。同じ匿名 identity を維持するには認証情報ファイルを保持してください。パスは `credentialsPath()` の解決結果で、Linux と macOS では通常 `~/.arkor/credentials.json`) | 匿名ブートストラップ完了後の情報行。クラウド側の識別子と、それを保持しているファイルの場所を明示する。 | 何もしなくてよい。別マシーンから同じ匿名 identity を使いたいなら認証情報ファイルをバックアップ。 |
| ``Anonymous sessions aren't guaranteed to persist — sign in with `arkor login --oauth` to tie future work to your Arkor Cloud account.``(匿名セッションは永続性が保証されないので、今後の作業を Arkor Cloud アカウントに紐付けたいなら `arkor login --oauth` でサインインしてください) | デプロイが OAuth をサポートすると分かっているときに、成功メッセージと並んで出る永続性ナッジ。匿名作業はクラウド API 側で SLA がないので、本格的に作業する前にアップグレード経路を提示している。匿名専用デプロイでは抑制される。 | 任意。今後の作業をアカウントに紐付けたいなら `arkor login --oauth`。既存の匿名作業はその id に残り、現状マイグレーションパスはない。 |
| ``Note: anonymous accounts work on this machine only.``(OAuth サポート時には ``Run `arkor login --oauth` to sign up for multi-device access.`` が後に続く / 和訳: 注意: 匿名アカウントはこのマシーンでのみ動作します。複数端末で使うには `arkor login --oauth` でサインアップしてください) | 匿名ブートストラップ成功行と並んで出る情報行。匿名アカウントは発行マシーンに対して単一端末ガードでバインドされるので、別端末での 401 を待たず最初に制約を提示する。OAuth フレーバーのアップグレードヒントは永続性ナッジと同じ `oauthAvailable` でゲートされるので、匿名専用デプロイでは bare な fact のみが出る(`arkor login --oauth` は失敗するため)。 | 任意。OAuth サポートのデプロイでは、複数端末でアカウント付きの作業を始めたいタイミングで `arkor login --oauth` を実行。既存の匿名作業はマイグレーションできず、OAuth アカウントは新規スタート。 |
Comment thread docs/cli/auth.mdx Outdated
- on anon-only deployments: ``Anonymous credentials were rejected as single-device. Anonymous accounts only work on one machine. This deployment does not advertise OAuth, so the only recovery is to mint a new anonymous identity (your previous workspace data cannot be recovered): arkor login --anonymous`` (exit `1`).
- `code: "anonymous_account_not_found"` → analogous OAuth-vs-anon-only split, ending in `arkor login --oauth` or `arkor login --anonymous`.

Errors without a known `code` (and any non-`CloudApiError` exceptions) are rethrown out of `main()`. They reach Node's top-level rejection handler via `bin.ts`, which logs them with the standard error formatting (class name + stack trace) and exits non-zero — so you'll see something like `CloudApiError: cloud-api 503` followed by a stack frame, not just the upstream `error` body.
Comment thread docs/ja/cli/auth.mdx Outdated
- 匿名専用デプロイ: ``Anonymous credentials were rejected as single-device. Anonymous accounts only work on one machine. This deployment does not advertise OAuth, so the only recovery is to mint a new anonymous identity (your previous workspace data cannot be recovered): arkor login --anonymous``(終了コード `1`)。
- `code: "anonymous_account_not_found"` も同様に分岐し、末尾は `arkor login --oauth` または `arkor login --anonymous` のいずれか。

既知の `code` を持たないエラー(および `CloudApiError` 以外の例外)は `main()` から再 throw され、`bin.ts` には catch がないので Node のデフォルトのトップレベル拒否ハンドラが標準のエラーフォーマット(クラス名 + スタックトレース)で表示し、非ゼロで終了します。つまり `CloudApiError: cloud-api 503` の後にスタックフレームが見え、上流の `error` 本文だけが見えるわけではありません。
Comment thread docs/cli/auth.mdx Outdated
| `Failed to fetch /v1/me (<status>). Token may be expired.` | `arkor whoami` | The cloud-api rejected the request with a non-200, non-426 status. Most often expired access tokens. | Re-run `arkor login` matching the current `mode`: `arkor login --oauth` for `mode: "auth0"`, `arkor login --anonymous` for `mode: "anon"` (the latter mints a new `anonymousId`, so it lands in a new workspace). The exit code stays `0` so wrapper scripts can inspect the message. |
| ``Anonymous credentials were rejected as single-device. …`` (followed by either `arkor login --oauth` or `arkor login --anonymous` depending on deployment) | `arkor whoami` and any other authenticated command | The cloud-api rejected the request with `code: "anonymous_token_single_device"` (HTTP 401 from the userAuth jti check or HTTP 409 from the rotate-jti CAS). Either the credentials file was copied to a second device, or another local process refreshed past this token within the recovery window. | Follow the command in the message: `arkor login --oauth` to sign up for an OAuth account on supporting deployments, or `arkor login --anonymous` to mint a new anonymous identity on anon-only deployments. Either path is a *new* identity; existing anonymous work cannot be migrated. The CLI exits `1`. |
| ``Your anonymous credentials are no longer valid. …`` (followed by either `arkor login --oauth` or `arkor login --anonymous`) | `arkor whoami` and any other authenticated command | The cloud-api rejected the request with `code: "anonymous_account_not_found"` (HTTP 401). The underlying `anonymous_users` row was deleted (admin / cascade / explicit revocation). | Same as above — follow the deployment-aware command in the message. The previous anonymous workspace cannot be recovered. The CLI exits `1`. |
| `CloudApiError: cloud-api <status>` (and a stack trace) | `arkor whoami` and any other authenticated command | A non-200 / non-426 cloud-api response without a structured auth-state `code` (transient 5xx, an unmapped 4xx, etc.). `cli/main.ts` rethrows it; bin.ts has no catch, so Node's default top-level rejection handler renders it. | Inspect the upstream message at the top of the trace; if it looks like a transport/server failure, retry. For an expired OAuth access token, re-run `arkor login --oauth`. For an expired anonymous token, re-run `arkor login --anonymous` (mints a new `anonymousId`). |
Comment thread docs/ja/cli/auth.mdx Outdated
| `Failed to fetch /v1/me (<status>). Token may be expired.`(`/v1/me` の取得に失敗しました(`<status>`)。トークンが期限切れの可能性があります) | `arkor whoami` | クラウド API が 200 と 426 以外のステータスで拒否した。多くは期限切れアクセストークン。 | 現在のモードに合わせて再ログイン: OAuth セッション(`mode: "auth0"`)なら `arkor login --oauth`、匿名セッション(`mode: "anon"`)なら `arkor login --anonymous`(新しい `anonymousId` で別ワークスペースになる点に注意)。終了コードは `0` のままで、ラッパースクリプトはメッセージを検査できる。 |
| ``Anonymous credentials were rejected as single-device. …``(末尾は `arkor login --oauth` または `arkor login --anonymous`、デプロイ形態次第) | `arkor whoami` および認証付きの全コマンド | クラウド API が `code: "anonymous_token_single_device"` で拒否した(userAuth の jti チェックからの HTTP 401、または rotate-jti の CAS からの HTTP 409)。認証情報ファイルが別端末にコピーされたか、別のローカルプロセスがこのトークンを recovery window 内でローテートし越えた、のいずれか。 | メッセージ末尾のコマンドに従う: OAuth サポートのデプロイなら `arkor login --oauth`、匿名専用デプロイなら `arkor login --anonymous`。どちらも *新規* identity で、過去の匿名作業は移行できない。CLI は `1` で終了。 |
| ``Your anonymous credentials are no longer valid. …``(末尾は `arkor login --oauth` または `arkor login --anonymous`) | `arkor whoami` および認証付きの全コマンド | クラウド API が `code: "anonymous_account_not_found"` で拒否した(HTTP 401)。背後の `anonymous_users` 行が削除された(admin / cascade / 明示的な revoke)。 | 上と同じく、メッセージ末尾のデプロイ依存コマンドに従う。前の匿名ワークスペースは取り戻せない。CLI は `1` で終了。 |
| `CloudApiError: cloud-api <status>`(とスタックトレース) | `arkor whoami` および認証付きの全コマンド | 構造化された認証状態 `code` を持たない非 200 / 非 426 のクラウド API レスポンス(一過性の 5xx、未マップの 4xx など)。`cli/main.ts` がそのまま再 throw し、`bin.ts` に catch がないので Node のデフォルトのトップレベル拒否ハンドラがレンダリングする。 | スタック先頭の上流メッセージを確認し、トランスポート / サーバー障害なら再試行。OAuth アクセストークンの期限切れなら `arkor login --oauth` を再実行。匿名トークンの期限切れなら `arkor login --anonymous` を再実行(新しい `anonymousId` で別ワークスペースになる)。 |
…dency

PR #100 round 3 (Copilot, 2026-05-02 15:26 UTC). Three concrete issues
to chase:

1. `whoami.ts` keyed the single-device note on
   `body.user.kind === "anonymous"`, but the cloud-api `/v1/me` schema
   doesn't guarantee a `kind` field on the response — and the existing
   test fixtures (and E2E response shapes) don't surface one. That meant
   the note silently never fired for anonymous users in practice. Switch
   to `creds.mode === "anon"` (already in scope from `readCredentials()`)
   so the discriminator is local-only and can't drift with the server
   schema. Update the unit test fixture to a plain user object so a
   regression that re-introduces a body-shape dependency would catch it.

2. The previous commit's docs claimed unmapped `CloudApiError`s reach
   "Node's default top-level rejection handler because `bin.ts` has no
   catch". That was wrong: `bin.ts:54-58` actually wraps the top-level
   `await main()` in a try/catch that logs `err.stack ?? err.message`
   to stderr and sets `process.exitCode = 1`. The explicit catch is
   there to dodge the bundled minified code-frame Node's default would
   surface, and to keep stderr flush deterministic across the supported
   Node range. Update both the prose paragraph and the troubleshooting
   table row in `docs/cli/auth.mdx` + `docs/ja/cli/auth.mdx`.

3. `docs/cli/dev.mdx` (and Japanese counterpart) had a pre-existing
   table row telling readers to "back up the credentials file if you
   want to keep using the same anonymous identity from another machine"
   directly above the new single-device note that says cross-device use
   is rejected. Reword the action text so the backup advice is scoped
   to local recovery on the *same* machine and points at the
   single-device row for cross-device guidance.

344 unit tests pass; new fixture exercises the post-regression path.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +164 to +167
ui.log.info(
opts.oauthAvailable === true
? ANON_SINGLE_DEVICE_NOTE_WITH_OAUTH
: ANON_SINGLE_DEVICE_NOTE,
Comment on lines +167 to +170
ui.log.info(
oauthAvailable
? ANON_SINGLE_DEVICE_NOTE_WITH_OAUTH
: ANON_SINGLE_DEVICE_NOTE,
Comment on lines 72 to +73
const message = fields.error || text || `cloud-api ${res.status}`;
return new CloudApiError(res.status, message);
return new CloudApiError(res.status, message, fields.code);
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1869df3b43

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/arkor/src/cli/main.ts Outdated
*/
async function probeOauthAvailability(): Promise<boolean> {
try {
const cfg = await fetchCliConfig(defaultArkorCloudApiUrl());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Probe OAuth config against the failing deployment URL

The anonymous-auth formatter now decides between --oauth and --anonymous by always calling fetchCliConfig(defaultArkorCloudApiUrl()), but the CloudApiError being handled may come from a different base URL (for example, commands that use a non-default API endpoint). In that case this probe can inspect the wrong deployment and emit the opposite recovery path, sending users to a command that fails on their actual target deployment. The probe should use the same base URL that produced the auth error (or the active credentials’ API URL) instead of the global default.

Useful? React with 👍 / 👎.

…ating

- main.ts: probe `/v1/auth/cli/config` against the credentials' own
  `arkorCloudApiUrl` instead of the global default, so users on a
  non-default deployment (or with a stale `ARKOR_CLOUD_API_URL`) get the
  recovery hint that matches the deployment that actually rejected them.
  (Codex P2 on PR #100)
- main.test.ts: partial-mock `core/credentials` so `readCredentials`
  becomes controllable per-test, and add a case that pins the probe URL
  to the credentials' value rather than the env-derived default.
- client.test.ts: cover `buildCloudApiError` preserving `code` (the
  field cli/main.ts pivots on for the friendly anon-auth-error path) and
  the `code === undefined` fall-through on bodies without it.
- login.test.ts / dev.test.ts: add gating tests for
  `ANON_SINGLE_DEVICE_NOTE` mirroring the persistence-nudge tests, so the
  OAuth-flavoured variant only fires when `oauthAvailable === true`.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +48 to +53
// Throw a `CloudApiError` carrying the structured `code` so the
// top-level handler in `cli/main.ts` can format anonymous auth-state
// failures (`anonymous_token_single_device`,
// `anonymous_account_not_found`) into actionable guidance instead of
// a generic "Token may be expired" line.
throw await buildCloudApiError(res);
// them at a command that fails immediately. The matching login/dev
// surfaces, which already know `oauthAvailable`, do append the
// upgrade hint when warranted.
process.stdout.write(`\n${ANON_SINGLE_DEVICE_NOTE}\n`);
Comment thread docs/cli/dev.mdx Outdated
| ``No credentials on file — bootstrapping an anonymous session. Run `arkor login --oauth` to sign in to your account instead.`` | First `arkor dev` on this machine when the deployment advertises OAuth. The CLI bootstraps anonymous so Studio can start immediately; the message is informational, not an error. | Nothing required. To upgrade to a real account, run `arkor login --oauth` separately (it overwrites `~/.arkor/credentials.json`) and refresh Studio. |
| `No credentials on file — requesting an anonymous token.` | Same as above on anon-only deployments (no Auth0 advertised in `/v1/auth/cli/config`). The CLI omits the `arkor login --oauth` hint because that command would fail there. | Nothing required. |
| ``Anonymous id: <id> — Arkor Cloud uses this id to recognise this client across sessions. Keep `<home>/.arkor/credentials.json` to stay signed in as the same anonymous identity.`` | Informational follow-up after the anonymous bootstrap completes — surfaces the cloud-side identifier and where it lives (the path is the resolved `credentialsPath()`, typically `~/.arkor/credentials.json` on Linux and macOS). | Nothing required. Back up the credentials file if you want to keep using the same anonymous identity from another machine. |
| ``Anonymous id: <id> — Arkor Cloud uses this id to recognise this client across sessions. Keep `<home>/.arkor/credentials.json` to stay signed in as the same anonymous identity.`` | Informational follow-up after the anonymous bootstrap completes — surfaces the cloud-side identifier and where it lives (the path is the resolved `credentialsPath()`, typically `~/.arkor/credentials.json` on Linux and macOS). | Nothing required. The "stay signed in as the same anonymous identity" guidance just means: don't delete the file. The cloud-api enforces single-device on the server side, so the file is not portable. Copying it to another machine will be rejected on the next refresh, and once a refresh lands (the current SDK doesn't auto-refresh, but it's on the roadmap), even an older backup of the file on this machine becomes stale because the server's `latest_jti` has moved on. Treat the file as live state, not as something to back up and restore. |
Comment thread docs/ja/cli/dev.mdx Outdated
| ``No credentials on file — bootstrapping an anonymous session. Run `arkor login --oauth` to sign in to your account instead.``(認証情報ファイルがありません。匿名セッションをブートストラップします。アカウントでサインインしたい場合は `arkor login --oauth` を実行してください) | OAuth をアドバタイズしているデプロイでこのマシーン初回の `arkor dev`。Studio をすぐ起動できるよう CLI が匿名でブートストラップしている旨の案内で、エラーではない。 | 何もしなくてよい。本物のアカウントにアップグレードしたいなら、別途 `arkor login --oauth` を実行(`~/.arkor/credentials.json` を上書き)して Studio をリロード。 |
| `No credentials on file — requesting an anonymous token.`(認証情報ファイルがありません。匿名トークンを要求します) | 同上だが匿名専用デプロイの場合(`/v1/auth/cli/config` で Auth0 がアドバタイズされていない)。`arkor login --oauth` は失敗するので OAuth ヒントは省かれる。 | 何もしなくてよい。 |
| ``Anonymous id: <id> — Arkor Cloud uses this id to recognise this client across sessions. Keep `<home>/.arkor/credentials.json` to stay signed in as the same anonymous identity.``(匿名 id: `<id>`。Arkor Cloud はこの id でセッション間でこのクライアントを識別します。同じ匿名 identity を維持するには認証情報ファイルを保持してください。パスは `credentialsPath()` の解決結果で、Linux と macOS では通常 `~/.arkor/credentials.json`) | 匿名ブートストラップ完了後の情報行。クラウド側の識別子と、それを保持しているファイルの場所を明示する。 | 何もしなくてよい。別マシーンから同じ匿名 identity を使いたいなら認証情報ファイルをバックアップ。 |
| ``Anonymous id: <id> — Arkor Cloud uses this id to recognise this client across sessions. Keep `<home>/.arkor/credentials.json` to stay signed in as the same anonymous identity.``(匿名 id: `<id>`。Arkor Cloud はこの id でセッション間でこのクライアントを識別します。同じ匿名 identity を維持するには認証情報ファイルを保持してください。パスは `credentialsPath()` の解決結果で、Linux と macOS では通常 `~/.arkor/credentials.json`) | 匿名ブートストラップ完了後の情報行。クラウド側の識別子と、それを保持しているファイルの場所を明示する。 | 何もしなくてよい。「同じ匿名 identity を維持」の案内は「ファイルを削除しない」という意味です。サーバー側の単一端末ガードのため、このファイルは可搬性がありません: 別マシーンへコピーすると次回 refresh で拒否され、一度 refresh が走ると(現在の SDK は自動 refresh しませんがロードマップに乗っています)同じマシーン上の古いバックアップですらサーバーの `latest_jti` が進んでしまうため stale になります。バックアップして復旧する対象ではなく、live state として扱ってください。 |
Comment thread docs/cli/auth.mdx Outdated
Comment on lines +119 to +123
- on OAuth-supporting deployments: ``Anonymous credentials were rejected as single-device. Anonymous accounts only work on one machine. Sign up for an account that supports multiple devices: arkor login --oauth`` (exit `1`).
- on anon-only deployments: ``Anonymous credentials were rejected as single-device. Anonymous accounts only work on one machine. This deployment does not advertise OAuth, so the only recovery is to mint a new anonymous identity (your previous workspace data cannot be recovered): arkor login --anonymous`` (exit `1`).
- `code: "anonymous_account_not_found"` → analogous OAuth-vs-anon-only split, ending in `arkor login --oauth` or `arkor login --anonymous`.

Errors without a known `code` (and any non-`CloudApiError` exceptions) are rethrown out of `main()`. `bin.ts` wraps the top-level `await main(...)` in a try/catch that logs `err.stack ?? err.message` to stderr and sets `process.exitCode = 1` — so you'll see something like `CloudApiError: cloud-api 503` followed by a stack frame, not just the upstream `error` body. (The explicit catch is there to avoid the bundled minified frame Node's default unhandled-rejection handler would surface, and to keep the stderr flush deterministic across the supported Node range.)
Comment thread docs/ja/cli/auth.mdx Outdated
Comment on lines +118 to +124
- 期限切れまたは無効化されたトークンはクラウド API からの非 2xx として現れます。`arkor whoami` は汎用的な「Token may be expired」を出さなくなり、代わりに上流の `code`(あれば)を持った `CloudApiError` を投げ、`cli/main.ts` のトップレベルハンドラが既知 2 種の認証状態 `code` を実行可能なガイダンスへ整形します。整形前に `main()` は `/v1/auth/cli/config` を best-effort で取得し、リカバリーヒントをデプロイ形態に揃えます。
- `code: "anonymous_token_single_device"`:
- OAuth サポートのデプロイ: ``Anonymous credentials were rejected as single-device. Anonymous accounts only work on one machine. Sign up for an account that supports multiple devices: arkor login --oauth``(終了コード `1`)。
- 匿名専用デプロイ: ``Anonymous credentials were rejected as single-device. Anonymous accounts only work on one machine. This deployment does not advertise OAuth, so the only recovery is to mint a new anonymous identity (your previous workspace data cannot be recovered): arkor login --anonymous``(終了コード `1`)。
- `code: "anonymous_account_not_found"` も同様に分岐し、末尾は `arkor login --oauth` または `arkor login --anonymous` のいずれか。

既知の `code` を持たないエラー(および `CloudApiError` 以外の例外)は `main()` から再 throw されます。`bin.ts` のトップレベル `await main(...)` は try/catch で囲まれており、`console.error(err.stack ?? err.message)` で stderr に出力した上で `process.exitCode = 1` をセットします。つまり `CloudApiError: cloud-api 503` の後にスタックフレームが見え、上流の `error` 本文だけが見えるわけではありません。明示的に catch しているのは、Node のデフォルトの unhandled rejection ハンドラがバンドル後の minified なコードフレームを出すのを避け、サポート対象の Node バージョン全体で stderr の flush を確定させるためです。
- whoami.ts: target the credentials' own `arkorCloudApiUrl` for `/v1/me`
  instead of `defaultArkorCloudApiUrl()`. Without this a stale
  `ARKOR_CLOUD_API_URL` (or a token issued against a non-default
  endpoint) would route the call to the wrong cloud-api, and the
  dead-end formatter in `cli/main.ts` could surface single-device
  guidance for a token that's still valid on its real deployment. Auth0
  creds don't pin a URL and fall through to the env default.
  (Copilot on PR #100)
- whoami.ts: emit `ANON_SINGLE_DEVICE_NOTE` on stderr, not stdout.
  `arkor whoami` writes the user JSON + `Orgs:` line to stdout; mixing
  human-oriented prose into that stream broke wrappers grepping/jq-ing
  the output. stdout now stays a stable, machine-parseable shape.
  (Copilot on PR #100)
- whoami.test.ts: split the existing assertion onto stdout/stderr to
  match the new contract, and add a case that pins the resolved baseUrl
  to the credentials' value over the env-derived default.
- docs/cli/dev.mdx + ja: broaden the "rejected on the next refresh"
  wording. The `latest_jti` mismatch fires from `userAuth` on every
  authenticated request, not just refresh, so the original phrasing
  understated when failures land. The current SDK still doesn't rotate
  the jti on its own (auto-refresh is on the roadmap) so a copied file
  often appears to work for a while; the new text spells that out
  without implying refresh is the only trigger.
- docs/cli/auth.mdx + ja: replace the stale "Other 4xx/5xx exit `0`
  with `Token may be expired`" exit-code line with the new contract
  (rethrow → `bin.ts` stack-trace → exit `1`), and split out the
  anonymous-auth dead-end exit `1` row so the section no longer
  contradicts the Token-expiry / Common-errors paragraphs below.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/arkor/src/cli/main.ts Outdated
Comment on lines +205 to +210
// Probe deployment OAuth status only on the dead-end path so we
// don't add a network round-trip to every successful command.
// Failure collapses to "no OAuth", which steers the formatter at
// the universally-available `arkor login --anonymous` recovery.
const oauthAvailable = await probeOauthAvailability();
const friendly = formatAnonymousAuthError(err, { oauthAvailable });
Comment on lines +95 to +99
// The note goes to stderr so wrapper scripts piping `arkor whoami`
// through `jq` (or grepping the JSON / `Orgs:` line on stdout)
// aren't broken by human-oriented prose appearing in their data
// stream. stdout stays a stable, machine-parseable shape.
process.stderr.write(`\n${ANON_SINGLE_DEVICE_NOTE}\n`);
Comment on lines +60 to +65
// Throw a `CloudApiError` carrying the structured `code` so the
// top-level handler in `cli/main.ts` can format anonymous auth-state
// failures (`anonymous_token_single_device`,
// `anonymous_account_not_found`) into actionable guidance instead of
// a generic "Token may be expired" line.
throw await buildCloudApiError(res);
…rror

- main.ts + auth0.ts: bound the OAuth-availability probe with
  `AbortSignal.timeout(3000)`. The probe runs *after* a command has
  already failed, so a degraded `/v1/auth/cli/config` would otherwise
  leave the user staring at nothing while the recovery hint waits on a
  hung HTTP call. `fetchCliConfig` now takes an `{ fetch, signal }`
  options object; the previous positional `fetchImpl` shape (still used
  by the older auth0.test.ts cases) was migrated.
- whoami.ts: gate `ANON_SINGLE_DEVICE_NOTE` on `process.stdout.isTTY &&
  process.stderr.isTTY`. Pipelines (`arkor whoami | jq`) drop
  `stdout.isTTY`; CI runners that treat stderr-on-success as a warning
  marker drop `stderr.isTTY`. Both groups now see clean output. The
  note still goes to stderr in the interactive case so stdout's
  machine-parseable JSON / `Orgs:` shape is preserved.
- whoami.test.ts: split the existing assertion into TTY / non-TTY
  cases — the first forces both streams to TTY before invoking
  runWhoami, the second pins them to false to cover CI/script use.
- bin.ts: render `CloudApiError` as just `err.message`; keep
  `err.stack ?? err.message` for unknown `Error`s. Routine HTTP
  failures (expired OAuth session, transient 5xx, unmapped 4xx) no
  longer dump a full stack frame, so wrappers and humans see one-line
  output that matches the upstream cloud-api message; genuine SDK bugs
  still surface a stack so they're filable.
- docs/cli/auth.mdx + ja: update the exit-codes paragraph, the
  Token-expiry write-up, and the Common-errors row to describe the
  new bin.ts rendering rule (message-only for CloudApiError) and the
  TTY gate on whoami's anonymous note.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (1)

packages/arkor/src/cli/commands/whoami.ts:40

  • This new direct createClient() call reintroduces the cloud-api client's known onDeprecation bug: passing recordDeprecation directly causes the client to log [@arkor/cloud-api-client] onDeprecation handler threw; ignoring: on deprecated responses because it treats the callback's void return as thenable-checkable. studio/server.ts already wraps recordDeprecation and returns null to avoid that noise; whoami should use the same workaround or deprecated /v1/me responses will start emitting an unexpected stderr line.
  const rpc = createClient({
    baseUrl,
    token: () =>
      creds.mode === "anon" ? creds.token : creds.accessToken,
    clientVersion: SDK_VERSION,
    onDeprecation: recordDeprecation,

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.ja.md Outdated
`arkor dev` は **Studio** と呼ばれるローカル Web UI を `http://localhost:4000` で開きます。初回起動時に使い捨ての匿名ワークスペースをプロビジョニングするので、すぐに実際のトレーニング実行を開始できます。

後からアカウントに紐付けたい場合は `arkor login --oauth` を実行してください
匿名ワークスペースは `arkor dev` を最初に実行したマシーン専用です。OAuth に切り替えても既存の匿名ワークスペースは引き継げません。`arkor login --oauth` は `~/.arkor/credentials.json` を新しい OAuth identity で上書きし、それ以降の作業は OAuth アカウントに紐付きますが、既存の匿名ジョブや org は発行元の credentials ファイルからしか辿れません。複数端末で作業したくなった時点で `arkor login --oauth` を実行し、アカウント付きの新しいワークスペースで再スタートしてください
Comment thread docs/cli/auth.mdx Outdated

Anonymous credentials let you try Arkor without an account: training runs, jobs, and any work you do are tied to the local machine via the anonymous token. The anonymous path always mints a brand-new token (and a new `anonymousId`) and overwrites `~/.arkor/credentials.json`, so re-running `arkor login --anonymous` does not refresh the existing identity. Switching to OAuth (`arkor login --oauth`, or selecting `OAuth (browser)` in the picker) overwrites the credentials file the same way and does not migrate prior anonymous workspaces or jobs into the account. Merging anonymous work into an OAuth account once you sign in is on the roadmap; until that lands, run `arkor login --oauth` **before** you start the runs you want associated with the account.

Anonymous accounts are intentionally **single-device**: the cloud-api binds the issued token to the machine that received it (via a `latest_jti` rotation each time the SDK refreshes the token), so copying `~/.arkor/credentials.json` to a second machine and using it from both will trip the server's single-device guard. The losing client receives an HTTP 401 / 409 with `code: "anonymous_token_single_device"`, which `cli/main.ts` surfaces as actionable guidance; deletion of the underlying anonymous row surfaces as `code: "anonymous_account_not_found"` the same way. The recovery hint is deployment-aware: on OAuth-supporting deployments the CLI points at `arkor login --oauth` so you can sign up for an account that supports multiple devices, while on anon-only deployments (where OAuth is not configured) it points at `arkor login --anonymous` instead — `--oauth` would fail there, and minting a fresh anonymous identity is the only recovery available. Note that **neither path migrates existing anonymous work** into the new identity; the previous workspace stays reachable only from the credentials file that issued it.
Comment thread docs/ja/cli/auth.mdx Outdated

匿名認証は、アカウントなしで Arkor を試すためのものです。学習、ジョブ、その他の作業は匿名トークンを介してローカルマシーンに紐づきます。あとで OAuth に切り替える(`arkor login --oauth`、またはピッカーから `OAuth (browser)` を選ぶ)と認証情報ファイルは差し替えられますが、作業は移行されません。匿名で学習したものを残したいなら、学習を始める前に `arkor login --oauth` を走らせてください。

匿名アカウントは設計上 **単一端末専用** です。クラウド API は発行したマシーンに対してトークンを `latest_jti` ローテーションで束ねるので、`~/.arkor/credentials.json` を別マシーンにコピーして両方から使うとサーバー側の単一端末ガードに引っかかります。負けた側のクライアントは HTTP 401 / 409 を `code: "anonymous_token_single_device"` で受け取り、`cli/main.ts` がそれを実行可能なガイダンスとして整形します。匿名行そのものが削除された場合は `code: "anonymous_account_not_found"` も同じ仕組みで surface されます。リカバリー提案は **デプロイ形態に応じて分岐** します。OAuth がサポートされているデプロイでは `arkor login --oauth` を案内して複数端末対応のアカウントへサインアップする経路を示し、匿名専用デプロイ(OAuth 未設定)では代わりに `arkor login --anonymous` を案内します。後者で `--oauth` を出すとそのコマンドはそのまま失敗するので、新規匿名 identity の発行が唯一のリカバリ手段になるためです。**いずれのパスも、過去の匿名作業は移行されません**。前のワークスペースには発行元の credentials ファイルからしか到達できません。
Comment thread packages/cli-internal/src/templates.ts Outdated
Comment on lines 140 to 147
Anonymous tokens are tied to this machine. Copying
\`~/.arkor/credentials.json\` to another device will be rejected as a
single-device policy violation. When you're ready for an account-backed
workspace that follows you across devices, run:

\`\`\`
npx arkor login
npx arkor login --oauth
\`\`\`
Comment thread packages/arkor/README.md Outdated
Comment on lines +102 to +109
issues a throwaway token tied to a brand-new personal org. **It only
works on the machine where it was issued.** Copying
`~/.arkor/credentials.json` to a second machine and using it from both
will trip the server's single-device guard, and one of the two will be
locked out with `anonymous_token_single_device`. On OAuth-supporting
deployments the CLI directs the user at `arkor login --oauth` to start
a real account; on anon-only deployments it points at `arkor login
--anonymous` instead, since `--oauth` would fail there. Either path is
Comment thread README.md Outdated
`arkor dev` opens **Studio**, a local web UI at `http://localhost:4000`. On first launch it provisions a throwaway anonymous workspace so you can fire off a real training run right away.

Run `arkor login --oauth` later if you want to claim your work under an account.
Anonymous workspaces are tied to this machine. They only work where you ran `arkor dev` first, and switching to OAuth does not migrate them: `arkor login --oauth` overwrites `~/.arkor/credentials.json` with a fresh OAuth identity that any *future* work will be associated with, but existing anonymous jobs and orgs stay reachable only from the credentials file that issued them. Run `arkor login --oauth` whenever you're ready to start an account-backed workspace that follows you across devices.
…docs

The auth/dev docs already explain that single-device enforcement comes
from a `latest_jti` mismatch on every authenticated request, but the
summary copy in README.md, README.ja.md, packages/arkor/README.md,
packages/cli-internal/src/templates.ts, and the one-paragraph blurb in
docs/cli/auth.mdx (+ JA mirror) all overstated the timing — saying a
copied credentials.json "will be rejected" immediately or "only works
on the machine where it was issued". The current SDK doesn't
auto-refresh anonymous tokens, so client-side rotation never happens
today; a copy on a second machine often keeps working until either
the issuing user runs `arkor login --anonymous` again (overwriting
the stored jti) or auto-refresh ships and rotates server-side. Once
that happens, every other copy fails with `anonymous_token_single_device`
on its next call.

Updated wording across these surfaces to:
1. Describe the mechanism as "userAuth checks `latest_jti` on every
   request" (matches the deployed reality), not "rotated each refresh"
   (which contradicts the same-page note that auto-refresh is not
   wired).
2. Surface the two timing regimes — "today: copies tend to keep
   working until a manual relogin or admin rotation", "after
   auto-refresh ships: any rotation invalidates other copies on the
   next call" — so users aren't surprised when a copied file appears
   to work for a while.
3. Soften "only works on this machine" / "will be rejected" to
   "designed for one machine" / "isn't a supported workflow" while
   keeping the policy clear.

The user-facing CLI message string `ANON_SINGLE_DEVICE_NOTE`
("Note: anonymous accounts work on this machine only.") is left
untouched: it's a short policy hint emitted at issuance, not a claim
about timing.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b64c560113

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/arkor/src/cli/main.ts Outdated
Comment on lines +64 to +65
} catch {
return false;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Distinguish probe failure from anon-only deployments

probeOauthAvailability() maps every /v1/auth/cli/config failure to false, and formatAnonymousAuthError treats false as “OAuth is not advertised.” In the common case where the config probe times out or transiently fails on an OAuth-enabled deployment, users are shown a definitive anon-only diagnosis and steered to arkor login --anonymous, which can hide the correct recovery path (--oauth) for that deployment. Return an “unknown” state (or separate error flag) so transient probe failures don't produce a false deployment claim.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/cli/auth.mdx Outdated

Both anonymous paths surface the new `anonymousId` and an explanation that the same id is how Arkor Cloud recognises this client across sessions, though the line shape differs by entry point. `arkor login` (`--anonymous` flag or picker → **Anonymous**) prints `Anonymous id: <id>` as the spinner stop and then a separate info line saying that keeping the credentials file (`credentialsPath()`, typically `~/.arkor/credentials.json` on Linux and macOS) is what preserves the identity. `arkor dev`'s auto-bootstrap skips the spinner and emits a single info line that already embeds the id and the same explanation. The picker → **Anonymous** path additionally surfaces a one-line warn alongside the success message — ``Anonymous sessions aren't guaranteed to persist — sign in with `arkor login --oauth` to tie future work to your Arkor Cloud account.`` — so the upgrade hint is visible at issuance time. The explicit `--anonymous` shortcut suppresses that warn because it skips the `/v1/auth/cli/config` fetch and so cannot tell whether `arkor login --oauth` would succeed on this deployment; pointing at it on a rare anon-only deployment would steer users at a command that fails.

Every anonymous issuance path also surfaces the single-device limitation as a separate info line. The exact wording is gated on `oauthAvailable` (same contract as the persistence nudge). On OAuth-supporting deployments callers see the upgrade-flavoured ``Note: anonymous accounts work on this machine only. Run `arkor login --oauth` to sign up for multi-device access.``, while on anon-only deployments (and on `arkor login --anonymous` where the cfg fetch is skipped intentionally) callers see only the bare ``Note: anonymous accounts work on this machine only.``, so users on those deployments aren't pointed at a command that would fail. `arkor whoami` on an anonymous identity also emits the bare variant on stderr — and only when both stdout and stderr are TTYs, so a wrapper redirecting `arkor whoami | jq` (loses `stdout.isTTY`) or a CI runner that treats stderr-on-success as a warning marker (loses `stderr.isTTY`) sees clean output. The note is purely a UX hint; it doesn't gate any behaviour, and `whoami`'s machine-readable JSON shape on stdout never changes.
Comment thread docs/ja/cli/auth.mdx Outdated

どちらの匿名パスでも、新しい `anonymousId` と「同じ id がセッションをまたいで Arkor Cloud がこのクライアントを認識するための識別子である」旨の説明を必ず surface します。ただし出力の形は入口によって異なります。`arkor login`(`--anonymous` フラグまたはピッカー → **Anonymous**)はスピナーの停止行として `Anonymous id: <id>` を出し、続けて「認証情報ファイル(`credentialsPath()`、Linux と macOS では通常 `~/.arkor/credentials.json`)を保持していれば同じ identity を維持できる」旨の info 行を別行で出します。`arkor dev` の自動ブートストラップはスピナーを使わず、id と同じ説明を 1 本の info 行にまとめて出します。ピッカー → **Anonymous** のパスでは、さらに成功メッセージと並んで 1 行の warn(``Anonymous sessions aren't guaranteed to persist — sign in with `arkor login --oauth` to tie future work to your Arkor Cloud account.``、和訳: 匿名セッションは永続性が保証されないので、今後の作業を Arkor Cloud アカウントに紐付けたいなら `arkor login --oauth` でサインインしてください)が出るので、発行時点でアップグレードのヒントが見えます。明示的な `--anonymous` ショートカットでは `/v1/auth/cli/config` の取得をスキップしているため `arkor login --oauth` がそのデプロイで成功するかわからず、稀に存在する匿名専用デプロイで失敗するコマンドへユーザーを誘導しないよう、warn は意図的に抑制されます。

匿名発行のあらゆる入口で、単一端末の制約も別行の info として surface します。実際の文言は永続性ナッジと同じ `oauthAvailable` のゲーティング契約に従って分岐します。OAuth サポートのデプロイでは ``Note: anonymous accounts work on this machine only. Run `arkor login --oauth` to sign up for multi-device access.``(和訳: 注意: 匿名アカウントはこのマシーンでのみ動作します。複数端末で使うには `arkor login --oauth` でサインアップしてください)が出ますが、匿名専用デプロイ、および `/v1/auth/cli/config` の取得を意図的にスキップする `arkor login --anonymous` の経路では、bare な ``Note: anonymous accounts work on this machine only.`` だけが出ます。失敗確実なコマンドへユーザーを誘導しないためです。`arkor whoami` も匿名 identity に対しては bare 版のみを stderr に出します。さらに stdout / stderr の両方が TTY のときに限るので、`arkor whoami | jq`(stdout が TTY でなくなる)や、stderr 出力をすべて警告扱いする CI ランナー(stderr が TTY でなくなる)では note は出ません。あくまで UX のヒントであり、どの挙動もゲートしません。stdout 側の機械可読 JSON は常に同じ形です。
Codex P2 (PR #100): probeOauthAvailability collapsed every probe
failure to false, and formatAnonymousAuthError treated false as
"OAuth is not advertised". On a transient probe failure (timeout,
network blip, malformed cfg) against an OAuth-supporting deployment,
the dead-end formatter would confidently steer users at
arkor login --anonymous and hide the real recovery (--oauth).

- main.ts: probe now returns the tri-state OauthAvailability
  ("available" | "absent" | "unknown"), and the call site translates
  to oauthAvailable: true | false | undefined for the formatter so
  "absent" (cfg fetched, no Auth0) and "unknown" (cfg fetch failed)
  no longer collide.
- anonymous-auth-error.ts: split the oauthAvailable === false branch
  from the new oauthAvailable === undefined branch. The latter hedges
  with both commands and an explicit "Couldn't reach the deployment
  to confirm whether OAuth is offered" header so users aren't told
  the deployment is anon-only when we couldn't actually verify. The
  OAuth-confirmed and absent branches keep their previous output.
- main.test.ts + anonymous-auth-error.test.ts: rename the previous
  "treats undefined as anon-only" cases to "hedges with both
  commands when probe is inconclusive", add the same coverage for
  anonymous_account_not_found, and assert the new wording does NOT
  claim the deployment is anon-only.

Copilot (PR #100, EN + JA): docs/cli/auth.mdx claimed arkor whoami's
stdout stays "machine-readable JSON" and used arkor whoami | jq as
the motivating example. The actual stdout is JSON.stringify(user, …)
followed by an optional human-readable Orgs: <slug>, … line, which
isn't a valid JSON stream end-to-end.

- docs/cli/auth.mdx + ja: drop the jq example, describe the real
  shape (JSON head + optional Orgs tail), and warn against piping
  the full output through a strict JSON parser.
- whoami.ts: the inline TTY-gate comment was inheriting the same
  framing. Updated it to match the doc and to spell out that stdout
  isn't a strict-JSON stream.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +123 to +128
export function isAnonymousAuthDeadEnd(err: unknown): err is CloudApiError {
return (
err instanceof CloudApiError &&
(err.code === ANONYMOUS_TOKEN_SINGLE_DEVICE ||
err.code === ANONYMOUS_ACCOUNT_NOT_FOUND)
);
Comment on lines +67 to +74
if (err.code === ANONYMOUS_TOKEN_SINGLE_DEVICE) {
if (ctx.oauthAvailable === true) {
return [
"Anonymous credentials were rejected as single-device.",
"Anonymous accounts only work on one machine. Sign up for an account that supports multiple devices:",
"",
" arkor login --oauth",
].join("\n");
Comment on lines +91 to +98
if (err.code === ANONYMOUS_ACCOUNT_NOT_FOUND) {
if (ctx.oauthAvailable === true) {
return [
"Your anonymous credentials are no longer valid.",
"Sign up to continue:",
"",
" arkor login --oauth",
].join("\n");
Comment on lines +55 to +65
// The unknown-state branch surfaces both commands so users on an
// OAuth-supporting deployment aren't denied the correct recovery
// just because the config endpoint timed out. The order points at
// `--oauth` first because it covers the majority of deployments;
// anon-only users will get a clean "OAuth is not configured" error
// and can fall through to the second command.
const unknownTail = [
"Couldn't reach the deployment to confirm whether OAuth is offered. Try the OAuth path first; if it fails with `OAuth is not configured`, fall through to the anonymous path:",
"",
" arkor login --oauth",
" arkor login --anonymous",
…ints

Copilot (PR #100): two findings about the anonymous-auth-error
helpers.

1. `isAnonymousAuthDeadEnd` was exported but no retry loop consumed
   it. `createTrainer().wait()` happily reconnected on every failure,
   including dead-end auth errors where retrying never recovers
   (`anonymous_token_single_device`, `anonymous_account_not_found`).
   The reconnect budget would burn through and bury the actionable
   recovery hint that `cli/main.ts` formats from the same error.

   - trainer.ts: `handleFailure` now checks `isAnonymousAuthDeadEnd`
     up front and rethrows immediately on a hit. The error bubbles
     unchanged to `cli/main.ts`'s top-level handler.
   - trainer.test.ts: new "fails fast on anonymous-auth dead-end
     errors without retrying" case asserts exactly one stream-open
     attempt and that the original CloudApiError is what surfaces.

2. The formatter recommended `arkor login --oauth` even in CI, but
   `runLogin()` rejects `--oauth` outright when `process.env.CI` is
   set (PKCE needs a browser callback CI runners can't satisfy). A
   dead-end in a CI job on an OAuth-supporting deployment was being
   pointed at a guaranteed failure.

   - anonymous-auth-error.ts: ctx now carries `inCi?: boolean`,
     defaulting to `Boolean(process.env.CI)` for callers (cli/main.ts)
     that don't plumb it. In CI:
     * the OAuth-confirmed branch points at `arkor login --anonymous`
       and tells the user where `--oauth` would actually run (a
       developer machine);
     * the unknown-state hedge drops the OAuth half entirely, since
       suggesting it would just send the runner at a dead command.
     The OAuth-absent branch is unchanged (already `--anonymous`).
   - anonymous-auth-error.test.ts: beforeEach scrubs CI to keep
     existing assertions deterministic; new cases cover the
     OAuth-confirmed and unknown-state CI branches plus the env
     auto-read.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.ja.md Outdated
`arkor dev` は **Studio** と呼ばれるローカル Web UI を `http://localhost:4000` で開きます。初回起動時に使い捨ての匿名ワークスペースをプロビジョニングするので、すぐに実際のトレーニング実行を開始できます。

後からアカウントに紐付けたい場合は `arkor login --oauth` を実行してください
匿名ワークスペースはこのマシーンに紐づきます。クラウド API はサーバー側で単一端末ガードを強制しているので、`~/.arkor/credentials.json` を別端末にコピーする運用は想定外です。各匿名ワークスペースは発行されたマシーンで使う前提です。今のところ CLI は匿名トークンを自動 refresh しないので、コピーがしばらく動いてしまうことはあります。発行ユーザーがもう一度 `arkor login --anonymous` を実行する(または自動 refresh が入って保存中の jti が進む)と、そのタイミングで他の全コピーは次回呼び出しから単一端末エラーで失敗するようになります。OAuth に切り替えても既存の匿名ワークスペースは引き継げません。`arkor login --oauth` は `~/.arkor/credentials.json` を新しい OAuth identity で上書きし、それ以降の作業は OAuth アカウントに紐付きますが、既存の匿名ジョブや org は発行元の credentials ファイルからしか辿れません。複数端末で作業したくなった時点で `arkor login --oauth` を実行し、アカウント付きの新しいワークスペースで再スタートしてください
Comment on lines +111 to +113
"Anonymous accounts only work on one machine. This deployment does not advertise OAuth, so the only recovery is to mint a new anonymous identity (your previous workspace data cannot be recovered):",
"",
" arkor login --anonymous",
Comment on lines +143 to +145
"Mint a new anonymous identity to continue (your previous workspace data cannot be recovered):",
"",
" arkor login --anonymous",
Comment thread packages/cli-internal/src/templates.ts Outdated
Comment on lines +155 to +156
be migrated, so future work created after sign-in is what ends up under
the account.
Comment thread README.md Outdated
`arkor dev` opens **Studio**, a local web UI at `http://localhost:4000`. On first launch it provisions a throwaway anonymous workspace so you can fire off a real training run right away.

Run `arkor login --oauth` later if you want to claim your work under an account.
Anonymous workspaces are tied to this machine. The cloud-api enforces a single-device guard server-side, so `~/.arkor/credentials.json` shouldn't be copied to a second machine — each anonymous workspace is meant to live where it was issued. Today the CLI doesn't auto-refresh the anonymous token, so a copy may keep working for a while and only start failing once the issuing user runs `arkor login --anonymous` again (or auto-refresh ships and rotates the stored jti); when that happens, every other copy starts failing on its next call with a single-device error. Switching to OAuth does not migrate prior anonymous work: `arkor login --oauth` overwrites `~/.arkor/credentials.json` with a fresh OAuth identity that any *future* work will be associated with, but existing anonymous jobs and orgs stay reachable only from the credentials file that issued them. Run `arkor login --oauth` whenever you're ready to start an account-backed workspace that follows you across devices.
Copilot (PR #100, 5 hits): re-issuing credentials alone is not enough
to resume work in an existing project directory. `ensureProjectState()`
in `packages/arkor/src/core/projectState.ts` reads `.arkor/state.json`
and reuses it unchanged when present, so after either path of recovery
(`arkor login --anonymous` re-mint or `arkor login --oauth` switch)
commands in the same directory keep targeting the previous identity's
`(orgSlug, projectSlug)`. Without the state-reset step the recovery
appears not to take effect: the user gets repeat 401s, or worse,
silently writes into a workspace they cannot reach.

- anonymous-auth-error.ts: introduce a STATE_RESET_NOTE constant and
  append it to every recovery branch (single-device + account-not-found,
  for `oauthAvailable` true / false / undefined / inCi). The note tells
  users to delete `.arkor/state.json` before resuming, or to run
  `arkor init` for an OAuth account.
- anonymous-auth-error.test.ts: add `expect(out!).toMatch(/.arkor/state.json/)`
  assertions on the OAuth-confirmed and OAuth-absent branches so a
  future change cannot silently strip the note.
- README.md: spell out that switching to OAuth does not auto-route
  subsequent runs in an existing project directory; users must delete
  `.arkor/state.json` (or run `arkor init` for the new account) before
  the new identity takes effect. Also drops the leftover em-dash on
  this paragraph.
- README.ja.md: JP mirror of the same correction.
- packages/arkor/README.md: add a separate paragraph describing the
  state-reset step so the package README is consistent with the root
  README. Replaces the em-dash on "Either path is a *new* identity"
  with a period.
- packages/cli-internal/src/templates.ts: same fix in the scaffolded
  README, including a copy-pasteable `rm -rf .arkor/state.json && npx
  arkor init` recovery hint.
@k-taro56 k-taro56 requested a review from Copilot May 5, 2026 11:50
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4ecce1e684

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +64 to +66
creds?.mode === "anon" && creds.arkorCloudApiUrl
? creds.arkorCloudApiUrl
: defaultArkorCloudApiUrl();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Probe OAuth on the endpoint that raised the auth error

probeOauthAvailability() now always prefers creds.arkorCloudApiUrl, but several CLI flows (e.g. arkor start via createTrainer) still call cloud APIs against defaultArkorCloudApiUrl() at runtime, so the dead-end error can come from a different deployment than the one being probed. In that case the formatter can recommend the wrong recovery command (--oauth vs --anonymous) for the endpoint that actually failed. Fresh evidence: createTrainer continues to bind baseUrl from defaultArkorCloudApiUrl() (not credential URL), so this mismatch is still reachable when ARKOR_CLOUD_API_URL differs from stored anonymous credentials.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md
`arkor dev` opens **Studio**, a local web UI at `http://localhost:4000`. On first launch it provisions a throwaway anonymous workspace so you can fire off a real training run right away.

Run `arkor login --oauth` later if you want to claim your work under an account.
Anonymous workspaces are tied to this machine. The cloud-api enforces a single-device guard server-side, so `~/.arkor/credentials.json` shouldn't be copied to a second machine. Each anonymous workspace is meant to live where it was issued. Today the CLI doesn't auto-refresh the anonymous token, so a copy may keep working for a while and only start failing once the issuing user runs `arkor login --anonymous` again (or auto-refresh ships and rotates the stored jti); when that happens, every other copy starts failing on its next call with a single-device error. Switching to OAuth does not migrate prior anonymous work: `arkor login --oauth` overwrites `~/.arkor/credentials.json` with a fresh OAuth identity, but existing anonymous jobs and orgs stay reachable only from the credentials file that issued them. **The CLI also won't automatically route runs in an existing project directory to the new identity**: `.arkor/state.json` pins the directory to the old `(orgSlug, projectSlug)`, so commands here will keep targeting the previous workspace until you delete that file (then re-run, or run `arkor init` for the OAuth account). Run `arkor login --oauth` whenever you're ready to start an account-backed workspace that follows you across devices.
Comment thread README.ja.md
`arkor dev` は **Studio** と呼ばれるローカル Web UI を `http://localhost:4000` で開きます。初回起動時に使い捨ての匿名ワークスペースをプロビジョニングするので、すぐに実際のトレーニング実行を開始できます。

後からアカウントに紐付けたい場合は `arkor login --oauth` を実行してください
匿名ワークスペースはこのマシーンに紐づきます。クラウド API はサーバー側で単一端末ガードを強制しているので、`~/.arkor/credentials.json` を別端末にコピーする運用は想定外です。各匿名ワークスペースは発行されたマシーンで使う前提です。今のところ CLI は匿名トークンを自動 refresh しないので、コピーがしばらく動いてしまうことはあります。発行ユーザーがもう一度 `arkor login --anonymous` を実行する(または自動 refresh が入って保存中の jti が進む)と、そのタイミングで他の全コピーは次回呼び出しから単一端末エラーで失敗するようになります。OAuth に切り替えても既存の匿名ワークスペースは引き継げません。`arkor login --oauth` は `~/.arkor/credentials.json` を新しい OAuth identity で上書きしますが、既存の匿名ジョブや org は発行元の credentials ファイルからしか辿れません。**さらに、既存のプロジェクトディレクトリでの実行は自動的に新しい identity 側へ切り替わりません**: `.arkor/state.json` が古い `(orgSlug, projectSlug)` をピンしているため、そのファイルを削除する(その後再実行するか、OAuth アカウントなら `arkor init` を走らせる)まで、このディレクトリのコマンドは前のワークスペースを参照し続けます。複数端末で作業したくなった時点で `arkor login --oauth` を実行し、アカウント付きの新しいワークスペースで再スタートしてください
Comment thread docs/cli/auth.mdx

Anonymous credentials let you try Arkor without an account: training runs, jobs, and any work you do are tied to the local machine via the anonymous token. The anonymous path always mints a brand-new token (and a new `anonymousId`) and overwrites `~/.arkor/credentials.json`, so re-running `arkor login --anonymous` does not refresh the existing identity. Switching to OAuth (`arkor login --oauth`, or selecting `OAuth (browser)` in the picker) overwrites the credentials file the same way and does not migrate prior anonymous workspaces or jobs into the account. Merging anonymous work into an OAuth account once you sign in is on the roadmap; until that lands, run `arkor login --oauth` **before** you start the runs you want associated with the account.

Anonymous accounts are intentionally **single-device**: the cloud-api stores a `latest_jti` per anonymous user and `userAuth` rejects every authenticated request whose JWT carries a different jti. The jti only changes when the token rotates, so the practical timing has two regimes. **Today** the CLI does not auto-refresh anonymous tokens (that wiring lives in `@arkor/cloud-api-client`'s `getToken()` and is on the [SDK roadmap](/roadmap)), so no rotation ever happens client-side and a copy of `~/.arkor/credentials.json` on a second machine usually keeps working — until the issuing user explicitly mints a new identity (`arkor login --anonymous` overwrites the file with a fresh jti) or an admin rotates server-side, at which point every other copy starts failing on its next call. **Once auto-refresh ships**, any refresh rotates `latest_jti` immediately, so the older-jti copy on any other device (or an older backup of the same file on this machine) starts failing on its next call without warning. Either way, the losing client receives an HTTP 401 / 409 with `code: "anonymous_token_single_device"`, which `cli/main.ts` surfaces as actionable guidance; deletion of the underlying anonymous row surfaces as `code: "anonymous_account_not_found"` the same way. The recovery hint is deployment-aware: on OAuth-supporting deployments the CLI points at `arkor login --oauth` so you can sign up for an account that supports multiple devices, while on anon-only deployments (where OAuth is not configured) it points at `arkor login --anonymous` instead — `--oauth` would fail there, and minting a fresh anonymous identity is the only recovery available. Note that **neither path migrates existing anonymous work** into the new identity; the previous workspace stays reachable only from the credentials file that issued it.
Comment thread docs/ja/cli/auth.mdx

匿名認証は、アカウントなしで Arkor を試すためのものです。学習、ジョブ、その他の作業は匿名トークンを介してローカルマシーンに紐づきます。あとで OAuth に切り替える(`arkor login --oauth`、またはピッカーから `OAuth (browser)` を選ぶ)と認証情報ファイルは差し替えられますが、作業は移行されません。匿名で学習したものを残したいなら、学習を始める前に `arkor login --oauth` を走らせてください。

匿名アカウントは設計上 **単一端末専用** です。クラウド API は匿名ユーザーごとに `latest_jti` を保持しており、`userAuth` は受信した JWT の jti が一致しないリクエストをすべて拒否します。jti はトークンのローテーションでしか変わらないので、実際の発火タイミングは大きく 2 つに分かれます。**今のところ** CLI は匿名トークンの自動 refresh を実装していません(`@arkor/cloud-api-client` の `getToken()` 側で配線予定で、[SDK ロードマップ](/ja/roadmap) に乗っています)。そのためクライアント側からのローテーションは起こらず、別マシーンにコピーした `~/.arkor/credentials.json` も大抵しばらく動いてしまいます。失効するのは、発行ユーザーが明示的に新 identity を発行したとき(`arkor login --anonymous` が新しい jti でファイルを上書き)か、管理者がサーバー側でローテーションしたとき。そのタイミングで他のコピーは次の呼び出しから失敗します。**自動 refresh が入ると**、refresh のたびに `latest_jti` が即座に進むので、別端末(または同じマシーン上の古いバックアップ)に残った旧 jti のコピーは予告なく次の呼び出しで失敗するようになります。いずれにせよ、負けた側のクライアントは HTTP 401 / 409 を `code: "anonymous_token_single_device"` で受け取り、`cli/main.ts` がそれを実行可能なガイダンスとして整形します。匿名行そのものが削除された場合は `code: "anonymous_account_not_found"` も同じ仕組みで surface されます。リカバリー提案は **デプロイ形態に応じて分岐** します。OAuth がサポートされているデプロイでは `arkor login --oauth` を案内して複数端末対応のアカウントへサインアップする経路を示し、匿名専用デプロイ(OAuth 未設定)では代わりに `arkor login --anonymous` を案内します。後者で `--oauth` を出すとそのコマンドはそのまま失敗するので、新規匿名 identity の発行が唯一のリカバリ手段になるためです。**いずれのパスも、過去の匿名作業は移行されません**。前のワークスペースには発行元の credentials ファイルからしか到達できません。
Comment thread packages/arkor/README.md
Comment on lines +106 to +113
second machine and using it from both will eventually trip the
server's single-device guard. The exact timing depends on whether the
SDK is currently auto-refreshing the token: today it isn't, so a
copy may keep working for a while; once one of the copies forces a
fresh login (or auto-refresh ships and rotates the stored jti), every
*other* copy starts failing on its next call with
`anonymous_token_single_device`. On OAuth-supporting deployments the
CLI directs the user at `arkor login --oauth` to start a real
Comment on lines +143 to +148
workflow. The issuing machine and the copy share one identity, and
once either side forces a token rotation (today: a manual
\`arkor login --anonymous\`; future: any auto-refresh) the other side
starts failing with a single-device error on its next call. When
you're ready for an account-backed workspace that follows you across
devices, run:
Comment on lines +71 to +75
* the state file (or run `arkor init` for OAuth) is what actually
* closes the loop.
*/
const STATE_RESET_NOTE =
"Local project state pins the working directory to the old workspace. Delete `.arkor/state.json` (or, for an OAuth account, re-run `arkor init`) before resuming work in this directory, otherwise commands here will keep targeting the previous identity's org/project.";
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