Executor version: v1.6.8
Surface: self-hosted on Cloudflare Workers (apps/host-cloudflare), behind Cloudflare Access
Integration: any workspace-owned connection (observed on cloudflare-api, cloudflare-bindings)
What happened
After upgrading to v1.6.8, an administrator (email listed in ADMIN_EMAILS) can no longer perform any workspace mutation from the console. On a workspace-owned connection the row menu offers only Check now — Edit, Reconnect and Remove are all hidden. Personal connections keep the full menu.
The server does not agree with the UI. From the same authenticated browser session:
POST /api/oauth/start {"clientOwner":"org","owner":"org","integration":"cloudflare-api",...}
→ 200 {"status":"redirect","authorizationUrl":"https://..."}
So the principal is an admin and the API authorises the write. Only the console hides it.
Cause
principalFromAccessClaims computes the role correctly (apps/host-cloudflare/src/auth/cloudflare-access.ts):
const isAdmin = email.length > 0 && config.adminEmails.includes(email.toLowerCase());
orgRole: isAdmin ? "admin" : "member",
But AccountProvider.me (apps/host-cloudflare/src/account/account-provider.ts) never returns it:
me: (headers) => ... Effect.succeed({
user: { id, email, name, avatarUrl },
organization: { id, name, slug },
}) // no orgRole / orgRoleModel
Confirmed against a live deployment — GET /api/account/me returns the correct email and organization, and no role field of any kind.
Per #1919, "Missing role data under the organization model fails closed", so the console treats every principal as a member and hides workspace mutations from everyone, admins included. Cloud derives roles from WorkOS and self-host from Better Auth; the Cloudflare host appears to have been missed.
What I expected
AccountProvider.me on host-cloudflare should surface orgRole (and orgRoleModel) from the verified Access principal, so the console can enable workspace mutations for admins — matching the cloud and self-host behaviour.
Steps to reproduce
- Deploy
apps/host-cloudflare at v1.6.8 behind Cloudflare Access, with ADMIN_EMAILS=you@example.com.
- Sign in through Access with that exact email.
GET /api/account/me → your email and organization are correct; no role is returned.
- Open an integration that has a workspace connection → its row menu shows only Check now.
POST /api/oauth/start with owner: "org" from that same session → 200 with an authorizationUrl, proving the server treats you as an admin.
The practical effect is that an expired workspace OAuth connection cannot be repaired through the UI at all, because Reconnect and Remove are both hidden. Rolling the Worker back to the pre-1.6.8 version restores the buttons.
Related note (possibly separate)
Cloudflare Access service tokens authenticate with common_name and carry no email, so isAdmin can never be true for them — the unit test asserts this directly (// a token is a member, not an admin). On an Access-gated single-tenant deployment this means every machine identity permanently loses workspace writes, including executor.mcp.addServer and connection reconnects from inside execute. An ADMIN_COMMON_NAMES allowlist alongside ADMIN_EMAILS would restore that; happy to open it as a separate feature request if you'd prefer.
Executor version: v1.6.8
Surface: self-hosted on Cloudflare Workers (
apps/host-cloudflare), behind Cloudflare AccessIntegration: any workspace-owned connection (observed on
cloudflare-api,cloudflare-bindings)What happened
After upgrading to v1.6.8, an administrator (email listed in
ADMIN_EMAILS) can no longer perform any workspace mutation from the console. On a workspace-owned connection the row menu offers only Check now — Edit, Reconnect and Remove are all hidden. Personal connections keep the full menu.The server does not agree with the UI. From the same authenticated browser session:
So the principal is an admin and the API authorises the write. Only the console hides it.
Cause
principalFromAccessClaimscomputes the role correctly (apps/host-cloudflare/src/auth/cloudflare-access.ts):But
AccountProvider.me(apps/host-cloudflare/src/account/account-provider.ts) never returns it:Confirmed against a live deployment —
GET /api/account/mereturns the correct email and organization, and no role field of any kind.Per #1919, "Missing role data under the organization model fails closed", so the console treats every principal as a member and hides workspace mutations from everyone, admins included. Cloud derives roles from WorkOS and self-host from Better Auth; the Cloudflare host appears to have been missed.
What I expected
AccountProvider.meonhost-cloudflareshould surfaceorgRole(andorgRoleModel) from the verified Access principal, so the console can enable workspace mutations for admins — matching the cloud and self-host behaviour.Steps to reproduce
apps/host-cloudflareat v1.6.8 behind Cloudflare Access, withADMIN_EMAILS=you@example.com.GET /api/account/me→ your email and organization are correct; no role is returned.POST /api/oauth/startwithowner: "org"from that same session →200with anauthorizationUrl, proving the server treats you as an admin.The practical effect is that an expired workspace OAuth connection cannot be repaired through the UI at all, because Reconnect and Remove are both hidden. Rolling the Worker back to the pre-1.6.8 version restores the buttons.
Related note (possibly separate)
Cloudflare Access service tokens authenticate with
common_nameand carry noemail, soisAdmincan never be true for them — the unit test asserts this directly (// a token is a member, not an admin). On an Access-gated single-tenant deployment this means every machine identity permanently loses workspace writes, includingexecutor.mcp.addServerand connection reconnects from insideexecute. AnADMIN_COMMON_NAMESallowlist alongsideADMIN_EMAILSwould restore that; happy to open it as a separate feature request if you'd prefer.