Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions src/codex/prompt-layers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export type { Ownership } from "./prompt-layers/toml-read";
import { activeConfigPath, activeStorePath, activeBaseVariantDir, journalPathFor, lockPathFor, type Paths } from "./prompt-layers/paths";
import { readFileOrNull, computeRevision, updateFingerprintField } from "./prompt-layers/revision";
import { normalizeBody, findInvalidCharacter, decodeBasicString } from "./prompt-layers/encoding";
import { rootArrayEntries, hasRootKey, rootLines, tableLines, boolInLines, inspectOwnership } from "./prompt-layers/toml-read";
import { rootArrayEntries, hasRootKey, rootLines, rootValue, tableLines, boolInLines, inspectOwnership } from "./prompt-layers/toml-read";
import { setRootBool, setRootString, setTableBool, setProjection, removeUnownedProjection } from "./prompt-layers/toml-edit";

/**
Expand Down Expand Up @@ -367,22 +367,25 @@ function readToggle(configBytes: string | null, id: ToggleId): ToggleState {

function readModelInstructionsFile(configBytes: string | null): string | null {
if (configBytes === null) return null;
const parsed = rootValue(configBytes, "model_instructions_file");
if (typeof parsed === "string") return parsed;
if (parsed === undefined) return null;
for (const line of rootLines(configBytes)) {
// Capture the whole literal INCLUDING its quotes and decode it, rather than
// returning the raw inner text. `setRootString` writes this key through
// `encodeBasicString`, which escapes backslashes, so on Windows the stored
// literal is "C:\\Users\\..." while the path is "C:\Users\...". Reading the
// inner text verbatim returned the doubled form: the round trip did not
// survive, `baseSelection` compared a doubled path against the real variant
// path and reported `external` for a variant this code had just selected.
// literal is "C:\\Users\\..." while the path is "C:\Users\...".
//
// `[^"]*` cannot span an escaped quote either. That is not a new limit -- it
// is the same one the writer's restricted escape set is built around, and
// `decodeBasicString` refuses anything outside it rather than guessing.
// Prefer Bun's TOML decoder above so externally-authored standard escapes
// remain present and external. This restricted scan is only a fallback for
// documents Bun cannot parse. If its narrow decoder refuses a literal,
// preserve that literal rather than treating the setting as absent.
const m = /^\s*model_instructions_file\s*=\s*("[^"]*")\s*(?:#.*)?$/.exec(line);
if (m) return decodeBasicString(m[1]!);
if (m) return decodeBasicString(m[1]!) ?? m[1]!;
}
return null;
// A present non-string value or unrecognised spelling fails closed. Only
// `undefined` above proves that the setting is absent.
return "<unreadable model_instructions_file>";
}

/** Variant ids are ours to generate, so they stay in one narrow shape. */
Expand Down
2 changes: 1 addition & 1 deletion src/codex/prompt-layers/toml-read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function rootArrayEntries(configBytes: string | null, key: string): strin
const PARSE_FAILED = Symbol("toml-parse-failed");

/** A root-scope value, `undefined` when the key is absent, `PARSE_FAILED` when the file will not parse. */
function rootValue(configBytes: string | null, key: string): unknown {
export function rootValue(configBytes: string | null, key: string): unknown {
if (configBytes === null) return undefined;
let parsed: unknown;
try {
Expand Down
13 changes: 13 additions & 0 deletions tests/codex-integration/codex-prompt-base-variants.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ describe("base variant selection", () => {
});
});

test("a hand-set key with standard TOML escapes stays external and cannot be overwritten", () => {
const config = 'model_instructions_file = "\\u002Fetc\\u002Fsomebody-elses.md"\n';
const paths = fixture(config);
expect(readPromptLayers(paths).baseSelection).toEqual({
kind: "external",
path: "/etc/somebody-elses.md",
});

const result = selectBaseVariant({ kind: "default" }, rev(paths), paths);
expect(result).toMatchObject({ ok: false, error: "developer_instructions_not_owned" });
expect(read(paths.configPath)).toBe(config);
});

test("selecting a variant writes an absolute path, and the default removes the key", () => {
const paths = fixture("model = \"x\"\n");
const created = writeBaseVariant({ id: null, title: "Terse", body: "Be brief." }, rev(paths), paths);
Expand Down
Loading