Skip to content

P-1952 Handle race condition in wagmi and SDK initialization#160

Closed
yosriady wants to merge 1 commit intomainfrom
qa/wagmi-race
Closed

P-1952 Handle race condition in wagmi and SDK initialization#160
yosriady wants to merge 1 commit intomainfrom
qa/wagmi-race

Conversation

@yosriady
Copy link
Contributor

@yosriady yosriady commented Feb 18, 2026


Open with Devin

Note

Low Risk
Small, additive change limited to initialization logic; main risk is incorrect seeding if wagmi state access differs across wrappers, but it is guarded and logged.

Overview
Fixes a wagmi/SDK initialization race by seeding WagmiEventHandler tracking state from the current wagmi connection state on startup, so analytics events have address/chainId even when wagmi auto-reconnects before subscriptions are attached.

Adds a new initializeFromCurrentState() call in the constructor with guarded state reads and error logging; no changes to the existing subscription-based listeners beyond this startup initialization.

Written by Cursor Bugbot for commit 9cdd857. This will update automatically on new commits. Configure here.

Greptile Summary

Fixes a race condition where wagmi auto-reconnects before the SDK's subscribe() listeners are attached, leaving trackingState without address/chainId for subsequent analytics events.

  • Adds initializeFromCurrentState() in the WagmiEventHandler constructor that synchronously reads the current wagmi connection state and seeds trackingState if a wallet is already connected.
  • The method is well-guarded: try/catch prevents constructor failure, null checks on address prevent partial state, and it runs before setupConnectionListeners() to avoid any ordering issues.
  • No existing tests cover the new code path — the test mock defaults to a disconnected state at construction time, so the "already connected" branch is never exercised.

Confidence Score: 4/5

  • This PR is safe to merge — it adds a defensive initialization guard with no changes to existing subscription logic.
  • Score of 4 reflects a small, well-scoped change with proper error handling and guards. Deducted 1 point for the lack of unit test coverage on the new code path, which means the "already connected" initialization behavior is untested.
  • test/wagmi/WagmiEventHandler.spec.ts needs new test cases for the initializeFromCurrentState behavior

Important Files Changed

Filename Overview
src/wagmi/WagmiEventHandler.ts Adds initializeFromCurrentState() method to seed tracking state from current wagmi connection on startup, fixing a race condition where wagmi auto-reconnects before SDK subscriptions are attached. Change is well-guarded with try/catch and null checks, but lacks unit test coverage for the new code path.

Sequence Diagram

sequenceDiagram
    participant App as App/Wagmi
    participant SDK as WagmiEventHandler
    participant State as wagmiConfig.getState()
    participant Track as trackingState

    Note over App,SDK: SDK initialization (constructor)
    App->>SDK: new WagmiEventHandler(formo, wagmiConfig)
    
    Note over SDK: NEW: initializeFromCurrentState()
    SDK->>State: getState()
    State-->>SDK: { status: "connected", chainId, connections }
    SDK->>SDK: getConnectedAddress(state)
    SDK->>Track: seed lastAddress, lastChainId, lastStatus

    Note over SDK: setupConnectionListeners()
    SDK->>App: wagmiConfig.subscribe(status)
    SDK->>App: wagmiConfig.subscribe(chainId)

    Note over App,Track: Later: transaction/signature events
    App->>SDK: mutation event (sendTransaction)
    SDK->>Track: read lastAddress, lastChainId
    Note over SDK: address/chainId available ✓
Loading

Last reviewed commit: 9cdd857

Context used:

  • Rule from dashboard - Review and address all automated code review comments (like Copilot suggestions) before merging PRs,... (source)

@yosriady yosriady changed the title Handle race condition in wagmi and SDK initialization P-1952 Handle race condition in wagmi and SDK initialization Feb 18, 2026
@linear
Copy link

linear bot commented Feb 18, 2026

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +130 to +157
/**
* Seed trackingState if wagmi is already in a connected state.
* This ensures transaction/signature events have an address available
* even when the SDK initializes after wagmi auto-reconnects.
*/
private initializeFromCurrentState(): void {
try {
const state = this.getState();
if (state.status === "connected") {
const address = this.getConnectedAddress(state);
const chainId = state.chainId;
if (address) {
this.trackingState.lastAddress = address;
this.trackingState.lastChainId = chainId;
this.trackingState.lastStatus = "connected";
logger.info(
"WagmiEventHandler: Wallet already connected, seeded tracking state",
{ address, chainId }
);
}
}
} catch (error) {
logger.error(
"WagmiEventHandler: Error reading initial state:",
error
);
}
}
Copy link

Choose a reason for hiding this comment

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

Missing test coverage for new method

The new initializeFromCurrentState() method has no corresponding tests. The existing test setup initializes the mock wagmi config in a disconnected state (line 70 of the test file), so the state.status === "connected" branch in this method is never exercised.

Consider adding tests for:

  1. Constructor seeds trackingState when wagmi is already connected at init time — verifying lastAddress, lastChainId, and lastStatus are set.
  2. Constructor handles errors from getState() gracefully (doesn't throw).
  3. Constructor handles the case where state is "connected" but getConnectedAddress returns undefined (e.g., empty accounts).
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/wagmi/WagmiEventHandler.ts
Line: 130-157

Comment:
**Missing test coverage for new method**

The new `initializeFromCurrentState()` method has no corresponding tests. The existing test setup initializes the mock wagmi config in a `disconnected` state (line 70 of the test file), so the `state.status === "connected"` branch in this method is never exercised.

Consider adding tests for:
1. Constructor seeds `trackingState` when wagmi is already connected at init time — verifying `lastAddress`, `lastChainId`, and `lastStatus` are set.
2. Constructor handles errors from `getState()` gracefully (doesn't throw).
3. Constructor handles the case where state is "connected" but `getConnectedAddress` returns `undefined` (e.g., empty accounts).

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is ON, but it could not run because on-demand usage is turned off. To enable Bugbot Autofix, turn on on-demand usage and set a spend limit in the Cursor dashboard.

if (address) {
this.trackingState.lastAddress = address;
this.trackingState.lastChainId = chainId;
this.trackingState.lastStatus = "connected";
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing chainId validation in initial state seeding

Medium Severity

initializeFromCurrentState only checks if (address) before seeding tracking state, but handleStatusChange uses a stricter guard: if (address && chainId !== undefined). Since WagmiState.chainId is typed number | undefined, the new method can seed lastAddress while lastChainId remains undefined — a partially-seeded state that the existing connect handler was explicitly designed to prevent.

Additional Locations (1)

Fix in Cursor Fix in Web

@yosriady yosriady closed this Feb 18, 2026
@yosriady
Copy link
Contributor Author

how could the signature and transactions be missing

Two possible causes:

  1. queryClient not passed (most likely) — Both setupMutationTracking() and setupQueryTracking() are gated behind the if (this.queryClient) check at line 120. If the user's init looks like:

<FormoAnalyticsProvider
writeKey="..."
options={{ wagmi: { config: wagmiConfig } }}
/>
...then queryClient is undefined, neither setup runs, and the SDK silently falls back to only tracking connect/disconnect/chain events. The only signal is a logger.warn that they'd never see without debug logging enabled.

  1. No address available (the bug we just fixed) — Even with queryClient passed, if the wallet was already connected before the SDK initialized, trackingState.lastAddress was never set. When a mutation fires, the address resolution at line 615 falls through to undefined and the event is silently dropped at line 618.

For this user, it's almost certainly #1 since they see zero transaction events. If it were #2, they'd see transactions that happen after a manual reconnect but not on first page load. Zero transactions points to the mutation cache never being subscribed to at all.

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

Comments