fix: guard /managed-workers routes in self-hosted mode#3900
fix: guard /managed-workers routes in self-hosted mode#3900HirenGajjar wants to merge 4 commits into
Conversation
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
|
@HirenGajjar is attempting to deploy a commit to the Hatchet Team on Vercel. A member of the Team first needs to authorize it. |
|
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. |
There was a problem hiding this comment.
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>
}| // ── Render ────────────────────────────────────────────────────────────── | ||
|
|
There was a problem hiding this comment.
| // ── Render ────────────────────────────────────────────────────────────── |
| }; | ||
|
|
||
| // Only show BillingRequired if there are no managed workers AND billing is required | ||
| // ── Derived values (not hooks) ────────────────────────────────────────── |
There was a problem hiding this comment.
| // ── Derived values (not hooks) ────────────────────────────────────────── |
| // ── Hooks (must all be called unconditionally, before any early returns) ── | ||
|
|
There was a problem hiding this comment.
| // ── 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
gregfurman
left a comment
There was a problem hiding this comment.
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>>({}); | ||
|
|
|
|
||
| export default function CreateWorker() { | ||
| function CreateWorkerImpl() { | ||
| // ── Hooks ──────────────────────────────────────────────────────────────── |
There was a problem hiding this comment.
🧹
| // ── Hooks ──────────────────────────────────────────────────────────────── |
| }); | ||
| }, | ||
| onError: handleApiError, | ||
| }); |
| onError: handleApiError, | ||
| }); | ||
|
|
||
| // ── Derived values ─────────────────────────────────────────────────────── |
There was a problem hiding this comment.
🧹
| // ── Derived values ─────────────────────────────────────────────────────── |
| }, | ||
| onError: handleApiError, | ||
| }); | ||
| // ── Early returns ───────────────────────────────────────────────────────── |
There was a problem hiding this comment.
🧹
| // ── Early returns ───────────────────────────────────────────────────────── |
| // ── Hooks ──────────────────────────────────────────────────────────────── | ||
|
|
There was a problem hiding this comment.
🧹
| // ── Hooks ──────────────────────────────────────────────────────────────── |
| const { handleApiError } = useApiError({}); | ||
|
|
||
| // ── Derived values ─────────────────────────────────────────────────────── | ||
|
|
||
| const workerPoolCount = listManagedWorkersQuery.data?.rows?.length || 0; | ||
| const [canCreateMoreWorkerPools] = can( | ||
| managedCompute.canCreateWorkerPool(workerPoolCount), | ||
| ); |
There was a problem hiding this comment.
Same Q as above. Why has this moved?
|
|
||
| const { handleApiError } = useApiError({}); | ||
|
|
||
| // ── Derived values ─────────────────────────────────────────────────────── |
There was a problem hiding this comment.
🧹
| // ── 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
gregfurman
left a comment
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
🧹
| // Cloud enabled but metadata not yet loaded |
| return null; | ||
| } | ||
|
|
||
| // Feature flag explicitly enables managed workers |
There was a problem hiding this comment.
🧹
| // Feature flag explicitly enables managed workers |
| return <>{children}</>; | ||
| } | ||
|
|
||
| // Self-hosted mode — show informative message |
There was a problem hiding this comment.
| // Self-hosted mode — show informative message |
| ); | ||
| } | ||
|
|
||
| return null; |
There was a problem hiding this comment.
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>
</>
}
/>
);| onClick={() => | ||
| navigate({ | ||
| to: appRoutes.tenantRunsRoute.to, | ||
| params: { tenant: tenantId }, | ||
| }) |
There was a problem hiding this comment.
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:
| 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." |
There was a problem hiding this comment.
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?
- 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
|
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. |
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. |

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
isCloudEnabledisfalsein self-hosted mode. Without the guard, thepage 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 guardpages/main/v1/managed-workers/create/index.tsx— added cloud guardTesting
Build the Vite app and launch Hatchet Lite per the steps in #3896.
Navigating to
/managed-workersand/managed-workers/createnowreturns a 404 page instead of the infinite loading screen.