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.
Problem
Every non-help, non-login command hangs after completing its main work because of a floating promise in
src/index.ts:167-177: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
fetchconnections 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
expclaim, 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.