fix(coding-agent): rejected unknown continued session ids

Treated a UUID passed after --continue as an explicit session target so a missing id cannot fall back to the latest persisted session.

Signed-off-by: Christian Stewart <christian@aperture.us>
This commit is contained in:
Christian Stewart
2026-07-11 00:50:17 -07:00
parent e7955ddf3c
commit d7fe28bcbf
3 changed files with 80 additions and 1 deletions
+3
View File
@@ -67,6 +67,9 @@
- Fixed native Windows binary compatibility on older Windows 10 CPUs by building the `omp-windows-x64.exe` release asset with a baseline x64 runtime instead of AVX2. (#5172)
- Fixed `GenerateImage` rejecting OpenAI Codex-compatible proxy bearer keys when the token does not expose a `chatgpt-account-id`. (#5174)
- Fixed context promotion documentation to accurately reflect the `contextPromotionTarget` runtime behavior and `contextPromotion.enabled` default. (#5163)
### Fixed
- Fixed `--continue <session-id>` falling back to an unrelated latest session when the requested session does not exist.
## [16.4.3] - 2026-07-11
+15
View File
@@ -634,6 +634,14 @@ async function getChangelogForDisplay(parsed: Args): Promise<string | undefined>
return undefined;
}
const SESSION_ID_ARG_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
function positionalContinueSessionId(parsed: Args): string | undefined {
if (!parsed.continue || parsed.resume || parsed.fork || parsed.messages.length !== 1) return undefined;
const message = parsed.messages[0]?.trim();
return message && SESSION_ID_ARG_RE.test(message) ? message : undefined;
}
/** Resolves CLI session flags into an existing, forked, in-memory, or cancelled session manager. */
export async function createSessionManager(
parsed: Args,
@@ -663,6 +671,13 @@ export async function createSessionManager(
if (parsed.noSession) {
return SessionManager.inMemory();
}
const continueSessionArg = positionalContinueSessionId(parsed);
if (continueSessionArg) {
parsed.resume = continueSessionArg;
parsed.continue = false;
parsed.messages = [];
}
if (typeof parsed.resume === "string") {
const sessionArg = parsed.resume;
if (sessionArg.includes("/") || sessionArg.includes("\\") || sessionArg.endsWith(".jsonl")) {
@@ -6,14 +6,19 @@
* `[Uncaught Exception]`.
*/
import { describe, expect, it, vi } from "bun:test";
import * as fsp from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
import type { Args } from "@oh-my-pi/pi-coding-agent/cli/args";
import type { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
import { createSessionManager, SessionResolutionError, writeStartupNotice } from "@oh-my-pi/pi-coding-agent/main";
import * as sessionListingModule from "@oh-my-pi/pi-coding-agent/session/session-listing";
import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager";
function buildResumeArgs(resume: string): Args {
function buildResumeArgs(resume: string, sessionDir?: string): Args {
return {
resume,
sessionDir,
messages: [],
fileArgs: [],
unknownFlags: new Map(),
@@ -21,6 +26,17 @@ function buildResumeArgs(resume: string): Args {
};
}
function buildContinueArgs(message: string, sessionDir?: string): Args {
return {
continue: true,
sessionDir,
messages: [message],
fileArgs: [],
unknownFlags: new Map(),
unrecognizedFlags: [],
};
}
function buildForkArgs(fork: string, noSession = false): Args {
return {
fork,
@@ -107,6 +123,51 @@ describe("createSessionManager — missing session (#2084)", () => {
}
});
it("rejects --resume with unknown id instead of falling back to latest persisted session", async () => {
const cwd = await fsp.mkdtemp(path.join(os.tmpdir(), "omp-resume-unknown-id-"));
const sessionDir = path.join(cwd, "sessions");
const missingId = "019ea530-ffff-7000-8000-000000000000";
try {
const latest = SessionManager.create(cwd, sessionDir);
latest.appendMessage({ role: "user", content: "newer persisted session", timestamp: Date.now() });
await latest.rewriteEntries();
const latestSessionId = latest.getSessionId();
expect(latestSessionId).not.toBe(missingId);
await expect(
createSessionManager(buildResumeArgs(missingId, sessionDir), cwd, stubSettings),
).rejects.toMatchObject({
name: "SessionResolutionError",
message: `Session "${missingId}" not found.`,
hint: expect.stringContaining("omp --resume"),
});
} finally {
await fsp.rm(cwd, { recursive: true, force: true });
}
});
it("rejects --continue followed by an unknown session id instead of falling back to latest", async () => {
const cwd = await fsp.mkdtemp(path.join(os.tmpdir(), "omp-continue-unknown-id-"));
const sessionDir = path.join(cwd, "sessions");
const missingId = "019ea530-ffff-7000-8000-000000000000";
try {
const latest = SessionManager.create(cwd, sessionDir);
latest.appendMessage({ role: "user", content: "latest should not be resumed", timestamp: Date.now() });
await latest.rewriteEntries();
expect(latest.getSessionId()).not.toBe(missingId);
await expect(
createSessionManager(buildContinueArgs(missingId, sessionDir), cwd, stubSettings),
).rejects.toMatchObject({
name: "SessionResolutionError",
message: `Session "${missingId}" not found.`,
hint: expect.stringContaining("omp --resume"),
});
} finally {
await fsp.rm(cwd, { recursive: true, force: true });
}
});
it("rejects --fork with SessionResolutionError carrying a usage hint", async () => {
vi.spyOn(sessionListingModule, "resolveResumableSession").mockResolvedValue(undefined);
try {