Skip to content

login silently persists no project: ResolveDefaultProject returns ("", nil) when /users/current/projects returns membership objects #7

Description

@akshaysasidrn

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:

  1. len(projects) == 1, not 0, so the ListOrgProjects fallback at line 101 never fires.
  2. p.DefaultProject is nil for every item, so the default-project loop matches nothing.
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions