Skip to content

fix: guard /managed-workers routes in self-hosted mode#3900

Open
HirenGajjar wants to merge 4 commits into
hatchet-dev:mainfrom
HirenGajjar:fix/managed-workers-self-hosted-guard
Open

fix: guard /managed-workers routes in self-hosted mode#3900
HirenGajjar wants to merge 4 commits into
hatchet-dev:mainfrom
HirenGajjar:fix/managed-workers-self-hosted-guard

Conversation

@HirenGajjar
Copy link
Copy Markdown

Fixes #3896

What

Added a useCloud() guard to the managed-workers index and create pages.
In self-hosted (Lite) mode both routes now return a 404 instead of
causing an infinite loading screen.

Why

isCloudEnabled is false in self-hosted mode. Without the guard, the
page attempted to call cloud-only billing and compute APIs that don't
exist, resulting in an infinite spinner and a flood of failed requests.

Changes

  • pages/main/v1/managed-workers/index.tsx — added cloud guard
  • pages/main/v1/managed-workers/create/index.tsx — added cloud guard

Testing

Build the Vite app and launch Hatchet Lite per the steps in #3896.
Navigating to /managed-workers and /managed-workers/create now
returns a 404 page instead of the infinite loading screen.

In self-hosted (Lite) mode, navigating to /managed-workers caused an
infinite loading screen because the page attempted to fetch cloud-only
billing and compute APIs that don't exist in self-hosted deployments.

Added a useCloud() guard to both the managed-workers index and create
pages. When isCloudLoading is true a spinner is shown; when
isCloudEnabled is false a 404 page is returned instead, preventing the
broken loading state.

Fixes hatchet-dev#3896
@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

@HirenGajjar is attempting to deploy a commit to the Hatchet Team on Vercel.

A member of the Team first needs to authorize it.

@promptless-for-oss
Copy link
Copy Markdown

Promptless prepared a documentation update related to this change.

Triggered by PR #3900

Added a "Cloud-only features" section to the cloud-vs-oss.mdx page that explicitly documents Managed Compute as a cloud-only feature. This clarifies that self-hosted users will see a 404 when attempting to access these routes.

Review: Document cloud-only features for self-hosted users

Copy link
Copy Markdown
Collaborator

@gregfurman gregfurman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite a few comments that are polluting the diff IMO. Think you should run task fmt-app to fix all these linting issues.

Also, perhaps we use custom error messaging here instead of the 404 approach 🤔 Wdyt of instead creating a gate component that we can use to wrap these managed worker exports? i.e

export function ManagedWorkersGate({ children }: PropsWithChildren) {
  const { cloud, featureFlags, isCloudEnabled } = useCloud();
  const managedWorkerEnabled = featureFlags?.['managed-worker'] === 'true';

  if (isCloudEnabled && !cloud) {
    return null;
  }

  if (managedWorkerEnabled) {
    return <>{children}</>;
  }

  if (!isCloudEnabled) {
    // create a new ErrorPageLayout with details on why page can't be accessed
    return <ErrorPageLayout>...</ErrorPageLayout>

Which can be used like:

// renamed the original ManagedWorkers to ManagedWorkersImpl 
// ...

export default function ManagedWorkers() {
  return <ManagedWorkersGate>{ManagedWorkersImpl()}</ManagedWorkersGate>
}

Comment on lines +178 to +179
// ── Render ──────────────────────────────────────────────────────────────

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// ── Render ──────────────────────────────────────────────────────────────

};

// Only show BillingRequired if there are no managed workers AND billing is required
// ── Derived values (not hooks) ──────────────────────────────────────────
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// ── Derived values (not hooks) ──────────────────────────────────────────

Comment on lines +22 to +23
// ── Hooks (must all be called unconditionally, before any early returns) ──

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// ── Hooks (must all be called unconditionally, before any early returns) ──

Replace inline useCloud() checks with a reusable ManagedWorkersGate
component that handles all three cases:
- cloud enabled but metadata still loading: render nothing
- managed-worker feature flag enabled: render children
- self-hosted mode: render ErrorPageLayout with descriptive message

Addresses review feedback on PR hatchet-dev#3900
Copy link
Copy Markdown
Collaborator

@gregfurman gregfurman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove some of these unnecessary headers and revert unnecessary changes? The diff is probably larger than it needs to be tbh

Also, Can we apply this new gated component approach to the remainder of components in the managed-workers dir?


const [portalLoading, setPortalLoading] = useState(false);
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹

Suggested change


export default function CreateWorker() {
function CreateWorkerImpl() {
// ── Hooks ────────────────────────────────────────────────────────────────
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹

Suggested change
// ── Hooks ────────────────────────────────────────────────────────────────

});
},
onError: handleApiError,
});
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why has this moved?

onError: handleApiError,
});

// ── Derived values ───────────────────────────────────────────────────────
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹

