Skip to content

fix(otto): preflight org membership before launching Otto (AI-992) - #2208

Open
akshayarora wants to merge 1 commit into
mainfrom
akshayarora/ai-992-otto-org-membership-preflight
Open

fix(otto): preflight org membership before launching Otto (AI-992)#2208
akshayarora wants to merge 1 commit into
mainfrom
akshayarora/ai-992-otto-org-membership-preflight

Conversation

@akshayarora

Copy link
Copy Markdown
Contributor

Summary

astro otto could launch Otto with a token whose user isn't a member of ASTRO_ORGANIZATION, and the mismatch was invisible until a downstream, RBAC-gated feature failed. A demo/interview user hit it as:

Failed to fetch skill "airflow-upgrade" and no cached version available:
403: organization with id cmrfr39s14zyl01jhp2mrcxnj is forbidden

Root cause: the org and the token identity are decoupled, and the only place that reconciles them is astro login (resolveActiveOrg), not astro otto. The org handed to Otto can come from the stored context block, an ASTRO_DOMAIN-selected block, or a stale shell-exported ASTRO_ORGANIZATION that BuildEnv passes through when the context org is empty. Core's LLM gateway isn't RBAC-gated, so Otto boots and chats fine; only the first org-scoped, permission-checked call (hosted skills) 403s — reading like a skills bug.

Change

A launch-time membership preflight in pkg/otto (preflight.go), run in Start() right after the login check:

  • Resolves the effective org exactly as BuildEnv does (context org, else inherited ASTRO_ORGANIZATION) and calls GetOrganization with the current token.
  • On a definitive 403/404, prints actionable guidance naming the signed-in user (astro login / astro organization switch / clear a stale export) and returns ErrNotOrgMember; cmd/otto.go exits quietly (mirrors ErrNotLoggedIn).
  • Fails closed only on 403/404. Request errors, 5xx, and unexpected shapes are treated as inconclusive and let the launch proceed — the preflight must never become a new way for astro otto to break.

Testing

pkg/otto/preflight_test.go (12 cases, all green): effective-org resolution; member proceeds silently; 403 and 404 fail closed with user-named guidance; the inherited-env-org path; self-user lookup failure still blocks with a generic subject; skip-when-no-org/token; and fail-open on request-error / 5xx. go build, go vet, and the full pkg/otto suite pass.

Notes

  • Core's 403 is correct RBAC behavior — a non-member has no org-scoped role binding, so no organization.skills.get. This PR fixes the client so the failure is caught early with a clear message instead of surfacing mid-session.
  • Companion (defense-in-depth) change in astronomer/otto makes the skill-fetch 403 self-explain for callers that bypass astro otto (dev scripts, SDK).
  • Tracked in AI-992.

🤖 Generated with Claude Code

`astro otto` could spawn Otto with a token whose user isn't a member of
ASTRO_ORGANIZATION — e.g. a stale ASTRO_ORGANIZATION export, or an org
selected under a different login. The mismatch was invisible at launch (the
LLM gateway isn't RBAC-gated) and only surfaced later as a confusing
"403: organization <id> is forbidden" on the first org-scoped call (hosted
skills).

Add a launch-time membership preflight in pkg/otto:

- Resolve the effective org the way BuildEnv does (context org, else the
  inherited ASTRO_ORGANIZATION that passes through when the context org is
  empty) and call GetOrganization with the current token.
- On a definitive 403/404, fail fast with actionable guidance that names the
  signed-in user (astro login / astro organization switch / clear a stale
  export), and exit quietly like ErrNotLoggedIn.
- Fail CLOSED only on 403/404; any transient or inconclusive outcome
  (request error, 5xx, unexpected shape) lets the launch proceed so the
  preflight can never become a new way for `astro otto` to break.

Adds pkg/otto/preflight_test.go covering member/403/404, the inherited-env
org path, self-user lookup failure, skip-when-no-org/token, and fail-open.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coveralls-official

Copy link
Copy Markdown

Coverage Report for CI Build 29289835256

Coverage increased (+0.03%) to 43.918%

Details

  • Coverage increased (+0.03%) from the base build.
  • Patch coverage: 11 uncovered changes across 3 files (39 of 50 lines covered, 78.0%).
  • 1 coverage regression across 1 file.

Uncovered Changes

File Changed Covered %
pkg/otto/preflight.go 44 39 88.64%
cmd/otto.go 3 0 0.0%
pkg/otto/start.go 3 0 0.0%

Coverage Regressions

1 previously-covered line in 1 file lost coverage.

File Lines Losing Coverage Coverage
cmd/otto.go 1 18.46%

Coverage Stats

Coverage Status
Relevant Lines: 59060
Covered Lines: 25938
Line Coverage: 43.92%
Coverage Strength: 8.54 hits per line

💛 - Coveralls

@akshayarora
akshayarora marked this pull request as ready for review July 23, 2026 17:42
@akshayarora
akshayarora requested a review from a team as a code owner July 23, 2026 17:42
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.

1 participant