Skip to content

Fix bootstrap#741

Merged
feruzm merged 3 commits into
developfrom
bug
Apr 6, 2026
Merged

Fix bootstrap#741
feruzm merged 3 commits into
developfrom
bug

Conversation

@feruzm
Copy link
Copy Markdown
Member

@feruzm feruzm commented Apr 6, 2026

Summary by CodeRabbit

  • Performance

    • Bootstrap process now has a 30s execution cap to avoid indefinite hangs.
    • Server-side prefetch operations are timeout-bounded to reduce slowdowns and improve page load.
  • Reliability

    • Timeouts produce clear HTTP error responses and ensure per-request query state is cleaned up even on failure.
  • Chores

    • Bumped @ecency/hive-tx dependency and published patch version updates.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 6, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5bc9c312-a7ae-4b4c-891b-83b13c53746c

📥 Commits

Reviewing files that changed from the base of the PR and between bbed1ce and 55ecded.

📒 Files selected for processing (4)
  • packages/sdk/CHANGELOG.md
  • packages/sdk/package.json
  • packages/wallets/CHANGELOG.md
  • packages/wallets/package.json
✅ Files skipped from review due to trivial changes (3)
  • packages/sdk/CHANGELOG.md
  • packages/wallets/package.json
  • packages/wallets/CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/sdk/package.json

📝 Walkthrough

Walkthrough

Adds a 30s hard timeout to the Mattermost bootstrap POST handler (returns 504 on expiry), moves bootstrap logic into a new handleBootstrap, ensures per-request QueryClient is constructed and cleared in finally, adds a server-only SSR prefetch timeout helper that resolves to undefined on expiry, and bumps @ecency/hive-tx + related package versions/changelogs.

Changes

Cohort / File(s) Summary
Bootstrap API Timeout & QueryClient cleanup
apps/web/src/app/api/mattermost/bootstrap/route.ts
Add BOOTSTRAP_TIMEOUT_MS, BootstrapTimeoutError, and handleBootstrap(req). POST now races handleBootstrap vs timeout and maps timeout → 504 JSON; other errors → 500. Construct per-request QueryClient before subscription fetch and clear() it in finally.
SSR Prefetch Timeout
apps/web/src/core/react-query/query-helpers.ts
Introduce SSR_PREFETCH_TIMEOUT_MS and withSsrTimeout that wraps prefetch promises only on server. prefetchQuery / prefetchInfiniteQuery await timeout-bounded prefetch; on timeout resolve to undefined and return existing cached data (or undefined).
App dependency bump
apps/web/package.json
Bump dependency @ecency/hive-tx ^7.1.1^7.2.0.
SDK package & changelog
packages/sdk/package.json, packages/sdk/CHANGELOG.md
Package version → 2.1.2; peerDependency @ecency/hive-tx ^7.1.1^7.2.0; add changelog entry "Fix bootstrap (#741)".
Wallets package & changelog
packages/wallets/package.json, packages/wallets/CHANGELOG.md
Package version → 3.0.2; update changelog to reflect dependency bump to @ecency/sdk 2.1.2.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant BootstrapAPI as "Bootstrap Handler"
    participant QueryClient
    participant Subscriptions as "Subscriptions Fetcher"
    participant Timer
    participant Response

    Client->>BootstrapAPI: POST /api/mattermost/bootstrap
    BootstrapAPI->>QueryClient: construct per-request QueryClient
    BootstrapAPI->>Timer: start 30s timeout race
    par Main execution
        BootstrapAPI->>Subscriptions: fetch subscriptions (uses QueryClient)
        Subscriptions-->>BootstrapAPI: data / error
        BootstrapAPI->>BootstrapAPI: perform bootstrap logic (handleBootstrap)
    and Timeout race
        Timer->>Timer: wait 30 seconds
    end

    alt Bootstrap completes
        BootstrapAPI->>QueryClient: clear/reset (finally)
        BootstrapAPI->>Response: return 200 with result
    else Timeout expires
        Timer->>BootstrapAPI: reject with BootstrapTimeoutError
        BootstrapAPI->>QueryClient: clear/reset (finally)
        BootstrapAPI->>Response: return 504 { error: "bootstrap timed out" }
    else Other error
        BootstrapAPI->>QueryClient: clear/reset (finally)
        BootstrapAPI->>Response: return 500 { error: <message> }
    end

    Response->>Client: HTTP response
