Skip to content

fix(sessions): infer adapter from model id on local task start#2937

Open
ashish921998 wants to merge 4 commits into
PostHog:mainfrom
ashish921998:fix/local-codex-model-fallback
Open

fix(sessions): infer adapter from model id on local task start#2937
ashish921998 wants to merge 4 commits into
PostHog:mainfrom
ashish921998:fix/local-codex-model-fallback

Conversation

@ashish921998

Copy link
Copy Markdown

Problem

When starting a local task with a GPT/Codex model selected, the session always started with Claude Opus 4.8 instead of the selected model. Cloud tasks worked correctly.

Root Cause

The local agent process (createAcpConnection) defaults to "claude" when no adapter is supplied:

const adapterType = config.adapter ?? "claude";

In createNewLocalSession, the adapter and model were forwarded independently. When adapter was undefined (e.g. due to a race during adapter switching or a missing value), the local agent silently fell back to Claude regardless of the selected model. The model string (e.g. gpt-5.5) was then passed to the Claude session, which couldn't resolve it and fell back to DEFAULT_GATEWAY_MODEL (claude-opus-4-8).

Cloud tasks were unaffected because the PostHog server independently validates and enforces the adapter for the selected model.

Fix

Integrates inferAdapterFromModelId (maps gpt-*/o*/codex-* to "codex", claude-* to "claude") as a safety net in three places in sessionService.ts:

  1. createNewLocalSession — infers the adapter from the model id when the caller did not supply one. Also avoids passing the Claude default model when the resolved adapter is Codex (Codex resolves its own default server-side via Agent.run).

  2. reconnectToLocalSession — adds inferAdapterFromModelId as a third-tier fallback (after the explicit adapter and the stored adapter) using the persisted model from config options.

  3. clearSessionError — now accepts optional overrides for adapter/model/executionMode/reasoningLevel and recovers these from the errored session's adapter + configOptions when no overrides are given. Also moves teardownSession to after createNewLocalSession succeeds, so a failed retry preserves the session placeholder.

Testing

  • biome lint packages/core passes with zero noRestrictedImports violations
  • tsc --noEmit --skipLibCheck passes on all changed files
  • Unit tests in modelAdapter.test.ts and clearSessionError.test.ts verified against implementation logic

ashish921998 and others added 2 commits June 25, 2026 22:26
When starting a local task with a GPT/Codex model selected, the local agent silently defaulted to Claude Opus if the adapter was missing. This adds inferAdapterFromModelId as a safety net in createNewLocalSession, reconnectToLocalSession, and clearSessionError. Cloud tasks were unaffected because the server validates the adapter independently.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
@greptile-apps

greptile-apps Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "Merge branch 'main' into fix/local-codex..." | Re-trigger Greptile

Comment on lines +1 to +8
export type TaskModelAdapter = "claude" | "codex";

const CODEX_MODEL_PATTERNS = [/^gpt-/i, /^o\d/i, /^codex-/i];
const CLAUDE_MODEL_PATTERNS = [/^claude-/i];

export function inferAdapterFromModelId(
modelId: string | null | undefined,
): TaskModelAdapter | undefined {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 TaskModelAdapter is an exact duplicate of Adapter from @posthog/shared ("claude" | "codex"). Defining it separately violates OnceAndOnlyOnce — a future third adapter value would need to be added in two places, and callers in sessionService.ts already use Adapter as the expected type. Since @posthog/core already imports from @posthog/shared, the return type can use the canonical Adapter directly.

Suggested change
export type TaskModelAdapter = "claude" | "codex";
const CODEX_MODEL_PATTERNS = [/^gpt-/i, /^o\d/i, /^codex-/i];
const CLAUDE_MODEL_PATTERNS = [/^claude-/i];
export function inferAdapterFromModelId(
modelId: string | null | undefined,
): TaskModelAdapter | undefined {
import type { Adapter } from "@posthog/shared";
const CODEX_MODEL_PATTERNS = [/^gpt-/i, /^o\d/i, /^codex-/i];
const CLAUDE_MODEL_PATTERNS = [/^claude-/i];
export function inferAdapterFromModelId(
modelId: string | null | undefined,
): Adapter | undefined {

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines 3105 to 3115
await this.createNewLocalSession(
taskId,
taskTitle,
repoPath,
authStatus.auth,
initialPrompt,
recoveredMode as ExecutionMode | undefined,
recoveredAdapter,
recoveredModel,
recoveredReasoning,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The currentValue from getConfigOptionByCategory is typed as string, so casting it directly to ExecutionMode is unsafe — any persisted value that doesn't match the union (e.g. a stale or renamed mode) would pass the cast silently and reach agent.start.mutate as an invalid permissionMode. The same pattern used for reasoningLevel — accept it as string and let the downstream call validate or ignore it — is safer here too.

Suggested change
await this.createNewLocalSession(
taskId,
taskTitle,
repoPath,
authStatus.auth,
initialPrompt,
recoveredMode as ExecutionMode | undefined,
recoveredAdapter,
recoveredModel,
recoveredReasoning,
);
await this.createNewLocalSession(
taskId,
taskTitle,
repoPath,
authStatus.auth,
initialPrompt,
(recoveredMode as ExecutionMode | undefined) ?? undefined,
recoveredAdapter,
recoveredModel,
recoveredReasoning,
);

ashish921998 and others added 2 commits June 25, 2026 22:43
…ode recovery

Replace duplicate TaskModelAdapter with Adapter from @posthog/shared, and
use getCurrentModeFromConfigOptions instead of an unsafe ExecutionMode cast.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant