Summary
stackdome login succeeds but silently persists no project. Every later command that needs one fails with exit 3 / Resource not found, and the user has no way to tell why.
ResolveDefaultProject returns ("", nil) — an empty project name with a nil error. login treats that as success, assigns it, and Save() drops the key because of omitempty.
Reproduced against https://stackdome.io with CLI v0.0.2-alpha (commit 9468030, darwin/arm64).
Reproduce
$ stackdome login --url https://stackdome.io --token <token>
Logged in as Akshay # exit 0, nothing on stderr
$ cat ~/.stackdome/config.json
{
"server_url": "https://stackdome.io",
"access_token": "…",
"organization_id": "0872dd94-5681-4872-b414-1711d39b3621",
"username": "Akshay"
} # no "project_name"
$ stackdome stack list -o json
{"error":"Resource not found","exit_code":3}
$ STACKDOME_PROJECT=default stackdome stack list -o json
[ … works … ]
The project exists and the org-scoped endpoint returns it correctly:
$ stackdome api /api/v1/organizations/<org_id>/projects -o json
{"items":[{"default_project":true,"id":"06c200aa-…","name":"default", …}],"total":1}
Root cause
GET /api/v1/users/current/projects returns project-membership objects, not Project objects — the project fields are nested under a project key:
{"items":[{"created_at":"0001-01-01T00:00:00Z",
"id":"",
"project":{"name":"default","default_project":true,"id":"06c200aa-…", …},
"project_id":"06c200aa-…",
"role":"Developer"}],
"total":1}
ListCurrentUserProjects (internal/client/auth.go:72) decodes items as []serverapi.Project. name and default_project are absent at the top level, so every field decodes to its zero value — and the generated model enforces no required fields, so json.Unmarshal returns nil.
ResolveDefaultProject (internal/client/auth.go:91) then walks off the end of every branch:
len(projects) == 1, not 0, so the ListOrgProjects fallback at line 101 never fires.
p.DefaultProject is nil for every item, so the default-project loop matches nothing.
- Control reaches
return projects[0].Name, nil → ("", nil).
persistLogin (cmd/stackdome/login.go:155) only warns when err != nil. The error is nil, so it assigns cfg.ProjectName = "", and ProjectName string \json:"project_name,omitempty"`` omits the key entirely. Login reports success; the config is unusable.
The comment at auth.go:97 says the server used to answer {"items":[],"total":0}. It no longer does — it returns one wrongly-shaped item. The fallback guard tests the old symptom, so the workaround has silently stopped working, and the new failure is worse than the one it was added to fix: an empty project name instead of a clear error.
Minimal reproduction
Decoding the verbatim server body into serverapi.ProjectList:
var resp serverapi.ProjectList
err := json.Unmarshal([]byte(body), &resp)
// err = <nil>
// len(Items) = 1
// Items[0].Name = ""
// Items[0].DefaultProject = <nil>
Suggested fixes
Two independent problems:
1. Server (the actual contract violation) — /api/v1/users/current/projects should return Project objects, matching the ProjectList schema its own OpenAPI spec declares. Right now the documented schema and the response disagree.
2. CLI (defense in depth) — even once the server is fixed:
- Decode the membership shape and read the nested
project, or give the endpoint its own generated model rather than reusing Project.
- Change the fallback guard from
len(projects) == 0 to "no item yielded a usable name", so a shape change degrades into the ListOrgProjects fallback instead of returning empty.
ResolveDefaultProject must never return ("", nil). An empty name should be an error. That single guard would have turned this into a clear message at login time instead of an exit 3 several commands later.
Side effects worth noting
STACKDOME_PROJECT=default is the only workaround, and it disables persisted stack selection (cmd/stackdome/stack.go:43), so --stack <name> becomes mandatory on every command too.
stackdome doctor reports project: {"status":"ok","implicit":true} in this state, which reads as healthy when it is not. It could flag a missing/empty project name instead.
Summary
stackdome loginsucceeds but silently persists no project. Every later command that needs one fails with exit3/Resource not found, and the user has no way to tell why.ResolveDefaultProjectreturns("", nil)— an empty project name with a nil error.logintreats that as success, assigns it, andSave()drops the key because ofomitempty.Reproduced against
https://stackdome.iowith CLIv0.0.2-alpha(commit9468030, darwin/arm64).Reproduce
The project exists and the org-scoped endpoint returns it correctly:
Root cause
GET /api/v1/users/current/projectsreturns project-membership objects, notProjectobjects — the project fields are nested under aprojectkey:{"items":[{"created_at":"0001-01-01T00:00:00Z", "id":"", "project":{"name":"default","default_project":true,"id":"06c200aa-…", …}, "project_id":"06c200aa-…", "role":"Developer"}], "total":1}ListCurrentUserProjects(internal/client/auth.go:72) decodesitemsas[]serverapi.Project.nameanddefault_projectare absent at the top level, so every field decodes to its zero value — and the generated model enforces no required fields, sojson.Unmarshalreturns nil.ResolveDefaultProject(internal/client/auth.go:91) then walks off the end of every branch:len(projects) == 1, not0, so theListOrgProjectsfallback at line 101 never fires.p.DefaultProjectisnilfor every item, so the default-project loop matches nothing.return projects[0].Name, nil→("", nil).persistLogin(cmd/stackdome/login.go:155) only warns whenerr != nil. The error is nil, so it assignscfg.ProjectName = "", andProjectName string \json:"project_name,omitempty"`` omits the key entirely. Login reports success; the config is unusable.The comment at
auth.go:97says the server used to answer{"items":[],"total":0}. It no longer does — it returns one wrongly-shaped item. The fallback guard tests the old symptom, so the workaround has silently stopped working, and the new failure is worse than the one it was added to fix: an empty project name instead of a clear error.Minimal reproduction
Decoding the verbatim server body into
serverapi.ProjectList:Suggested fixes
Two independent problems:
1. Server (the actual contract violation) —
/api/v1/users/current/projectsshould returnProjectobjects, matching theProjectListschema its own OpenAPI spec declares. Right now the documented schema and the response disagree.2. CLI (defense in depth) — even once the server is fixed:
project, or give the endpoint its own generated model rather than reusingProject.len(projects) == 0to "no item yielded a usable name", so a shape change degrades into theListOrgProjectsfallback instead of returning empty.ResolveDefaultProjectmust never return("", nil). An empty name should be an error. That single guard would have turned this into a clear message at login time instead of an exit3several commands later.Side effects worth noting
STACKDOME_PROJECT=defaultis the only workaround, and it disables persisted stack selection (cmd/stackdome/stack.go:43), so--stack <name>becomes mandatory on every command too.stackdome doctorreportsproject: {"status":"ok","implicit":true}in this state, which reads as healthy when it is not. It could flag a missing/empty project name instead.