Once a Managed Auth connection is AUTHENTICATED, Kernel runs periodic health checks. For eligible flows, Kernel can automatically reauthenticate the session to keep it valid. This page covers the runtime lifecycle, configuration options, and recovery steps.
After the initial login, every connection moves through this loop:
On a configurable cadence, Kernel spins up a browser with the profile and verifies the session. A check reaches one of three conclusions: still logged in, definitely logged out, or **inconclusive** — the page didn't prove either way.A check needs concrete evidence to conclude. If the site returns a challenge page, times out, partially loads, or shows something ambiguous, the result is inconclusive and the connection is left exactly as it was. Nothing happens until the next check.
An inconclusive check never triggers reauth. Kernel would rather check again on the next cycle than log in again unnecessarily — a spurious login can trip risk checks on the site, prompt a device-verification email, or invalidate a working session.
Health checks run on a configurable interval. Your plan sets the minimum:
| Plan | Minimum interval |
|---|---|
| Hobbyist | 1 hour |
| Start-Up | 20 minutes |
| Enterprise | Fully configurable (as low as 5 minutes) |
You can raise the interval above your plan's minimum, but not below it. Update it with health_check_interval (in seconds) — changes take effect immediately, so the next check uses the new value:
await kernel.auth.connections.update(
auth.id,
health_check_interval=1800, # 30 minutes
)_, err := client.Auth.Connections.Update(ctx, auth.ID, kernel.AuthConnectionUpdateParams{
ManagedAuthUpdateRequest: kernel.ManagedAuthUpdateRequestParam{
HealthCheckInterval: kernel.Int(1800), // 30 minutes
},
})
if err != nil {
panic(err)
}For an eligible connection, Kernel continues reauthenticating when every health check finds the session expired. A successful login resets the automatic reauthentication state, so a site whose session TTL is shorter than your health check interval is reauthenticated on every cycle.
If you're seeing the connection flip to NEEDS_AUTH frequently and want shorter detection windows, lower health_check_interval toward your plan's minimum.
Check the can_reauth boolean on a connection. It describes eligibility to attempt reauthentication, not whether the next login will succeed. It's true only when all of these conditions hold:
- A previous login succeeded — Kernel has recorded the result of a completed login.
- A credential is linked — It's stored in Kernel or sourced from 1Password.
- No blocking requirement is recorded — Kernel hasn't recorded a requirement that prevents an automatic attempt.
Use can_reauth_reason to see why the connection is or isn't eligible. See Get auth connection for the possible values.
can_reauth reflects what Kernel knows about the previous login. A site can introduce a new requirement during a later attempt. If Kernel needs input, a choice, or approval, the connection moves to NEEDS_AUTH so you can start a new login.
After a successful login, Kernel saves the login flow. If a later attempt needs input, a choice, or approval, the connection moves to NEEDS_AUTH instead of guessing how to proceed.
You can handle these flows in two ways:
- Switch to TOTP — if the site supports authenticator apps, add a
totp_secretto your credential. KERNEL generates codes on demand, removing the need to manually provide that authenticator code. this doesn't eliminate other challenges or guarantee unattended reauthentication. if a code expires before the site accepts it, KERNEL retries with a fresh one. - Trigger manual re-auth — Start a new login session and route the user through the Hosted UI or Programmatic flow.
Call .login() immediately before work that requires an authenticated session. Use this as a pre-task authentication check, not a replacement for the normal login flow. Don't rely only on the connection's current status: it reflects the latest completed health check, and the website session can expire after that check.
For a connection with a previous successful login and saved auth check URL, .login() runs the verifier first. If the profile is still signed in, the flow reaches SUCCESS without submitting anything. If it isn't, KERNEL starts the normal login flow and uses saved credentials when available.
Follow the connection until the flow reaches SUCCESS, then create the browser or continue the task. If the flow reaches AWAITING_INPUT or AWAITING_EXTERNAL_ACTION, stop the pre-task helper and send the user to the hosted_url returned by this .login() call. Continue that login flow; don't call .login() again, because a new call cancels the flow already in progress.
Use this pattern whether periodic health checks are enabled or disabled:
```typescript TypeScript async function ensureAuthenticated(connectionId: string) { const login = await kernel.auth.connections.login(connectionId); const events = await kernel.auth.connections.follow(connectionId);for await (const event of events) { if (event.event !== 'managed_auth_state') continue;
if (event.flow_status === 'SUCCESS') return;
if (
event.flow_step === 'AWAITING_INPUT' ||
event.flow_step === 'AWAITING_EXTERNAL_ACTION'
) {
throw new Error(`user action required at ${login.hosted_url}`);
}
}
throw new Error('authentication did not complete successfully'); }
await ensureAuthenticated(auth.id);
const kernelBrowser = await kernel.browsers.create({ profile: { name: 'github-profile' }, });
```python Python
async def ensure_authenticated(connection_id: str):
login = await kernel.auth.connections.login(connection_id)
events = await kernel.auth.connections.follow(connection_id)
async for event in events:
if event.event != "managed_auth_state":
continue
if event.flow_status == "SUCCESS":
return
if event.flow_step in {"AWAITING_INPUT", "AWAITING_EXTERNAL_ACTION"}:
raise RuntimeError(f"user action required at {login.hosted_url}")
raise RuntimeError("authentication did not complete successfully")
await ensure_authenticated(auth.id)
kernel_browser = await kernel.browsers.create(
profile={"name": "github-profile"},
)
login, err := client.Auth.Connections.Login(ctx, auth.ID, kernel.AuthConnectionLoginParams{})
if err != nil {
panic(err)
}
events := client.Auth.Connections.FollowStreaming(ctx, auth.ID)
authenticated := false
for events.Next() {
event := events.Current()
if event.Event != "managed_auth_state" {
continue
}
if event.FlowStatus == "SUCCESS" {
authenticated = true
break
}
if event.FlowStep == "AWAITING_INPUT" || event.FlowStep == "AWAITING_EXTERNAL_ACTION" {
panic("user action required at " + login.HostedURL)
}
}
if err := events.Err(); err != nil {
panic(err)
}
if !authenticated {
panic("authentication did not complete successfully")
}
kernelBrowser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{
Profile: shared.BrowserProfileParam{
Name: kernel.String("github-profile"),
},
})
if err != nil {
panic(err)
}
_ = kernelBrowserIf a login attempt fails — whether triggered by a health check, an auto-reauth, or a manual .login() — the flow is marked FAILED and the event includes error_code and error_message.
How much Kernel retries depends on the failure. A transient site problem (a 5xx page, a maintenance screen) is retried once against the login page before giving up. A conclusive rejection by the site — wrong credentials, a locked account, an unsupported method — is not retried, because retrying would burn attempts against a lockout or fail identically.
Common codes:
See the API reference for the full list.
credentials_invalid— Update the linked credential and call.login()to re-run the flow. When the site identifies which field it rejected during an interactive login, Kernel asks for a corrected value in place — see replacing a rejected credential.totp_code_rejected— Retry with a code from a new TOTP window. If independently generated codes keep failing, reconnect the account and update its TOTP secret. One rejected code does not prove that the saved secret is stale.totp_required/sms_code_required/email_code_required— Start an interactive login and provide the requested code. Add a TOTP secret to the linked credential to make future authenticator-code challenges automatic.account_choice_required/customer_input_required/external_action_required— Start an interactive login and complete the choice, field, or external approval. Kernel does not guess an identity or trigger notification-producing steps during unattended reauth.account_locked— Unlock the account with the site directly. Calling.login()again before that will not help and may extend the lockout.rate_limited— Wait for the site's retry window before starting another login.bot_detected/captcha_blocked— Pin the connection to a cleaner proxy (ISP or custom). For aggressive sites, also enable stealth and review the bot detection guide.unsupported_auth_method— Switch the account to a supported sign-in method (e.g. password + TOTP instead of a passkey) and re-link the credential.website_error— Usually the site, not the connection. Retry later; if it persists, confirm the connection'slogin_urlstill points at a working login page.
Two tools handle most investigations:
-
Dashboard live view — The Browser Sessions tab in the Kernel dashboard shows every auth browser session (logins, health checks, reauths) with a live view. Watch a session in real time to see exactly where it's getting stuck.
-
Session recordings — To record only the next single login attempt without recording subsequent health checks and reauths, pass
record_session: trueon.login():
login = await kernel.auth.connections.login(
auth.id,
record_session=True,
)login, err := client.Auth.Connections.Login(ctx, auth.ID, kernel.AuthConnectionLoginParams{
RecordSession: kernel.Bool(true),
})
if err != nil {
panic(err)
}
_ = loginTo record every auth session on the connection (logins, health checks, and reauths), set record_session: true connection-wide — see Record Sessions for Debugging.
- Connection Configuration —
health_check_interval,proxy,record_session, and other shared options - Credentials — what gets stored and how it powers auto-reauth
- FAQ — quick answers to common questions
