summaryrefslogtreecommitdiffhomepage
path: root/packages/api
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-29 23:14:55 +0900
committerAdam Malczewski <[email protected]>2026-05-29 23:14:55 +0900
commit497b397e873f96d6fde3d8a44b3318e1ee1cbef4 (patch)
tree3ae632fbe445f881e49fbfd6f490394fc8ed7e9e /packages/api
parentc8e76ef506da32884ccf9ea2ac83a4d344c62943 (diff)
downloaddispatch-497b397e873f96d6fde3d8a44b3318e1ee1cbef4.tar.gz
dispatch-497b397e873f96d6fde3d8a44b3318e1ee1cbef4.zip
fix(claude): eliminate /home mount race that blanks Claude credentials at boot
On hosts where /home is a separate filesystem, the dispatch-api service could start before /home was mounted. The API's first DB access then failed (EACCES: mkdir '/home/tradam'), Claude account discovery silently caught the error and left claudeAccounts empty, and -- because discovery only ran in the constructor -- it stayed empty for the whole process lifetime. Every Claude message then fell back to the deepseek-v4-flash / empty-key defaults, producing a 401 'Missing API key' from OpenCode Zen. Fixes: - s6 run script waits (capped ~30s) for /home/tradam before exec'ing bun; passes instantly where /home is on the root filesystem. - systemd unit gains RequiresMountsFor=/home and After=...home.mount. - agent-manager re-runs _refreshClaudeAccounts() on config hot-reload and lazily on an empty cache in the Anthropic path, so a process that lost the boot race self-heals on the next request instead of staying broken.
Diffstat (limited to 'packages/api')
-rw-r--r--packages/api/src/agent-manager.ts16
1 files changed, 15 insertions, 1 deletions
diff --git a/packages/api/src/agent-manager.ts b/packages/api/src/agent-manager.ts
index c09a607..c873388 100644
--- a/packages/api/src/agent-manager.ts
+++ b/packages/api/src/agent-manager.ts
@@ -244,6 +244,10 @@ export class AgentManager {
}
// Update model registry with new config
this._initModelRegistry(newConfig);
+ // Re-discover Claude accounts: a config reload may accompany freshly
+ // imported credentials, and (critically) lets a process that failed
+ // account discovery at boot recover without a full restart.
+ this._refreshClaudeAccounts();
// Invalidate cached agents so next message uses updated config
for (const tabAgent of this.tabAgents.values()) {
tabAgent.agent = null;
@@ -575,11 +579,21 @@ export class AgentManager {
if (key.provider === "anthropic") {
// Anthropic provider: resolve credentials from Claude accounts
const credFile = key.credentials_file;
- const account =
+ const findAccount = () =>
this.claudeAccounts.find((a) => a.id === effectiveKeyId) ??
(credFile
? this.claudeAccounts.find((a) => a.source === credFile)
: this.claudeAccounts[0]);
+ let account = findAccount();
+ // Self-heal: account discovery runs once at construction and can
+ // fail at boot (e.g. the data dir isn't mounted yet and
+ // getDatabase() throws EACCES), leaving claudeAccounts empty for
+ // the process lifetime. If the lookup fails, re-run discovery now
+ // that the DB is reachable and retry before giving up.
+ if (!account) {
+ this._refreshClaudeAccounts();
+ account = findAccount();
+ }
if (account) {
const creds = refreshAccountCredentials(account);
if (creds && creds.expiresAt > Date.now() + 60_000) {