Skip to content

perf: move token refresh and user identification to a detached subprocess #132

@angeloashmore

Description

@angeloashmore

Problem

Every non-help, non-login command hangs after completing its main work because of a floating promise in src/index.ts:167-177:

refreshToken()
    .then(async (token) => {
        if (!token) return;
        const host = await getHost();
        const profile = await getProfile({ token, host });
        segmentIdentify({ shortId: profile.shortId, intercomHash: profile.intercomHash });
        sentrySetUser({ id: profile.shortId });
    })
    .catch(() => {});

This kicks off up to three HTTP requests (refresh token, get host, get profile) that aren't awaited but keep the Node process alive until the underlying fetch connections close. The command's output is already printed, but the process lingers.

Segment flush and the update notifier don't have this problem — they both use detached subprocesses (spawn + unref) that let the main process exit immediately.

Solution

Two changes:

1. Only refresh when the token is close to expiring

The auth token is a JWT valid for ~6.5 hours. Instead of refreshing on every command, decode the JWT (base64, no library needed), check the exp claim, and only refresh when there is 1 hour or less remaining. Most commands will skip the refresh entirely.

2. Move the token refresh to a detached subprocess

When a refresh is needed, move it to a detached subprocess matching the pattern used by Segment (src/lib/segment.ts:flushTelemetry) and the update notifier (src/lib/update-notifier.ts:spawnBackgroundCheck). The subprocess refreshes the token and writes it to disk — the main process exits immediately.

For user identification (segmentIdentify/sentrySetUser): attempt to fetch the profile using the current token in the main process. If the token is expired or invalid, skip identification — don't block on it. The next command will have a fresh token (written by the previous command's subprocess) and identification will succeed then.

This means the first command after a token expires won't have user identification in telemetry, but every subsequent command will. This is an acceptable tradeoff for not blocking process exit.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions