Skip to content

fix(cron): wire auth profile env injection into isolated cron runner #2079

@alexey-pelykh

Description

@alexey-pelykh

Context

Cron isolated sessions fail with [CLI_ERROR] Not logged in · Please run /login because the cron runner doesn't inject auth credentials into the CLI environment.

The auto-reply path wraps CLI execution in withAuthKeyRetryresolveAuthEnv() (src/auto-reply/reply/agent-runner-execution.ts:375), which resolves the agent's auth profile to env vars (e.g., ANTHROPIC_API_KEY). The cron isolated runner creates ChannelBridge with only runtimeEnv from config (e.g., CLAUDE_CONFIG_DIR), missing the API key/token.

Root cause

Upstream added auth profile propagation to the cron runner in v2026.2.22 (91944ede4c), passing authProfileId to runEmbeddedPiAgent. During sync #2071 (v2026.2.25), this was skipped because it depends on src/agents/auth-profiles/session-override.ts (EXCLUDED — fork has its own src/auth/ module). The divergence note incorrectly assumed CLI runtimes handle their own auth.

See: hq/upstream/salvage-notes/cron-auth-profile-propagation.md

What

Wrap the ChannelBridge creation + execution in src/cron/isolated-agent/run.ts with withAuthKeyRetry, mirroring the auto-reply path pattern at agent-runner-execution.ts:376-402.

Implementation

In src/cron/isolated-agent/run.ts (~line 333), replace the direct ChannelBridge creation + bridge.handle() with:

import { withAuthKeyRetry } from "../../middleware/auth-key-retry.js";

runResult = await withAuthKeyRetry<AgentDeliveryResult>(
  {
    cfg: cfgWithAgentDefaults,
    agentId,
    baseEnv: resolveAgentRuntimeEnv(params.cfg, agentId),
  },
  async (runtimeEnv) => {
    const bridge = new ChannelBridge({
      provider: resolveAgentRuntimeOrThrow(params.cfg, agentId),
      sessionMap,
      gatewayUrl: resolveGatewayUrlFromConfig(cfgWithAgentDefaults),
      gatewayToken: resolveGatewayTokenFromConfig(cfgWithAgentDefaults),
      workspaceDir,
      runtimeArgs: resolveAgentRuntimeArgs(params.cfg, agentId),
      runtimeEnv,
    });
    return await bridge.handle(message, undefined, abortSignal);
  },
  (result) => result.error,
);

Key details:

  • baseEnv = resolveAgentRuntimeEnv() (preserves CLAUDE_CONFIG_DIR etc.)
  • withAuthKeyRetry merges baseEnv + resolved auth env, passes combined env to callback
  • With single profile: maxAttempts = 1, no retry — degenerates to resolve-once
  • With multiple profiles: rotates on isAuthRotatableError (rate-limit/auth failures)
  • Third arg (result) => result.error extracts error from non-thrown results for rotation check

Out of scope

Acceptance Criteria

  • Cron isolated sessions receive auth credentials via env injection
  • Auth profile rotation works for cron when multiple profiles configured (rate-limit → rotate to next profile)
  • Single-profile config: no retry, resolves once (verify maxAttempts = 1)
  • Existing runtimeEnv config (e.g., CLAUDE_CONFIG_DIR) is preserved in merged env
  • Tests updated; CI passes

Files

  • src/cron/isolated-agent/run.ts (primary — wrap bridge execution with withAuthKeyRetry)
  • src/middleware/auth-key-retry.ts (reference — existing retry/rotation logic)
  • src/auto-reply/reply/agent-runner-execution.ts:376-402 (reference — pattern to mirror)

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    gutRemoving dead upstream subsystems

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions