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:
@@ -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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user