Repository navigation
Conversation
…rint the project dashboard link
AI review: 8 P0-P2 finding(s)Adds 🟠 P1
🟡 P2
⚪ P3
Not checked: The AI review read the diff, the code, and the PR's text; it ran no tests or builds and read nothing outside this repository.
Push a fix, or have a collaborator with write access (including the PR author) add the Human review: needed
Decide the points above: approve to accept them, or request changes. Suggested reviewer: |
| if err := os.MkdirAll(dir, dirMode); err != nil { | ||
| return fmt.Errorf("failed to create %s: %w", dir, err) | ||
| } | ||
| starter, err := initcmd.ResolveStarter(opts.template, "") |
There was a problem hiding this comment.
🟡 P2: An unknown --template is caught only after sign-in and project creation, and then only as a warning
initcmd.ResolveStarter doesn't check that the starter exists (in volcano init, the 'unknown starter' error comes later from projectinit). So volcano connect --template react (or any typo) signs in, creates a real cloud project, links it, and writes the env file. Only then does scaffold() fail with unknown starter, which line 473 turns into Warning: Skipped the scaffold. The command exits 0 and --json reports success, so an agent won't notice. The user ends up with an extra project, which counts against the plan's limit, and no starter. Fix: check the starter up front, before ensureSession (for example, export a projectinit check that runs validStarterName and starterExists), and fail on an unknown template.
Found by claude-opus-5-5
| // A working directory that no longer exists can't be linked. | ||
| return cfg, nil //nolint:nilerr // the link is optional; the config itself loaded | ||
| } | ||
| if cfg.Link, err = FindLink(wd); err != nil { |
There was a problem hiding this comment.
⚪ P3: A malformed .volcano/project.json in the directory or any parent breaks every command that loads config, including login and connect
Load now returns an error when FindLink can't read or parse a link file. A link with merge-conflict markers (the docs suggest committing the link to share it), an empty {}, or a directory at that path makes volcano login, volcano connect, and projects list fail with failed to load config: failed to parse project link .... Even connect, which would overwrite the link anyway, can't repair it. In local mode, withLocalConfig also quietly loses the user's function aliases, because it ignores the Load error. The message names the file, so users can recover. Still, it would be better for commands that don't need a project (login, connect, account-scoped commands) to warn and ignore a bad link instead of failing.
Found by claude-opus-5-5
| return config.Update(func(cfg *config.Config) (bool, error) { | ||
| cfg.UserToken = credentials.Token | ||
| cfg.UserID = credentials.UserID | ||
| cfg.SessionAPIURL = credentials.APIURL |
There was a problem hiding this comment.
⚪ P3: api_url is saved even when it equals the compiled default, which pins every new login to today's default
SaveCredentials always writes cfg.SessionAPIURL = credentials.APIURL, including https://api.volcano.dev. Config.APIURL() ranks the saved URL above compiledDefaultAPIURL. So if a later release ships a different compiled default, or someone runs a make local build with its own DEFAULT_API_URL, the new default is silently ignored until the user logs out. The stated goal is only to remember a non-default API. Saving api_url only when it differs from DefaultAPIURL() (or clearing it otherwise) keeps that goal without the pinning.
Found by claude-opus-5-5
| } | ||
|
|
||
| func run(ctx context.Context, opts options) error { | ||
| dir, err := filepath.Abs(opts.dir) |
There was a problem hiding this comment.
⚪ P3: Running connect from the home directory links and scaffolds the whole home directory
Onboarding means pasting npx -y @volcano.dev/cli connect into a terminal, and a fresh terminal often starts in ~. Run there, connect creates a project named after the user's account directory and writes ~/.volcano/project.json (next to the global config), ~/volcano/, ~/.gitignore, and ~/AGENTS.md. FindLink reads the home directory's link before it stops walking, so that link then applies to every non-git directory under home. Consider refusing to connect $HOME (or asking first on a terminal), and telling the user to cd into a project directory.
Found by claude-opus-5-5
| output.Success(opts.out, "Logged in successfully") | ||
| printNonDefaultAPI(opts.out, credentials) | ||
| if credentials.Project != nil { | ||
| output.Success(opts.out, "Now using project: %s (%s)", credentials.Project.Name, credentials.Project.ID) |
There was a problem hiding this comment.
⚪ P3: login --project says 'Now using project' even inside a linked directory, where the link overrides it
SaveCredentials saves the login project as the global current_project, but in a linked directory Config.Project() now prefers the link. So volcano login --token pt-... --project B, run where the link names project A, prints Now using project: B, while later commands there still target A. With a project token, those commands then fail with 403. volcano use was updated to write the link in this case; login wasn't. Either update the link the same way, or mention that the directory's link takes precedence.
Found by claude-opus-5-5
| - `volcano connect` | ||
| - `volcano cloud access-tokens …` | ||
| - `volcano cloud domains list` and `volcano cloud domains verify` | ||
| - `volcano durable approvals …` |
There was a problem hiding this comment.
⚪ P3: The scripting doc claims --json for whole command groups that only partly support it
The list includes volcano projects keys service ... and volcano cloud access-tokens .... But only projects keys service create has --json (service_keys.go), and cloud access-tokens revoke has none. A script that copies these and runs, say, volcano cloud access-tokens revoke <id> --json fails with unknown flag: --json (exit 1). Name the subcommands that actually support it.
Found by claude-opus-5-5
| cfg.UserID = credentials.UserID | ||
| cfg.SessionAPIURL = credentials.APIURL | ||
| if credentials.Project != nil { | ||
| cfg.CurrentProject = credentials.Project |
There was a problem hiding this comment.
🟡 P2: Honor explicit login project selection inside linked directories
In a directory linked to project A, volcano login --token pt-B --project B validates B and reports that B is selected, but this saves only CurrentProject. Config.Project() still prioritizes the link to A, so subsequent cloud commands address A and fail with 403. An account token can instead operate on A despite the explicit selection of B. Make explicit login selection respect the directory link, as volcano use now does.
Found by gpt-6.1-sol
| if err := ensureSession(ctx, opts, sessions); err != nil { | ||
| return err | ||
| } | ||
| authenticated, err := sessions.Authenticated() |
There was a problem hiding this comment.
🟡 P2: Use the supplied token throughout the connect workflow
When VOLCANO_TOKEN contains account A's token and --token supplies account B's token, ensureSession validates and saves B, but this reload uses Config.Token(), which selects A's environment token. Connect then creates its project under A or fails accessing --project from B after reporting successful sign-in. Use the explicit token for the entire operation, or reject conflicting credential inputs before proceeding.
Found by gpt-6.1-sol
| func exitCode(err error) int { | ||
| status := api.Status(err) | ||
| switch { | ||
| case errors.Is(err, config.ErrNotAuthenticated), status == http.StatusUnauthorized: |
There was a problem hiding this comment.
🟡 P2: Preserve error categories from real commands for stable exit codes
auth.LoginWithToken and projectTokenValidationError replace HTTP 401 errors with plain errors.New("invalid token"), so rejected login --token and connect --token calls still exit 1 instead of 2. Similarly, project.ListAnonKeys and selectedProject return plain missing-project errors, making key commands exit 1 instead of 3. Preserve the API errors or wrap the corresponding sentinels so these commands satisfy the new documented contract.
Found by gpt-6.1-sol
| output.Warning(log, "Skipped the scaffold: %s. Run `volcano init` to see the conflicts.", strings.Join(conflicts, "; ")) | ||
| return | ||
| } | ||
| output.Warning(log, "Skipped the scaffold: %v", err) |
There was a problem hiding this comment.
🟡 P2: Reject invalid templates before creating a cloud project
ResolveStarter normalizes names without checking that a starter exists. Consequently, connect --template nextjss creates and links a cloud project, then turns the unknown-starter error into a warning and exits successfully without the requested scaffold. Validate the template before remote mutations and propagate invalid-template errors instead of reporting a successful connection.
Found by gpt-6.1-sol
| // projectAnonKey returns the project's default anon key, the publishable key | ||
| // the SDK and frontends use. It creates one only when the project has none. | ||
| func projectAnonKey(ctx context.Context, client *api.Client, projectID uuid.UUID) (string, error) { | ||
| keys, err := client.ListAnonKeys(ctx, projectID) |
There was a problem hiding this comment.
🟡 P2: Search subsequent anon-key pages before choosing a fallback
api.ListAnonKeys requests only the default page and discards pagination metadata; the vendored API contract defaults to 10 keys. When an existing project's default key is on a later page, this function writes an arbitrary first-page key into cloud.env, potentially giving the application different permissions. Fetch subsequent pages until the default key is found before using a fallback.
Found by gpt-6.1-sol
|
|
||
| // projectDashboardURL is the project's overview page in the dashboard. | ||
| func projectDashboardURL(webURL string, projectID uuid.UUID) string { | ||
| return strings.TrimRight(webURL, "/") + "/dashboard/overview?" + url.Values{"project": []string{projectID.String()}}.Encode() |
There was a problem hiding this comment.
🟡 P2: Coordinate project dashboard links with the dashboard consumer
The PR description states that volcano-web's /dashboard/overview does not yet select projects from ?project=. If the browser last selected A, connecting B prints a supposedly project-specific link that opens A instead. Deploy the matching dashboard support before relying on this URL, or use a route that already honors project selection.
Found by gpt-6.1-sol
| defer server.Close() | ||
| chdirTemp(t) | ||
|
|
||
| out, err := runConnectHere(t, server, "--token", "vbt_one-time") |
There was a problem hiding this comment.
Exposed secret in internal/cmd/connect/connect_test.go - low severity
Detected a secret as command line option
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More Info
|
|
||
| // canOfferSignIn reports whether a person is there to answer: both ends are | ||
| // terminals and nothing marks the run as non-interactive. | ||
| func canOfferSignIn(in io.Reader, out io.Writer) bool { |
There was a problem hiding this comment.
🟡 P2: Sign-in prompt can hang agents that run commands under a pseudo-terminal, and can't be declined
canOfferSignIn only checks that stdin and stderr are TTYs, plus CI and the new, undocumented VOLCANO_NONINTERACTIVE. Agents that run shell commands in a real PTY, such as Copilot agent mode or Cursor in the VS Code integrated terminal, pass that check. Because session.Factory.Authenticated() now calls deps.SignIn, a volcano cloud … command run there without a login stops at Do you already have one? [y/N]: and waits for input. Before this change it failed right away with the not-authenticated error.
The prompt also has no decline option: an empty answer starts browser signup. docs/authentication.md says that a coding agent gets exit code 2 instead, which isn't true for these agents.
Suggested fixes:
- Skip the prompt when known agent markers are set.
internal/setupalready detects some of them. - Make the default answer fail with
ErrNotAuthenticatedinstead of starting signup. - Document
VOLCANO_NONINTERACTIVE.
Found by claude-opus-5-5
| output.Note(opts.log, "%s is taken, so the new project is %s.", name, candidate) | ||
| } | ||
| return project, nil | ||
| case api.Status(err) == http.StatusConflict && suffix <= maxNameSuffix: |
There was a problem hiding this comment.
⚪ P3: Name-suffix retry depends on a 409 that the API spec doesn't list for project create
createProject only tries name-2, name-3, … when POST /projects returns 409. The generated client in this repo (CreateProjectClientResponse) lists only 201, 400, 403 and 503, and the spec says names "must be unique". If the platform answers a duplicate name with 400, connect in a directory whose name matches an existing project fails instead of adding a suffix. The fake API in the tests returns 409, so the tests can't catch this. Confirm the status with volcano-hosting, or also match the 400 duplicate-name refusal.
Found by claude-opus-5-5
| func (e *BootstrapRefusedError) Unwrap() error { return e.Err } | ||
|
|
||
| // exchangeBootstrapToken trades a vbt_ token for a normal CLI login. | ||
| func (s Service) exchangeBootstrapToken(ctx context.Context, apiURL, token string) (*api.BootstrapExchange, error) { |
There was a problem hiding this comment.
⚪ P3: A vbt_ token is spent before --project is validated, so a failed login wastes it and leaves an unused token
LoginWithToken exchanges the one-time token first, then validates --project. If validation fails (wrong ID, 403 or 404), the exchanged CLI login is never saved, and the server keeps a live login named after this host that nobody holds. The vbt_ token is also used up, so rerunning volcano connect --token vbt_… --project <id> reports "already used" and falls back to browser login. Consider saving the exchanged credential before project validation, or validating the project in a separate step.
Found by claude-opus-5-5
| volcano projects create my-app # create a project | ||
| volcano projects list # list your projects | ||
| volcano use my-app # set the active project | ||
| volcano connect # link this directory to a project |
There was a problem hiding this comment.
⚪ P3: README example says volcano connect links this directory, but with no argument it creates a new one
With no directory argument, connect creates a new project and a new subdirectory named after it (see resolveTarget and docs/connect.md). The comment # link this directory to a project describes volcano connect .. Change the example to volcano connect ., or change the comment.
Found by claude-opus-5-5
| - `volcano cloud domains list` and `volcano cloud domains verify` | ||
| - `volcano durable approvals …` | ||
| - `volcano functions invoke` | ||
| - `volcano projects keys service …` |
There was a problem hiding this comment.
⚪ P3: Lists volcano projects keys service … as supporting --json, but only create does
In internal/cmd/project/service_keys.go, only the create subcommand registers --json. list and get don't, so a script following this page gets an unknown-flag error. Narrow the entry to volcano projects keys service create.
Found by claude-opus-5-5
| cfg.UserID = credentials.UserID | ||
| cfg.SessionAPIURL = credentials.APIURL | ||
| if credentials.Project != nil { | ||
| cfg.CurrentProject = credentials.Project |
There was a problem hiding this comment.
🟠 P1: Update the directory link when login selects a project
Inside a directory linked to A, volcano login --token pk-... --project B saves B globally and prints “Now using project: B”, but Config.Project() still selects the link to A. Subsequent commands such as volcano cloud deploy therefore target A despite the successful explicit selection; a token scoped to B instead produces 403 errors. Update the active directory link when persisting the selected login project, as volcano use now does.
Found by gpt-6.1-sol
| output.Note(opts.log, "%s is taken, so the new project is %s.", name, candidate) | ||
| } | ||
| return project, nil | ||
| case api.Status(err) == http.StatusConflict && suffix <= maxNameSuffix: |
There was a problem hiding this comment.
🟡 P2: Handle duplicate project names returned as HTTP 400
The existing internal/project/project_test.go::TestCreateWrapsAPIError represents a duplicate-name refusal as HTTP 400 with name already exists, and the create endpoint's OpenAPI contract documents 400 rather than 409. With that response, connecting another directory named after an existing project fails immediately instead of trying name-2. Recognize the duplicate-name refusal specifically rather than requiring HTTP 409.
Found by gpt-6.1-sol
| switch { | ||
| case errors.Is(err, config.ErrNotAuthenticated), status == http.StatusUnauthorized: | ||
| return exitNotAuthenticated | ||
| case errors.Is(err, config.ErrNoProjectSelected), errors.Is(err, config.ErrNoProjectSelectedForProjectToken): |
There was a problem hiding this comment.
🟡 P2: Classify missing-project errors from project key commands
For an authenticated user without a selected project, volcano projects keys anon list and volcano projects keys service list return plain errors from project.Service.ListAnonKeys and selectedProject. Neither wraps the sentinels checked here, so these commands exit 1 instead of the documented 3. Have those paths preserve the appropriate missing-project sentinel.
Found by gpt-6.1-sol
| } | ||
|
|
||
| func run(ctx context.Context, opts options) error { | ||
| starter, err := initcmd.ResolveStarter(opts.template, "") |
There was a problem hiding this comment.
🟡 P2: Validate templates before creating a cloud project
ResolveStarter only normalizes names, so a typo such as --template nextjss passes this check. Connect then creates and links a real cloud project before RunStarterIn rejects the template; scaffold reduces that error to a warning and connect exits successfully without the requested scaffold. Validate starter availability before authentication or project creation and return an error for an unsupported template.
Found by gpt-6.1-sol
|
|
||
| This directory is linked to a Volcano cloud project in .volcano/project.json | ||
| (gitignored). Volcano CLI commands run here use that project. If the file is | ||
| missing, run `+"`volcano connect`"+`. |
There was a problem hiding this comment.
🟡 P2: Use connect dot to recover a checkout's missing link
The link is intentionally gitignored, so a teammate cloning the repository normally reaches this instruction. Running bare volcano connect in that unlinked checkout creates a new child directory and scaffold, leaving the cloned application unlinked. Generate volcano connect . here; the newly added “link this directory” example in docs/README.md needs the same correction.
Found by gpt-6.1-sol
| func createProject(ctx context.Context, opts options, client *api.Client, name string, taken func(string) bool) (*apiclient.Project, error) { | ||
| candidate := name | ||
| for suffix := 2; ; suffix++ { | ||
| if taken != nil && taken(candidate) && suffix <= maxNameSuffix { |
There was a problem hiding this comment.
🟡 P2: Stop when directory-name suffixes are exhausted
If directories shop through shop-20 already exist, the iteration with candidate shop-20 has suffix == 21, so this condition stops honoring taken(candidate). Connect can then create that cloud project, reuse the occupied directory, and overwrite its project link and cloud values. Return an exhaustion error instead of proceeding with a known directory collision.
Found by gpt-6.1-sol
| volcano projects create my-app # create a project | ||
| volcano projects list # list your projects | ||
| volcano use my-app # set the active project | ||
| volcano connect # link this directory to a project |
There was a problem hiding this comment.
🟡 P2: README says bare volcano connect links this directory, but it creates a new project in a new subdirectory
The new Projects example reads volcano connect # link this directory to a project. With no directory argument, resolveTarget creates a new project with a generated name and a new ./<name> subdirectory, then links that subdirectory. Only volcano connect . links the current directory, as docs/connect.md and ErrNoProjectSelected both say. A reader who follows the README gets an extra project (or a project-limit error on the free plan) and an unexpected nested directory, while the current directory stays unlinked. Change the example to volcano connect ..
Found by claude-opus-5-5
| } else { | ||
| output.Success(opts.log, "Using project %s (%s)", project.Name, project.Id) | ||
| } | ||
| if err := waitUntilActive(ctx, authenticated.API, project); err != nil { |
There was a problem hiding this comment.
⚪ P3: A failed or repeated connect without a directory argument leaves an unlinked project and creates another on rerun
The project is created in resolveTarget, but cliproject.Link only runs after waitUntilActive, which can fail after its 2-minute timeout or when the user interrupts. That leaves a created project and an empty ./<generated-name> directory with no link. A rerun from the same working directory finds no link, generates a new name, and creates another project. On paid plans that duplicates projects; on the free plan it hits the project limit. Agents that rerun a pasted command do exactly this, yet docs/connect.md says the command "is safe to run again". Consider writing the link right after the project is chosen, before waiting, and qualify the "safe to run again" wording (it only holds inside the linked directory).
Found by claude-opus-5-5
| // canOfferSignIn reports whether a person is there to answer: both ends are | ||
| // terminals and nothing marks the run as non-interactive. | ||
| func canOfferSignIn(in io.Reader, out io.Writer) bool { | ||
| if os.Getenv("CI") != "" || os.Getenv("VOLCANO_NONINTERACTIVE") != "" { |
There was a problem hiding this comment.
⚪ P3: canOfferSignIn doesn't treat TERM=dumb as non-interactive, unlike volcano setup
setupcmd.interactive treats CI, VOLCANO_NONINTERACTIVE, and TERM=dumb as non-interactive. canOfferSignIn checks only the first two plus TTYs. Some coding agents run commands inside a pseudo-terminal, where both TTY checks pass. When such an agent is not logged in, any cloud command blocks on Do you already have one? [y/N]:. docs/authentication.md promises that an agent gets exit code 2 instead. If the agent sends EOF or anything other than y/yes, the empty or non-yes answer starts browser signup. Add the TERM=dumb check (or share the setup helper) so both commands detect non-interactive runs the same way.
Found by claude-opus-5-5
| if link != nil || err != nil { | ||
| return link, err | ||
| } | ||
| if dir == home || exists(filepath.Join(dir, ".git")) { |
There was a problem hiding this comment.
⚪ P3: FindLink walks to / outside $HOME and git repos, so any ancestor's .volcano/project.json can break or retarget every command
For a working directory that is neither inside a git repository nor under $HOME (e.g. /tmp/app, or /workspace in a container), the walk continues to the filesystem root. config.Load now returns an error for any unreadable or malformed link it meets. So a stray or planted /tmp/.volcano/project.json makes every command run below it fail, including login and connect, which could repair it. A valid one silently overrides volcano use for those commands. /tmp is world-writable on shared hosts. Consider stopping the walk at the filesystem boundary of the first directory the user owns, or treating link errors from ancestors other than the working directory as "no link" with a warning.
Found by claude-opus-5-5
| |---|---|---| | ||
| | `0` | Success | | | ||
| | `1` | Any other failure | Read the error on stderr | | ||
| | `2` | No credential is set, or the API answered HTTP 401 | Run `volcano login`, or `volcano signup` for a new account | |
There was a problem hiding this comment.
⚪ P3: An HTTP 401 on login --token / connect --token exits 1, not the documented 2
scripting.md documents exit code 2 for "the API answered HTTP 401". LoginWithToken turns a 401 from ValidateToken into errors.New("invalid token"), which wraps neither *api.Error nor config.ErrNotAuthenticated, so exitCode returns 1. A script or agent that branches on 2 to fetch a new credential won't recognize a rejected --token. Wrap config.ErrNotAuthenticated (or the API error) in that return, or narrow the doc's wording.
Found by claude-opus-5-5
| if err := ensureSession(ctx, opts, sessions); err != nil { | ||
| return err | ||
| } | ||
| authenticated, err := sessions.Authenticated() |
There was a problem hiding this comment.
🟠 P1: Use the explicitly validated token for the connect workflow
With VOLCANO_TOKEN=pk-A exported, connect --token pk-B validates and saves B, then reloads authentication through sessions.Authenticated(). Config.Token() still returns A, so project creation and key retrieval run under account A despite reporting successful authentication with B. Pin the validated credentials for the remaining workflow, or reject the conflicting environment token before proceeding.
Found by gpt-6.1-sol
| if err != nil { | ||
| return "", nil, false, err | ||
| } | ||
| dir, err := makeDir(filepath.Join(wd, dirName(project.Name))) |
There was a problem hiding this comment.
🟡 P2: Check existing links before reusing a project-named directory
connect --project B silently reuses the automatically chosen directory even when it belongs to A. For example, valid project names my_app and my-app normalize to the same directory. The subsequent writes replace A's link and cloud credentials with B's, changing the target of later volcano cloud deploy calls. Reuse an existing directory only when its link matches B; otherwise choose an unused directory or fail.
Found by gpt-6.1-sol
… not the saved login
| token = strings.TrimSpace(token) | ||
| project = strings.TrimSpace(project) | ||
| client, err := s.sessions.APIClient(s.apiURL(cfg), token) | ||
| apiURL := s.apiURL(cfg) |
There was a problem hiding this comment.
⚪ P3: vbt_ tokens are sent to the saved session API, which may not be the API that minted them
LoginWithToken resolves apiURL through cfg.APIURL(), which now returns SessionAPIURL from an earlier login. Take a user whose last login was against staging or a local API. They paste the dashboard prompt (connect --token vbt_…), and the production-minted one-time token goes to that other host. The exchange is refused as bootstrap_token_invalid, and ensureSession falls back to browser login on the same non-default API, so the project is created there instead of in the account the prompt came from. This only affects users with a saved non-default session, mostly maintainers. The same stickiness means a plain volcano login or volcano signup can't return to the default API without logout or VOLCANO_API_URL. Consider ignoring SessionAPIURL for login, signup and the bootstrap exchange, or only persisting it when it differs from the default.
Found by claude-opus-5-5
| // a new directory named after it. | ||
| func resolveTarget(ctx context.Context, opts options, session *clisession.Session, wd string) (string, *apiclient.Project, bool, error) { | ||
| if opts.dir != "" { | ||
| dir, err := makeDir(opts.dir) |
There was a problem hiding this comment.
⚪ P3: Directory argument is created before the project exists, so a failed create leaves an empty directory
resolveTarget calls makeDir(opts.dir) before existingProject or linkedOrNewProject run. If project creation fails, for example at the plan's project limit (common on the free plan, where signup already created one project) or with an invalid --project value, volcano connect my-app exits with an error and leaves an empty ./my-app. The error then suggests volcano connect --project <id>, which with no directory argument sets up a different ./<project-name> directory, so the empty one stays behind. Creating the directory after the project resolves would avoid this.
Found by claude-opus-5-5
| fallback = key.KeyValue | ||
| } | ||
| } | ||
| if fallback != "" { |
There was a problem hiding this comment.
🟡 P2: Search all anon-key pages before choosing a fallback
api.Client.ListAnonKeys fetches only the first default-sized page and discards pagination metadata. If an existing project's default key is on a later page, connect selects an arbitrary first-page key instead. Anon keys can have different permissions, so the generated frontend configuration can fail operations allowed by the project's default key. Paginate until the default is found before considering a fallback.
Found by gpt-6.1-sol
| if envKey(line) == v.key { | ||
| lines[i] = v.key + "=" + v.value | ||
| found = true | ||
| break |
There was a problem hiding this comment.
🟡 P2: Replace every existing definition of an env key
upsertEnv stops after replacing the first matching assignment. If cloud.env already contains a duplicate key, a later stale definition remains authoritative when envfile.Load parses it. Connect can report updating the project values while the recommended cloud deploy still reads the previous project's anon key. Remove duplicate assignments or update every occurrence of each managed key.
Found by gpt-6.1-sol
Tracking
/dashboard/new,?project=selection)Why
Summary
volcano connect [directory]: signs in (reuses a valid login,--token, browser device login, or--signup), picks the project, links the directory, runs the base scaffold ifvolcano/is missing (or--template), writes the API URL and default anon key tovolcano/cloud.env, gitignores the link andvolcano/*.env, adds a marked Volcano section toAGENTS.md, installs skills, and printsProject:,Directory:andDashboard:lines;--jsonsupportedconnectcreates the project first (--name, or a generated{volcano}-{color}-{NN}name using the dashboard's word lists) and sets it up in a new./<name>directory; with a directory argument the project is named after the directory;--project <project-id>uses an existing project; run inside a linked directory it keeps the linked projectconnecttriesname-2,name-3, and so on (up to-20)connectfails and lists each existing project with itsvolcano connect --project <id>commandvbt_bootstrap tokens:--token vbt_…onconnectorlogincallsPOST /auth/cli-bootstrap-tokens/exchange(volcano-hosting#1924) and saves only the returned CLI login, with the same lifetime as avolcano logintoken; thevbt_token is never written to diskconnectsays which and falls back to browser login, not the saved login, which may be another accountvolcano signuptoovolcano connect .to create a project and link the current directoryconnectinstalls skills for every detected coding agent, asvolcano setup --yesdoes;volcano setupitself is unchanged frommain.volcano/project.json; project precedence is nowVOLCANO_PROJECT_ID> directory link > the projectvolcano usesaved;volcano useinside a linked directory updates the link, andprojects renamekeeps the link's name in stepAGENTS.mdsection names no project for the same reasonapi_urlin~/.volcano/config.json) and use it whenVOLCANO_API_URLis unset, so a staging login no longer sends its token to production; it is ignored whenVOLCANO_TOKENsupplies the tokenlogin/signupprint the API they signed in to when it is not the defaultsandboxes exec/shellstill pass the guest's exit code through. This changes exit codes that were 1 before--jsontoprojects listandprojects getcodefield from raw error bodies ininternal/api/errors.go, so hand-written calls can branch on itdocs/connect.mdanddocs/scripting.md, and updatedocs/authentication.mdanddocs/README.md;vbt_tokens stay out ofdocs/until volcano-hosting#1924 shipsvolcano connect --tokenvolcano connect .also writes the env file,AGENTS.mdand the scaffold, which a bare create would skipvolcano logintoken, the exchanged token lasts the API'splatform_token_ttl(30 days by default), then the user signs in again--jsonon every command; this PR covers only whatconnectand the projects commands needvolcano cloud migrations deploy, which doesn't exist (it iscloud databases migration up, orcloud deploy)cloud databases create --pg-versionpapercutVerification
make check(generated-code check, release workflow check, lint, installer tests,go test ./...)node scripts/lint-docs.mjs <volcano-cli>/docsfrom a localKong/volcano-docscheckout that contains the pinned commitgo run ./cmd/volcano connect --helpconnectis covered by unit tests against a fake API: first run, rerun, project limit, name suffixes, generated names and directories, an existing directory's name skipped, a rerun from inside a linked directory,--projectwith and without a directory argument,--projectrejecting a name, a bootstrap token exchanged, and a refused bootstrap token going to browser login instead of the saved login;git check-ignoreconfirmsvolcano/cloud.envand.volcano/project.jsonare ignoredconnectcreates a real project, and the exchange endpoint isn't deployed yet