fix(code-queue): preflight runner skills availability

fix(code-queue): preflight runner skills availability
This commit is contained in:
Lyon
2026-05-22 23:33:02 +08:00
committed by GitHub
8 changed files with 340 additions and 68 deletions
+13
View File
@@ -163,6 +163,19 @@ bun scripts/code-queue-pr-preflight-example.ts --repo pikasTech/unidesk --base m
该脚本只读调用 `gh auth status`,并执行 `gh pr create --dry-run``gh pr comment --dry-run`。它检查当前 shell 的 `GH_TOKEN/GITHUB_TOKEN` 是否存在、GitHub REST egress 是否可达、repo 是否可见,并且只输出 token 来源和存在性,不输出 token 值。它不能证明 Code Queue default scheduler 已注入 token;跨 queue 派单 admission 应使用 `codex pr-preflight`
### Runner Skill 可用性
D601 Code Queue runner 的长期 skills source of truth 是宿主 `/home/ubuntu/.agents/skills`,生产和 dev Code Queue Pod 都必须只读挂载到容器内 `/root/.agents/skills`,并设置 `UNIDESK_SKILLS_PATH=/root/.agents/skills`。不要使用或传播任何拼错的 skills 路径;诊断中只能把这类问题作为 forbidden path risk 暴露。
执行面 `/health``/api/dev-ready``/api/runtime-preflight` 必须输出同一份只读 skill availability report。稳定字段包括 `source``target``requiredSkills``missingSkills``degraded``blocker``valuesPrinted=false``requiredSkills` 至少覆盖 `docs-spec``cli-spec``frontend-design``playwright-cli`;如果目标目录缺失、不是只读挂载、必需 skill 缺失,或拼写错误路径存在,报告必须显示 `ok=false` 和结构化 `blocker`,不能把 runner 能力缺口伪装成业务任务失败。
受控加载路径是更新宿主 `/home/ubuntu/.agents/skills` 后让 Code Queue Pod 通过 hostPath 读取;在线热更新只能作为临时 runbook,长期验收必须以 manifest/source-of-truth、结构化 health/preflight 和合同测试为准。需要验证时优先运行:
```bash
bun scripts/code-queue-runner-skills-contract-test.ts
bun scripts/cli.ts codex pr-preflight --remote --issue <issue-number>
```
指挥官审查 checklist
- PR base 是声明的目标分支,head branch 命名可追踪,远端 head commit 可 fetch。
@@ -0,0 +1,92 @@
import { readFileSync } from "node:fs";
import { collectSkillAvailability } from "../src/components/microservices/code-queue/src/skill-availability";
type JsonRecord = Record<string, unknown>;
function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
}
function asRecord(value: unknown, label: string): JsonRecord {
assertCondition(typeof value === "object" && value !== null && !Array.isArray(value), `${label} must be an object`, value);
return value as JsonRecord;
}
function countOccurrences(haystack: string, needle: string): number {
return haystack.split(needle).length - 1;
}
const productionManifest = readFileSync("src/components/microservices/k3sctl-adapter/k3s/code-queue.k8s.yaml", "utf8");
const devManifest = readFileSync("src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-code-queue.k8s.yaml", "utf8");
const runtimePreflight = readFileSync("src/components/microservices/code-queue/src/runtime-preflight.ts", "utf8");
const indexSource = readFileSync("src/components/microservices/code-queue/src/index.ts", "utf8");
const skillModule = readFileSync("src/components/microservices/code-queue/src/skill-availability.ts", "utf8");
const promptSource = readFileSync("src/components/microservices/code-queue/src/prompts.ts", "utf8");
const docsReference = readFileSync("docs/reference/code-queue-supervision.md", "utf8");
const forbiddenPathLiteral = [".ag", "nets/skills"].join("");
assertCondition(!productionManifest.includes(forbiddenPathLiteral), "production manifest must not propagate misspelled skills path");
assertCondition(!devManifest.includes(forbiddenPathLiteral), "dev manifest must not propagate misspelled skills path");
assertCondition(!promptSource.includes(forbiddenPathLiteral), "runner prompt must not mention misspelled skills path");
assertCondition(!skillModule.includes(forbiddenPathLiteral), "skill availability implementation must not propagate misspelled skills path literal");
assertCondition(!docsReference.includes(forbiddenPathLiteral), "reference docs must not propagate misspelled skills path literal");
assertCondition(countOccurrences(productionManifest, "name: UNIDESK_SKILLS_PATH") === 3, "production read/write/scheduler must set UNIDESK_SKILLS_PATH", {
count: countOccurrences(productionManifest, "name: UNIDESK_SKILLS_PATH"),
});
assertCondition(countOccurrences(productionManifest, "mountPath: /root/.agents/skills") === 3, "production read/write/scheduler must mount skills target", {
count: countOccurrences(productionManifest, "mountPath: /root/.agents/skills"),
});
assertCondition(countOccurrences(productionManifest, "path: /home/ubuntu/.agents/skills") === 3, "production read/write/scheduler must use hostPath source of truth", {
count: countOccurrences(productionManifest, "path: /home/ubuntu/.agents/skills"),
});
assertCondition(countOccurrences(productionManifest, "name: skills-dir") >= 6, "production manifest must define skills-dir mounts and volumes", {
count: countOccurrences(productionManifest, "name: skills-dir"),
});
assertCondition(devManifest.includes("path: /home/ubuntu/.agents/skills"), "dev manifest should keep the same hostPath source of truth");
const available = collectSkillAvailability({
source: "/home/ubuntu/.agents/skills",
target: "/home/ubuntu/.agents/skills",
requiredSkills: ["docs-spec", "cli-spec", "frontend-design", "playwright-cli"],
});
assertCondition(available.source === "/home/ubuntu/.agents/skills", "skill report must expose source");
assertCondition(available.target === "/home/ubuntu/.agents/skills", "skill report must expose target");
assertCondition(Array.isArray(available.requiredSkills) && available.requiredSkills.includes("docs-spec"), "skill report must expose requiredSkills");
assertCondition(Array.isArray(available.missingSkills), "skill report must expose missingSkills");
assertCondition(available.valuesPrinted === false, "skill report must declare valuesPrinted=false");
assertCondition(asRecord(available.pathSpelling, "pathSpelling").forbiddenPathMustNotBeUsed === true, "skill report must flag misspelled path risk without spreading the literal path");
assertCondition(!JSON.stringify(available).includes(forbiddenPathLiteral), "skill report must not propagate misspelled path literal");
assertCondition(!JSON.stringify(available).includes("GH_TOKEN"), "skill report must not include secret environment names unrelated to skills");
const missing = collectSkillAvailability({
source: "/home/ubuntu/.agents/skills",
target: "/path/that/does/not/exist/for-code-queue-skills-test",
requiredSkills: ["docs-spec", "cli-spec"],
});
assertCondition(missing.ok === false, "missing target should fail");
assertCondition(missing.degraded === true, "missing target should be degraded");
assertCondition(missing.blocker === "skills-target-missing", "missing target should expose blocker", missing);
assertCondition(missing.missingSkills.includes("docs-spec") && missing.missingSkills.includes("cli-spec"), "missing target should list required missing skills", missing);
assertCondition(missing.valuesPrinted === false, "missing report must also declare valuesPrinted=false");
assertCondition(runtimePreflight.includes("skills: SkillAvailabilityReport"), "runtime preflight type must include skills report");
assertCondition(runtimePreflight.includes("collectSkillAvailability"), "runtime preflight must collect skills availability");
assertCondition(runtimePreflight.includes("skills.ok && ports.codex.ok"), "runtime preflight ok must depend on skills.ok");
assertCondition(indexSource.includes("const skillsReady = skills.ok === true"), "dev-ready must gate on structured skills ok");
process.stdout.write(`${JSON.stringify({
ok: true,
checks: [
"production Code Queue mounts /home/ubuntu/.agents/skills read-only at /root/.agents/skills",
"skill availability report exposes source, target, requiredSkills, missingSkills, degraded/blocker and valuesPrinted=false",
"runtime-preflight and dev-ready use the same structured skill report",
"misspelled skills paths are only surfaced as a forbidden diagnostic risk",
],
observedRunner: {
source: available.source,
target: available.target,
ok: available.ok,
missingSkills: available.missingSkills,
valuesPrinted: available.valuesPrinted,
},
}, null, 2)}\n`);
+3
View File
@@ -303,6 +303,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
fileItem("scripts/src/code-queue-liveness-fixtures.ts"),
fileItem("scripts/code-queue-trace-summary-contract-test.ts"),
fileItem("scripts/code-queue-pr-preflight-contract-test.ts"),
fileItem("scripts/code-queue-runner-skills-contract-test.ts"),
fileItem("scripts/code-queue-submit-routing-contract-test.ts"),
fileItem("scripts/code-queue-supervisor-disclosure-contract-test.ts"),
fileItem("scripts/host-codex-commander-skeleton-contract-test.ts"),
@@ -339,6 +340,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
items.push(commandItem("code-queue:issue3-diagnostics-and-image-preflight", ["bun", "scripts/code-queue-issue3-regression-test.ts"], 30_000));
items.push(commandItem("code-queue:trace-summary-contract", ["bun", "scripts/code-queue-trace-summary-contract-test.ts"], 30_000));
items.push(commandItem("code-queue:pr-preflight-contract", ["bun", "scripts/code-queue-pr-preflight-contract-test.ts"], 30_000));
items.push(commandItem("code-queue:runner-skills-contract", ["bun", "scripts/code-queue-runner-skills-contract-test.ts"], 30_000));
items.push(commandItem("code-queue:submit-routing-contract", ["bun", "scripts/code-queue-submit-routing-contract-test.ts"], 30_000));
items.push(commandItem("code-queue:supervisor-disclosure-contract", ["bun", "scripts/code-queue-supervisor-disclosure-contract-test.ts"], 30_000));
items.push(commandItem("host-codex-commander:skeleton-contract", ["bun", "scripts/host-codex-commander-skeleton-contract-test.ts"], 30_000));
@@ -364,6 +366,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
items.push(skippedItem("code-queue:issue3-diagnostics-and-image-preflight", "Code Queue issue #3 regression fixtures are opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:trace-summary-contract", "Code Queue trace summary contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:pr-preflight-contract", "Code Queue PR preflight contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:runner-skills-contract", "Code Queue runner skill availability contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:submit-routing-contract", "Code Queue submit routing contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:supervisor-disclosure-contract", "Code Queue supervisor disclosure contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("host-codex-commander:skeleton-contract", "host Codex commander skeleton contract is opt-in with script checks", "--scripts-typecheck or --full"));
+9 -1
View File
@@ -2593,13 +2593,21 @@ function compactSkillsStatus(value: unknown): Record<string, unknown> | null {
const record = asRecord(value);
if (record === null) return null;
return {
ok: record.ok ?? false,
path: record.path ?? null,
source: record.source ?? null,
target: record.target ?? null,
mountPoint: record.mountPoint ?? null,
exists: record.exists ?? false,
available: record.available ?? false,
degraded: record.degraded ?? true,
blocker: record.blocker ?? null,
readonly: record.readonly ?? false,
skillCount: record.skillCount ?? 0,
cliSpecAvailable: record.cliSpecAvailable ?? false,
requiredSkills: Array.isArray(record.requiredSkills) ? record.requiredSkills : [],
missingSkills: Array.isArray(record.missingSkills) ? record.missingSkills : [],
valuesPrinted: record.valuesPrinted ?? false,
pathSpelling: record.pathSpelling ?? null,
repairHint: record.repairHint ?? null,
};
}
@@ -134,6 +134,7 @@ import {
readOaTraceStepsForTask,
} from "./oa-events";
import { collectRuntimePreflight, runtimePreflightJson } from "./runtime-preflight";
import { collectSkillAvailability, skillAvailabilityJson } from "./skill-availability";
import { configureSelfTests, runJudgeInfraSelfTest, runQueueClaimMoveSelfTest, runQueueOrderingSelfTest, runReferenceInjectionSelfTest, runTracePortSelfTest, runTraceSummaryContractSelfTest } from "./self-tests";
import {
codexToolLifecycleStartedBeforeIn,
@@ -2420,72 +2421,8 @@ function runProbe(command: string, args: string[], timeout = 3_000): { ok: boole
return { ok: result.status === 0, output };
}
function decodeMountInfoPath(value: string): string {
return value.replace(/\\([0-7]{3})/gu, (_match, octal: string) => String.fromCharCode(Number.parseInt(octal, 8)));
}
function mountInfoForPath(path: string): { mountPoint: string | null; readonly: boolean | null } {
try {
const target = resolve(path);
let best: { mountPoint: string; readonly: boolean } | null = null;
for (const line of readFileSync("/proc/self/mountinfo", "utf8").split(/\r?\n/u)) {
if (line.trim().length === 0) continue;
const fields = line.split(" ");
const mountPoint = decodeMountInfoPath(fields[4] ?? "");
const options = (fields[5] ?? "").split(",");
if (mountPoint.length === 0) continue;
const matches = target === mountPoint || target.startsWith(mountPoint.endsWith("/") ? mountPoint : `${mountPoint}/`);
if (!matches) continue;
if (best === null || mountPoint.length > best.mountPoint.length) best = { mountPoint, readonly: options.includes("ro") };
}
return best ?? { mountPoint: null, readonly: null };
} catch {
return { mountPoint: null, readonly: null };
}
}
function collectSkillsStatus(): JsonValue {
const path = config.skillsPath;
const exists = existsSync(path);
const mountInfo = mountInfoForPath(path);
let directory = false;
let skillCount = 0;
let cliSpecAvailable = false;
let readonly = mountInfo.readonly === true;
let error: string | null = null;
if (exists) {
try {
const stat = statSync(path);
directory = stat.isDirectory();
if (directory) {
const entries = readdirSync(path, { withFileTypes: true });
skillCount = entries.filter((entry) => entry.isDirectory()).length;
cliSpecAvailable = existsSync(resolve(path, "cli-spec", "SKILL.md"));
if (mountInfo.readonly === null) {
const writeProbe = runProbe("sh", ["-lc", `test ! -w ${shellQuote(path)}`], 2_000);
readonly = writeProbe.ok;
}
}
} catch (probeError) {
error = probeError instanceof Error ? probeError.message : String(probeError);
}
}
const available = exists && directory;
return {
path,
mountPoint: mountInfo.mountPoint,
exists,
directory,
available,
readonly,
skillCount,
cliSpecAvailable,
expectedMount: "host ~/.agents/skills mounted read-only to UNIDESK_SKILLS_PATH",
repairHint: available && readonly && cliSpecAvailable
? null
: "DEV code-queue should mount /home/ubuntu/.agents/skills read-only at /root/.agents/skills and set UNIDESK_SKILLS_PATH=/root/.agents/skills.",
error,
} as unknown as JsonValue;
return skillAvailabilityJson(collectSkillAvailability({ target: config.skillsPath }));
}
function collectDevReady(): JsonValue {
@@ -2534,7 +2471,7 @@ function collectDevReady(): JsonValue {
const githubKnownHostProbe = runProbe("ssh-keygen", ["-F", "github.com", "-f", "/root/.ssh/known_hosts"]);
const sshSharedReady = existsSync("/root/.ssh") && sshKeyProbe.ok && sshKeyProbe.output.trim().length > 0;
const skills = collectSkillsStatus() as Record<string, JsonValue>;
const skillsReady = skills.available === true && skills.readonly === true && skills.cliSpecAvailable === true;
const skillsReady = skills.ok === true;
const runtimePreflight = runtimePreflightJson(collectRuntimePreflight({ includeRemote: false, includePushDryRun: false }));
const ok = missingTools.length === 0 && dockerProbe.ok && composeProbe.ok && workdirExists && dockerSocketExists && codexConfigReady && sshSharedReady && skillsReady;
const value: JsonValue = {
@@ -2,6 +2,7 @@
import { spawnSync } from "node:child_process";
import type { JsonValue } from "./types";
import { collectSkillAvailability, type SkillAvailabilityReport } from "./skill-availability";
export type RuntimePreflightAgentPort = "codex" | "opencode";
@@ -51,6 +52,7 @@ export interface RuntimePreflightReport {
version: string;
};
path: string;
skills: SkillAvailabilityReport;
ports: Record<RuntimePreflightAgentPort, RuntimePreflightPortStatus>;
pullRequestDelivery: PullRequestDeliveryPreflight;
}
@@ -645,13 +647,14 @@ function opencodeStatus(path: string, checkedAt: string): RuntimePreflightPortSt
export function collectRuntimePreflight(options: RuntimePreflightOptions = {}): RuntimePreflightReport {
const checkedAt = new Date().toISOString();
const path = process.env.PATH ?? "";
const skills = collectSkillAvailability({ target: process.env.UNIDESK_SKILLS_PATH || "/root/.agents/skills" });
const ports = {
codex: codexStatus(path, checkedAt),
opencode: opencodeStatus(path, checkedAt),
};
const pullRequestDelivery = collectPullRequestDeliveryPreflight(options, checkedAt);
return {
ok: ports.codex.ok && ports.opencode.ok && pullRequestDelivery.ok,
ok: skills.ok && ports.codex.ok && ports.opencode.ok && pullRequestDelivery.ok,
checkedAt,
cwd: process.cwd(),
pid: process.pid,
@@ -661,6 +664,7 @@ export function collectRuntimePreflight(options: RuntimePreflightOptions = {}):
version: process.version,
},
path,
skills,
ports,
pullRequestDelivery,
};
@@ -0,0 +1,188 @@
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import { resolve } from "node:path";
import { spawnSync } from "node:child_process";
import type { JsonValue } from "./types";
export interface SkillAvailabilityOptions {
target: string;
source?: string;
requiredSkills?: string[];
}
export interface SkillAvailabilityReport {
ok: boolean;
available: boolean;
degraded: boolean;
blocker: string | null;
checkedAt: string;
source: string;
target: string;
path: string;
mountPoint: string | null;
exists: boolean;
directory: boolean;
readonly: boolean;
skillCount: number;
requiredSkills: string[];
missingSkills: string[];
skills: Array<{ name: string; present: boolean; skillMdPresent: boolean; path: string }>;
pathSpelling: {
expectedTarget: string;
forbiddenPathChecked: true;
forbiddenPathExists: boolean;
forbiddenPathMustNotBeUsed: true;
};
expectedMount: string;
repairHint: string | null;
error: string | null;
valuesPrinted: false;
}
const defaultRequiredSkills = ["docs-spec", "cli-spec", "frontend-design", "playwright-cli"];
const defaultSource = "/home/ubuntu/.agents/skills";
const expectedTarget = "/root/.agents/skills";
const forbiddenSkillsDirName = [".ag", "nets"].join("");
const forbiddenTargets = [`/root/${forbiddenSkillsDirName}/skills`, `/home/ubuntu/${forbiddenSkillsDirName}/skills`];
const skillDirectoryAliases: Record<string, string[]> = {
"playwright-cli": ["playwright-cli", "playwright"],
};
function shellQuote(value: string): string {
return `'${value.replace(/'/gu, "'\\''")}'`;
}
function commandOk(command: string, args: string[], timeoutMs = 2000): boolean {
const result = spawnSync(command, args, {
encoding: "utf8",
timeout: timeoutMs,
maxBuffer: 64 * 1024,
shell: false,
});
return result.error === undefined && result.status === 0;
}
function decodeMountInfoPath(value: string): string {
return value.replace(/\\([0-7]{3})/gu, (_match, octal: string) => String.fromCharCode(Number.parseInt(octal, 8)));
}
function mountInfoForPath(path: string): { mountPoint: string | null; readonly: boolean | null } {
try {
const target = resolve(path);
let best: { mountPoint: string; readonly: boolean } | null = null;
for (const line of readFileSync("/proc/self/mountinfo", "utf8").split(/\r?\n/u)) {
if (line.trim().length === 0) continue;
const fields = line.split(" ");
const mountPoint = decodeMountInfoPath(fields[4] ?? "");
const options = (fields[5] ?? "").split(",");
if (mountPoint.length === 0) continue;
const matches = target === mountPoint || target.startsWith(mountPoint.endsWith("/") ? mountPoint : `${mountPoint}/`);
if (!matches) continue;
if (best === null || mountPoint.length > best.mountPoint.length) best = { mountPoint, readonly: options.includes("ro") };
}
return best ?? { mountPoint: null, readonly: null };
} catch {
return { mountPoint: null, readonly: null };
}
}
function candidateDirs(name: string): string[] {
return skillDirectoryAliases[name] ?? [name];
}
function skillStatus(target: string, requiredSkills: string[]): SkillAvailabilityReport["skills"] {
return requiredSkills.map((name) => {
const candidates = candidateDirs(name).map((candidate) => resolve(target, candidate));
const existingPath = candidates.find((candidate) => existsSync(candidate) && statSync(candidate).isDirectory());
const path = existingPath ?? candidates[0] ?? resolve(target, name);
return {
name,
present: existingPath !== undefined,
skillMdPresent: existsSync(resolve(path, "SKILL.md")),
path,
};
});
}
export function collectSkillAvailability(options: SkillAvailabilityOptions): SkillAvailabilityReport {
const target = options.target;
const source = options.source ?? defaultSource;
const requiredSkills = options.requiredSkills ?? defaultRequiredSkills;
const checkedAt = new Date().toISOString();
const mountInfo = mountInfoForPath(target);
const exists = existsSync(target);
const forbiddenPathExists = forbiddenTargets.some((path) => existsSync(path));
let directory = false;
let skillCount = 0;
let readonly = mountInfo.readonly === true;
let error: string | null = null;
let skills: SkillAvailabilityReport["skills"] = requiredSkills.map((name) => ({
name,
present: false,
skillMdPresent: false,
path: resolve(target, name),
}));
if (exists) {
try {
const stat = statSync(target);
directory = stat.isDirectory();
if (directory) {
const entries = readdirSync(target, { withFileTypes: true });
skillCount = entries.filter((entry) => entry.isDirectory()).length;
skills = skillStatus(target, requiredSkills);
if (mountInfo.readonly === null) {
readonly = commandOk("sh", ["-lc", `test ! -w ${shellQuote(target)}`]);
}
}
} catch (probeError) {
error = probeError instanceof Error ? probeError.message : String(probeError);
}
}
const missingSkills = skills.filter((skill) => !skill.present || !skill.skillMdPresent).map((skill) => skill.name);
const available = exists && directory;
const blocker = !available
? "skills-target-missing"
: !readonly
? "skills-target-not-readonly"
: missingSkills.length > 0
? "required-skills-missing"
: forbiddenPathExists
? "forbidden-skills-path-present"
: null;
const ok = blocker === null;
return {
ok,
available,
degraded: !ok,
blocker,
checkedAt,
source,
target,
path: target,
mountPoint: mountInfo.mountPoint,
exists,
directory,
readonly,
skillCount,
requiredSkills,
missingSkills,
skills,
pathSpelling: {
expectedTarget,
forbiddenPathChecked: true,
forbiddenPathExists,
forbiddenPathMustNotBeUsed: true,
},
expectedMount: `${source} mounted read-only to ${target}`,
repairHint: ok
? null
: `Mount ${source} read-only at ${target}, set UNIDESK_SKILLS_PATH=${target}, and remove any forbidden skills path spelling.`,
error,
valuesPrinted: false,
};
}
export function skillAvailabilityJson(report: SkillAvailabilityReport): JsonValue {
return report as unknown as JsonValue;
}
@@ -80,6 +80,8 @@ spec:
value: "/var/lib/unidesk/code-queue/opencode-xdg"
- name: CODE_QUEUE_SOURCE_CODEX_CONFIG
value: "/root/.codex/config.toml"
- name: UNIDESK_SKILLS_PATH
value: "/root/.agents/skills"
- name: CODE_QUEUE_DEFAULT_MODEL
value: "gpt-5.5"
- name: CODE_QUEUE_MODELS
@@ -177,6 +179,9 @@ spec:
- name: codex-auth
mountPath: /root/.codex/auth.json
readOnly: true
- name: skills-dir
mountPath: /root/.agents/skills
readOnly: true
- name: ssh-dir
mountPath: /root/.ssh
readOnly: true
@@ -232,6 +237,10 @@ spec:
hostPath:
path: /home/ubuntu/.codex/auth.json
type: File
- name: skills-dir
hostPath:
path: /home/ubuntu/.agents/skills
type: Directory
- name: ssh-dir
hostPath:
path: /home/ubuntu/.ssh
@@ -317,6 +326,8 @@ spec:
value: "/var/lib/unidesk/code-queue/opencode-xdg"
- name: CODE_QUEUE_SOURCE_CODEX_CONFIG
value: "/root/.codex/config.toml"
- name: UNIDESK_SKILLS_PATH
value: "/root/.agents/skills"
- name: CODE_QUEUE_DEFAULT_MODEL
value: "gpt-5.5"
- name: CODE_QUEUE_MODELS
@@ -414,6 +425,9 @@ spec:
- name: codex-auth
mountPath: /root/.codex/auth.json
readOnly: true
- name: skills-dir
mountPath: /root/.agents/skills
readOnly: true
- name: ssh-dir
mountPath: /root/.ssh
readOnly: true
@@ -469,6 +483,10 @@ spec:
hostPath:
path: /home/ubuntu/.codex/auth.json
type: File
- name: skills-dir
hostPath:
path: /home/ubuntu/.agents/skills
type: Directory
- name: ssh-dir
hostPath:
path: /home/ubuntu/.ssh
@@ -1013,6 +1031,8 @@ spec:
value: "/var/lib/unidesk/code-queue/opencode-xdg"
- name: CODE_QUEUE_SOURCE_CODEX_CONFIG
value: "/root/.codex/config.toml"
- name: UNIDESK_SKILLS_PATH
value: "/root/.agents/skills"
- name: CODE_QUEUE_DEFAULT_MODEL
value: "gpt-5.5"
- name: CODE_QUEUE_MODELS
@@ -1110,6 +1130,9 @@ spec:
- name: codex-auth
mountPath: /root/.codex/auth.json
readOnly: true
- name: skills-dir
mountPath: /root/.agents/skills
readOnly: true
- name: ssh-dir
mountPath: /root/.ssh
readOnly: true
@@ -1165,6 +1188,10 @@ spec:
hostPath:
path: /home/ubuntu/.codex/auth.json
type: File
- name: skills-dir
hostPath:
path: /home/ubuntu/.agents/skills
type: Directory
- name: ssh-dir
hostPath:
path: /home/ubuntu/.ssh