Loading
sequenceDiagram
    participant Client
    participant Server as "SSR Server"
    participant ReactQuery as "React Query"
    participant Timer
    participant Cache as "Query Cache"

    Client->>Server: Request page (SSR)
    Server->>ReactQuery: prefetchQuery(key, fn)
    alt isServer true
        ReactQuery->>Timer: wrap prefetch with withSsrTimeout (SSR_PREFETCH_TIMEOUT_MS)
        par Prefetch execution
            ReactQuery->>ReactQuery: execute fetch function
            ReactQuery->>Cache: store result if completes
        and Timeout monitor
            Timer->>Timer: wait timeout
        end
        alt Prefetch completes
            ReactQuery->>Cache: data stored
        else Timeout expires
            Timer->>ReactQuery: resolve to undefined (no rejection)
        end
    else isServer false
        ReactQuery->>ReactQuery: call prefetch directly (no timeout)
    end
    ReactQuery->>Cache: getQueryData(key)
    Cache->>Server: return cached data or undefined
    Server->>Client: Rendered HTML
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Poem

🐰
I hopped through code with a ticking little clock,
Thirty seconds to boot, then I gently stop.
Prefetches blink out on SSR's quiet hill,
QueryClient swept clean — no crumbs left to fill,
A soft timeout hop, and the build stays still.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title "Fix bootstrap" is too generic and vague. While it relates to changes in the bootstrap route, it doesn't specify what the fix addresses or highlight the primary change. Consider a more descriptive title like "Add 30s timeout to Mattermost bootstrap endpoint" or "Fix bootstrap with timeout handling and QueryClient cleanup" to better convey the main changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bug

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
apps/web/src/app/api/mattermost/bootstrap/route.ts (3)

119-127: Consider disconnecting the QueryClient after use.

The per-request QueryClient is a good choice to avoid stale cache issues (as the comment explains). However, calling qc.clear() after use ensures any internal timers or subscriptions are cleaned up immediately rather than waiting for garbage collection.

