Skip to content

fix(ssr): harden credential query-key factory + fetchers against the 'use client' stub bug#5206

Merged
waleedlatif1 merged 2 commits into
stagingfrom
fix/credential-keys-ssr-hardening
Jun 25, 2026
Merged

fix(ssr): harden credential query-key factory + fetchers against the 'use client' stub bug#5206
waleedlatif1 merged 2 commits into
stagingfrom
fix/credential-keys-ssr-hardening

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

Preventive hardening for the same bug class that crashed the tables page (#5204): a server-evaluated module importing a runtime value from a 'use client' module resolves to a client-reference stub on the server, so calling it throws (X is not a function). A comprehensive audit found no other live crashes, but two latent fragilities of this class — fixed here so they can't fire:

  1. blocks/blocks/credential.ts (block defs are evaluated server-side) imported workspaceCredentialKeys + fetchWorkspaceCredentialList from the 'use client' hooks/queries/credentials.ts.
  2. lib/workflows/comparison/resolve-values.ts (reached server-side via the comparison barrel) imported fetchCredentialSetById from the 'use client' hooks/queries/credential-sets.ts.

Both are only called from browser paths today, so neither crashes — but the top-level imports are stubs on the server, waiting to fire.

Fix

Extract the affected primitives into non-'use client' modules, mirroring the existing folder-keys.ts / fetch-workflow-envelope.ts precedents, and import from there everywhere:

  • hooks/queries/utils/credential-keys.tsworkspaceCredentialKeys
  • hooks/queries/utils/fetch-workspace-credentials.tsfetchWorkspaceCredentialList
  • hooks/queries/utils/fetch-credential-set.tsfetchCredentialSetById

credentials.ts / credential-sets.ts now import these back (used by their hooks); all consumers (block, invitations, organization, resolve-values, + the test mock) point at the new modules. No behavior change.

Type of Change

  • Bug fix (preventive SSR hardening)

Testing

  • tsc 0 errors (confirms every importer was updated), Biome clean, check:react-query passes.
  • CI "Test and Build" runs the full suite + next build (the gate that actually exercises the SSR boundary).

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jun 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jun 25, 2026 1:39am

Request Review

@cursor

cursor Bot commented Jun 25, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Import-path refactor with no API or data changes; reduces SSR risk and adds static enforcement for server surfaces (blocks, prefetch, routes, triggers).

Overview
Prevents the same SSR crash class as the tables-page bug: server code that calls exports from 'use client' query modules gets client-reference stubs (e.g. workspaceCredentialKeys.list is not a function).

Refactor: Moves workspaceCredentialKeys, fetchWorkspaceCredentialList, and fetchCredentialSetById into non-'use client' modules under hooks/queries/utils/ (credential-keys.ts, fetch-workspace-credentials.ts, fetch-credential-set.ts). Client hooks re-import them; server paths (blocks/blocks/credential.ts, lib/workflows/comparison/resolve-values.ts, cache invalidation in invitations/organization/secrets) import the utils directly. No runtime behavior change.

Guardrails: Adds scripts/check-client-boundary-imports.ts and wires bun run check:client-boundary into CI and documents the rule in sim-architecture.md / sim-queries.md.

Reviewed by Cursor Bugbot for commit f2ccd0b. Configure here.

@greptile-apps

greptile-apps Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR hardens the SSR boundary by extracting credential query-key factories and standalone fetchers out of 'use client' modules into three new non-client utility modules, preventing latent crashes where server-evaluated code (block definitions, comparison helpers) would call a client-reference stub. No runtime behavior changes; the fix mirrors the existing folder-keys.ts / fetch-workflow-envelope.ts pattern already in the codebase.

  • New utility modules (credential-keys.ts, fetch-credential-set.ts, fetch-workspace-credentials.ts) hold the moved primitives; the original 'use client' hook files re-import them, preserving their existing API surface for client consumers.
  • All known callers updated: blocks/blocks/credential.ts, lib/workflows/comparison/resolve-values.ts, hooks/queries/invitations.ts, hooks/queries/organization.ts, app/.../secrets-manager.tsx, and the relevant test mock now import from the new paths.
  • New CI guard (scripts/check-client-boundary-imports.ts) added as check:client-boundary and wired into the test-build.yml workflow to statically flag any future value import from a 'use client' module in server-surface files (blocks/, prefetch*, app/api/**/route.ts, triggers/).

Confidence Score: 5/5

Safe to merge — purely a structural refactor with no behavior changes and full consumer coverage confirmed by tsc and the new CI guard.

The change extracts three well-scoped primitives into standalone non-client modules and updates every known consumer. The new utility files are exact replicas of what was removed, the 'use client' hook files re-import them so their own APIs are unchanged, and the CI guard closes the regression vector going forward. No logic is altered.

No files require special attention. The CI guard script covers direct server-surface imports; transitive paths through library files remain outside its scope by design, consistent with the project's existing boundary checks.

Important Files Changed

Filename Overview
scripts/check-client-boundary-imports.ts New CI guard that statically detects value imports from 'use client' modules in server-surface files; import parsing, directive detection, specifier resolution, and escape hatch logic are all correct.
apps/sim/hooks/queries/utils/credential-keys.ts New non-client key factory for workspace credentials; identical logic to what was in credentials.ts, no 'use client' directive, safe to import from server-evaluated modules.
apps/sim/hooks/queries/utils/fetch-credential-set.ts New non-client fetcher extracted from credential-sets.ts; identical implementation, allows server-reachable resolve-values.ts to import it safely.
apps/sim/hooks/queries/utils/fetch-workspace-credentials.ts New non-client fetcher extracted from credentials.ts; matches the pattern of the existing fetch-workflow-envelope.ts utility.
apps/sim/blocks/blocks/credential.ts Splits previously combined import from 'use client' credentials.ts into two separate safe-module imports; fixes the primary latent SSR crash vector in block definitions.
apps/sim/lib/workflows/comparison/resolve-values.ts Re-pointed fetchCredentialSetById to the new non-client utility module; fixes the second latent SSR crash path in the comparison barrel.
apps/sim/hooks/queries/credentials.ts Removed workspaceCredentialKeys and fetchWorkspaceCredentialList definitions; now imports them from the new utility modules. Public hook API is unchanged.
apps/sim/hooks/queries/credential-sets.ts Removed fetchCredentialSetById definition and getCredentialSetContract import; now imports fetchCredentialSetById from the new utility module.
apps/sim/app/workspace/[workspaceId]/settings/components/secrets/components/secrets-manager/secrets-manager.tsx Updated workspaceCredentialKeys import to point to the new non-client utility module; addresses the build break from the previous review cycle.
.github/workflows/test-build.yml Adds check:client-boundary step to CI pipeline alongside the existing React Query pattern audit; correctly positioned before the realtime prune check.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    subgraph Server["Server-evaluated (no 'use client')"]
        A["blocks/blocks/credential.ts"]
        B["lib/workflows/comparison/resolve-values.ts"]
    end

    subgraph Utils["hooks/queries/utils/ (no 'use client')"]
        C["credential-keys.ts\n(workspaceCredentialKeys)"]
        D["fetch-workspace-credentials.ts\n(fetchWorkspaceCredentialList)"]
        E["fetch-credential-set.ts\n(fetchCredentialSetById)"]
    end

    subgraph Client["'use client' hooks"]
        F["hooks/queries/credentials.ts"]
        G["hooks/queries/credential-sets.ts"]
    end

    subgraph Consumers["Other client consumers"]
        H["invitations.ts"]
        I["organization.ts"]
        J["secrets-manager.tsx"]
    end

    A -->|now imports| C
    A -->|now imports| D
    B -->|now imports| E

    F -->|re-imports| C
    F -->|re-imports| D
    G -->|re-imports| E

    H -->|now imports| C
    I -->|now imports| C
    J -->|now imports| C

    style Server fill:#ffe0e0
    style Utils fill:#e0ffe0
    style Client fill:#e0e8ff
    style Consumers fill:#fff8e0
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    subgraph Server["Server-evaluated (no 'use client')"]
        A["blocks/blocks/credential.ts"]
        B["lib/workflows/comparison/resolve-values.ts"]
    end

    subgraph Utils["hooks/queries/utils/ (no 'use client')"]
        C["credential-keys.ts\n(workspaceCredentialKeys)"]
        D["fetch-workspace-credentials.ts\n(fetchWorkspaceCredentialList)"]
        E["fetch-credential-set.ts\n(fetchCredentialSetById)"]
    end

    subgraph Client["'use client' hooks"]
        F["hooks/queries/credentials.ts"]
        G["hooks/queries/credential-sets.ts"]
    end

    subgraph Consumers["Other client consumers"]
        H["invitations.ts"]
        I["organization.ts"]
        J["secrets-manager.tsx"]
    end

    A -->|now imports| C
    A -->|now imports| D
    B -->|now imports| E

    F -->|re-imports| C
    F -->|re-imports| D
    G -->|re-imports| E

    H -->|now imports| C
    I -->|now imports| C
    J -->|now imports| C

    style Server fill:#ffe0e0
    style Utils fill:#e0ffe0
    style Client fill:#e0e8ff
    style Consumers fill:#fff8e0
Loading

Reviews (3): Last reviewed commit: "docs+ci: codify the 'use client' server-..." | Re-trigger Greptile

Comment thread apps/sim/hooks/queries/credentials.ts
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

@waleedlatif1 waleedlatif1 requested a review from a team as a code owner June 25, 2026 01:31
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit aaaf238. Configure here.

…modules

Preventively closes the same 'use client' SSR client-reference-stub class that
crashed the tables page. Server-evaluated modules (the credential block def, the
workflow-comparison helpers) imported workspaceCredentialKeys /
fetchWorkspaceCredentialList / fetchCredentialSetById from 'use client' hook
modules, where they resolve to client-reference stubs on the server (a future
server call path would throw 'X is not a function').

Extract them into non-client hooks/queries/utils/{credential-keys,
fetch-workspace-credentials,fetch-credential-set}.ts (mirroring folder-keys.ts /
fetch-workflow-envelope.ts) and import from there. No behavior change — these
values were only ever called from browser paths.
…t-boundary

Document the Next.js rule that server code can only render a 'use client'
export as a component, never call it (server imports resolve to client-reference
stubs that throw — the tables-page crash). Add the rule to
.claude/rules/sim-queries.md + a cross-ref in sim-architecture.md.

Add scripts/check-client-boundary-imports.ts (wired into CI as check:client-boundary)
that flags any value import from a 'use client' module in a server-evaluated,
non-JSX surface (prefetch / route handler / trigger / block definition), so this
class can't silently recur. Escape hatch: // client-boundary-allow: <reason>.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit f2ccd0b. Configure here.

@waleedlatif1 waleedlatif1 merged commit 6260eda into staging Jun 25, 2026
16 checks passed
@waleedlatif1 waleedlatif1 deleted the fix/credential-keys-ssr-hardening branch June 25, 2026 01:46
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