Suggested change
// ── Derived values ───────────────────────────────────────────────────────

},
onError: handleApiError,
});
// ── Early returns ─────────────────────────────────────────────────────────
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹

Suggested change
// ── Early returns ─────────────────────────────────────────────────────────

Comment on lines +21 to +22
// ── Hooks ────────────────────────────────────────────────────────────────

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹

Suggested change
// ── Hooks ────────────────────────────────────────────────────────────────

Comment on lines +43 to +50
const { handleApiError } = useApiError({});

// ── Derived values ───────────────────────────────────────────────────────

const workerPoolCount = listManagedWorkersQuery.data?.rows?.length || 0;
const [canCreateMoreWorkerPools] = can(
managedCompute.canCreateWorkerPool(workerPoolCount),
);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same Q as above. Why has this moved?


const { handleApiError } = useApiError({});

// ── Derived values ───────────────────────────────────────────────────────
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹

Suggested change
// ── Derived values ───────────────────────────────────────────────────────

- Remove unnecessary section comments and revert code reordering
- Apply ManagedWorkersGate to demo-template and $managed-worker pages
- Restore original comments and structure in all modified files

Addresses review feedback on PR hatchet-dev#3900
Copy link
Copy Markdown
Collaborator

@gregfurman gregfurman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking great! Think we should add a fallback case for if managed compute isn't enabled but someone is running in hatchet cloud.

Otherwise, would just like to double-check with the team on the copy used in the error messages.

In the meantime, could you perhaps add screenshots of this new modal in action to the PR description? Also if you could update it to reflect the latest changes that'd be brilliant 🙏

const navigate = useNavigate();
const { tenantId } = useCurrentTenantId();

// Cloud enabled but metadata not yet loaded
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹

Suggested change
// Cloud enabled but metadata not yet loaded

return null;
}

// Feature flag explicitly enables managed workers
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹

Suggested change
// Feature flag explicitly enables managed workers

return <>{children}</>;
}

// Self-hosted mode — show informative message
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Self-hosted mode — show informative message

);
}

return null;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we fallback to an error component if we try to access this but the feature flag for managed-worker is not enabled? Doesn't have to be this verbatim since I used some extra iconography + buttons but am thinking something to this extent

  return (
    <ErrorPageLayout
      icon={<LifeBuoy className="h-5 w-5" />}
      title="Managed Workers not enabled"
      description="Managed Workers aren't enabled by default for your tenant. Contact support for more information."
      actions={
        <>
          <Button
            leftIcon={<Mail className="h-4 w-4" />}
            onClick={() =>
              window.open(`mailto:${HATCHET_SUPPORT_ADDRESS}`, '_blank')
            }
          >
            Contact us
          </Button>
          <Button
            leftIcon={<Undo2 className="h-4 w-4" />}
            variant="outline"
            onClick={() => window.history.back()}
          >
            Go back
          </Button>
        </>
      }
    />
  );

Comment on lines +35 to +39
onClick={() =>
navigate({
to: appRoutes.tenantRunsRoute.to,
params: { tenant: tenantId },
})
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rather allow for an undo to the previous window instead of /runs and use the Undo2 icon (like we do elsewhere) i.e

Assuming theUndo2 is imported from lucide-react:

Suggested change
onClick={() =>
navigate({
to: appRoutes.tenantRunsRoute.to,
params: { tenant: tenantId },
})
leftIcon={<Undo2 className="h-4 w-4" />}
variant="outline"
onClick={() => window.history.back()}

<ErrorPageLayout
icon={<CloudIcon className="h-5 w-5" />}
title="Managed Compute is not available"
description="Managed Compute is a cloud-only feature and is not supported in self-hosted deployments. Sign up for Hatchet Cloud to access this feature."
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be linking directly to the cloud offering here? Unsure how much we'd like to do this in the OSS offering though. Wdyt @abelanger5?

i.e
Image

- Replace navigate-to-runs button with window.history.back() + Undo2 icon
- Add fallback ErrorPageLayout for cloud users without managed-worker feature flag
- Remove unnecessary useNavigate/useCurrentTenantId/appRoutes from gate

Addresses review feedback on PR hatchet-dev#3900
@HirenGajjar
Copy link
Copy Markdown
Author

I was unable to run the dev frontend locally due to a pre-existing missing @/lib/generated/docs file that blocks Vite from starting. Happy to add screenshots once that's resolved, or if you can point me to the correct generation step. The gate component follows the same ErrorPageLayout pattern used in tenant-forbidden.tsx and not-found.tsx so the visual output should be consistent with those.

@gregfurman
Copy link
Copy Markdown
Collaborator

I was unable to run the dev frontend locally due to a pre-existing missing @/lib/generated/docs file that blocks Vite from starting.

Have you not run this locally? How were you able to test that everything rendered without error?

You can see our CONTRIBUTING.md for details on how to get your local environment setup.

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.

[BUG] [FRONTEND] Invalid access of /managed-workers in self-hosted mode

3 participants