♻️ Proposed fix
     let subscriptions: Subscription[] = [];
     try {
         const qc = new QueryClient();
-        subscriptions =
-          (await qc.fetchQuery(
-            getAccountSubscriptionsQueryOptions(username)
-          )) || [];
+        try {
+          subscriptions =
+            (await qc.fetchQuery(
+              getAccountSubscriptionsQueryOptions(username)
+            )) || [];
+        } finally {
+          qc.clear();
+        }
     } catch (error) {
         console.error("MM bootstrap: Unable to load Hive/Ecency subscriptions", error);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/api/mattermost/bootstrap/route.ts` around lines 119 - 127,
The QueryClient instance (qc) created to fetch subscriptions may leave
timers/subscriptions active; ensure you call qc.clear() after use to immediately
clean up internal state—wrap the fetchQuery call in a try/finally (or ensure in
both success and catch paths) so that qc.clear() is invoked regardless of
outcome; reference the QueryClient instance named qc used with
fetchQuery(getAccountSubscriptionsQueryOptions(username)) and clear it after
setting subscriptions.

47-60: Consider adding runtime validation for the request body.

Using any for the parsed body loses type safety. A runtime validation library like zod would provide both type inference and validation, reducing the risk of unexpected runtime errors from malformed requests.

// Example with zod
import { z } from "zod";

const BootstrapBodySchema = z.object({
  username: z.string().optional(),
  accessToken: z.string().optional(),
  refreshToken: z.string().optional(),
  displayName: z.string().optional(),
  communityTitle: z.string().optional(),
  community: z.string().optional(),
});

// Then in handleBootstrap:
const parsed = BootstrapBodySchema.safeParse(await req.json());
if (!parsed.success) {
  return NextResponse.json({ error: "invalid json" }, { status: 400 });
}
const body = parsed.data;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/api/mattermost/bootstrap/route.ts` around lines 47 - 60,
Replace the loose any parsing of req.json() with runtime validation: define a
BootstrapBodySchema (e.g., using zod) that lists optional string fields
username, accessToken, refreshToken, displayName, communityTitle, and community,
then call safeParse on the parsed JSON before using it; if validation fails
return the same 400 response, otherwise assign const body = parsed.data and
continue using body.username / body.accessToken / body.refreshToken /
body.displayName / body.communityTitle / body.community to ensure typed,
validated inputs for the bootstrap handler.

24-29: Timer is not cleared when handleBootstrap resolves first; consider cleanup.

When handleBootstrap completes before the 30-second timeout, the setTimeout remains scheduled until it fires. While the reject becomes a no-op on the settled promise, it's cleaner to clear the timer explicitly.

Additionally, more critically, when the timeout fires, handleBootstrap continues executing in the background—making Mattermost API calls whose results are discarded. This wastes server resources and could contribute to rate limiting.

♻️ Proposed fix with timer cleanup and optional AbortSignal
 export async function POST(req: Request) {
   try {
+    const controller = new AbortController();
+    let timeoutId: NodeJS.Timeout | undefined;
+    
+    const timeoutPromise = new Promise<never>((_, reject) => {
+      timeoutId = setTimeout(() => {
+        controller.abort();
+        reject(new BootstrapTimeoutError());
+      }, BOOTSTRAP_TIMEOUT_MS);
+    });
+    
+    try {
-    return await Promise.race([
-      handleBootstrap(req),
-      new Promise<never>((_, reject) =>
-        setTimeout(() => reject(new BootstrapTimeoutError()), BOOTSTRAP_TIMEOUT_MS)
-      )
-    ]);
+      return await Promise.race([
+        handleBootstrap(req, controller.signal),
+        timeoutPromise
+      ]);
+    } finally {
+      clearTimeout(timeoutId);
+    }
   } catch (error) {

Then pass the signal to handleBootstrap and propagate it to any fetch calls that support it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/api/mattermost/bootstrap/route.ts` around lines 24 - 29, The
current Promise.race usage leaves the timeout scheduled and doesn't stop
handleBootstrap when the timeout wins; change the pattern to create a timer via
setTimeout returning a handle, and clearTimeout(timer) when handleBootstrap
resolves or rejects to avoid dangling timers; also create an AbortController and
pass controller.signal into handleBootstrap (and update handleBootstrap to
accept an optional AbortSignal and forward it to any fetch/async calls) and call
controller.abort() when the timeout fires (i.e., when you reject with
BootstrapTimeoutError) so ongoing background work is canceled; reference
symbols: handleBootstrap, BootstrapTimeoutError, BOOTSTRAP_TIMEOUT_MS.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/web/src/app/api/mattermost/bootstrap/route.ts`:
- Around line 119-127: The QueryClient instance (qc) created to fetch
subscriptions may leave timers/subscriptions active; ensure you call qc.clear()
after use to immediately clean up internal state—wrap the fetchQuery call in a
try/finally (or ensure in both success and catch paths) so that qc.clear() is
invoked regardless of outcome; reference the QueryClient instance named qc used
with fetchQuery(getAccountSubscriptionsQueryOptions(username)) and clear it
after setting subscriptions.
- Around line 47-60: Replace the loose any parsing of req.json() with runtime
validation: define a BootstrapBodySchema (e.g., using zod) that lists optional
string fields username, accessToken, refreshToken, displayName, communityTitle,
and community, then call safeParse on the parsed JSON before using it; if
validation fails return the same 400 response, otherwise assign const body =
parsed.data and continue using body.username / body.accessToken /
body.refreshToken / body.displayName / body.communityTitle / body.community to
ensure typed, validated inputs for the bootstrap handler.
- Around line 24-29: The current Promise.race usage leaves the timeout scheduled
and doesn't stop handleBootstrap when the timeout wins; change the pattern to
create a timer via setTimeout returning a handle, and clearTimeout(timer) when
handleBootstrap resolves or rejects to avoid dangling timers; also create an
AbortController and pass controller.signal into handleBootstrap (and update
handleBootstrap to accept an optional AbortSignal and forward it to any
fetch/async calls) and call controller.abort() when the timeout fires (i.e.,
when you reject with BootstrapTimeoutError) so ongoing background work is
canceled; reference symbols: handleBootstrap, BootstrapTimeoutError,
BOOTSTRAP_TIMEOUT_MS.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 375a8dee-68e9-4800-aab4-3e78bf17e15f

📥 Commits

Reviewing files that changed from the base of the PR and between c067963 and f92cdda.

📒 Files selected for processing (2)
  • apps/web/src/app/api/mattermost/bootstrap/route.ts
  • apps/web/src/core/react-query/query-helpers.ts

@feruzm feruzm added patch Bug fixes and patches (1.0.0 → 1.0.1), add this only if any packages/ have patch changes in PR labels Apr 6, 2026
@feruzm feruzm merged commit cc26162 into develop Apr 6, 2026
1 check passed
@feruzm feruzm deleted the bug branch April 6, 2026 19:20
@coderabbitai coderabbitai Bot mentioned this pull request Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch Bug fixes and patches (1.0.0 → 1.0.1), add this only if any packages/ have patch changes in PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant