#7350: fix(cron): pass agentId and AccountId through heartbeat chain for multi-agent routing
gateway
agents
Cluster:
Cron Session Enhancements
## Summary
Three fixes for multi-agent cron/heartbeat routing:
- **Pass agentId + forcedByCron through wakeMode=now heartbeat chain** — `timer.ts` called `runHeartbeatOnce` without the job's `agentId`, and `server-cron.ts` dropped it. Non-default agents were either skipped by `isHeartbeatEnabledForAgent` or ran as the default agent. Added `forcedByCron` flag to bypass the enabled check for cron-triggered wakes.
- **Sync Claude CLI credentials bidirectionally** — Only Qwen CLI credentials were synced. When Claude Code independently refreshed its OAuth token, the old refresh token became invalid (`invalid_grant` failures). Now `syncExternalCliCredentials()` reads from `~/.claude/.credentials.json`, and `refreshOAuthTokenWithLock()` writes back after refresh.
- **Pass AccountId in heartbeat context** — When a cron job fires for a non-default agent, the heartbeat context was missing `AccountId`. This caused tool calls (e.g. `message send`) to fall back to the default Telegram account instead of the agent's own account.
## Test plan
- [ ] Cron job for non-default agent correctly passes `agentId` through to `runHeartbeatOnce`
- [ ] Non-default agents without explicit heartbeat config can still be woken by cron (`forcedByCron` bypass)
- [ ] Heartbeat tool calls use the correct channel account (not default)
- [ ] Claude CLI credential sync works bidirectionally (refresh in either direction keeps both stores in sync)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR tightens multi-agent cron/heartbeat routing by threading `agentId` through the wakeMode=now chain (timer → gateway cron service → heartbeat runner) and adding a `forcedByCron` flag to allow cron-triggered wakes even when an agent isn’t enabled for scheduled heartbeats. It also extends external CLI auth synchronization to include Claude CLI, and writes refreshed OAuth credentials back to the Claude CLI credential store to avoid `invalid_grant` from stale refresh tokens.
Key touched areas:
- Cron service types + timer execution now pass `{ agentId, forcedByCron }` into `runHeartbeatOnce`.
- Gateway cron service forwards those options into the infra heartbeat runner.
- Heartbeat runner uses `forcedByCron` to bypass agent-enabled/interval gating and passes `AccountId` into the heartbeat context so tool calls route via the correct channel account.
- Auth profile sync now reads Claude CLI creds and OAuth refresh writes them back.
Issues flagged focus on keeping the Claude CLI write-back robust and preventing crashes in the new Claude token-sync logging path.
<h3>Confidence Score: 4/5</h3>
- This PR looks safe to merge with minor robustness fixes recommended.
- Changes are localized and align with the stated routing/auth-sync goals, but there are two edge-case correctness issues: (1) Claude CLI write-back gating uses the pre-refresh provider value and can skip syncing after a successful refresh, and (2) the new Claude token-sync logging can throw if token credentials omit `expires`.
- src/agents/auth-profiles/oauth.ts, src/agents/auth-profiles/external-cli-sync.ts
<!-- greptile_other_comments_section -->
<sub>(5/5) You can turn off certain types of comments like style [here](https://app.greptile.com/review/github)!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#19385: fix: pass authProfileId from cron session to runEmbeddedPiAgent
by gigi-trifle · 2026-02-17
81.6%
#6522: fix(cron): deliver original message when agent response is heartbea...
by sidmohan0 · 2026-02-01
79.7%
#2123: fix(auth): sync from Claude CLI keychain before OAuth refresh
by jorge123255 · 2026-01-26
79.0%
#22707: fix: pass agentDir to runEmbeddedPiAgent in cron isolated sessions
by mrlerner · 2026-02-21
78.9%
#12310: cron: pass agentDir to embedded runner for isolated sessions
by magendary · 2026-02-09
78.6%
#16303: fix: apply cron payload.model override to session entry and pass au...
by superlowburn · 2026-02-14
78.1%
#6007: fix(cron): auto-map agentId to accountId for Discord deliveries
by dwfinkelstein · 2026-02-01
77.6%
#8097: fix: auto-convert one-shot reminders for reliable delivery
by Gerrald12312 · 2026-02-03
77.6%
#21014: fix(cron): suppress main-session summary for HEARTBEAT_OK responses
by nickjlamb · 2026-02-19
77.6%
#8307: fix(cron): improve tool description with reliable reminder guidance
by vishaltandale00 · 2026-02-03
77.6%