fix: expose apply-patch missing-file errors

This commit is contained in:
Codex
2026-06-02 02:35:51 +00:00
parent db8b1b64af
commit e95e1c044a
2 changed files with 35 additions and 9 deletions
+3 -1
View File
@@ -425,7 +425,7 @@ async function readRemoteText(executor: ApplyPatchV2Executor, target: string): P
const [bytesText, expectedSha256] = stat.stdout.trim().split(/\s+/u);
const expectedBytes = Number(bytesText);
if (!Number.isSafeInteger(expectedBytes) || expectedBytes < 0 || !/^[0-9a-f]{64}$/u.test(expectedSha256 ?? "")) {
throw new ApplyPatchV2Error("remote apply-patch v2 stat returned invalid metadata", { path: target, stdout: stat.stdout.slice(0, 500) });
throw new ApplyPatchV2Error("remote apply-patch v2 stat returned invalid metadata", { path: target, stdout: stat.stdout.slice(0, 500), stderr: stat.stderr.slice(-500) });
}
const chunks: Buffer[] = [];
@@ -606,6 +606,8 @@ function remoteV2Script(operation: RemoteV2Operation, args: string[]): string[]
"case \"$op\" in",
" stat)",
" target=$1",
" if [ ! -e \"$target\" ]; then printf 'file not found: %s\\n' \"$target\" >&2; exit 1; fi",
" if [ -d \"$target\" ]; then printf 'not a file: %s\\n' \"$target\" >&2; exit 1; fi",
" bytes=$(wc -c < \"$target\" | tr -d '[:space:]')",
" digest=$(sha256_file \"$target\")",
" printf '%s %s\\n' \"$bytes\" \"$digest\"",
+32 -8
View File
@@ -19,6 +19,7 @@ import {
shellArgv,
sshFailureHint,
sshShellCompatibilityPrelude,
sshUserToolPathPrelude,
sshRuntimeTimeoutHint,
sshRuntimeTimeoutMs,
sshRuntimeTimingHint,
@@ -49,6 +50,10 @@ function sha256BufferHex(value: Buffer): string {
return createHash("sha256").update(value).digest("hex");
}
function sshShellScriptPrelude(): string {
return `${sshUserToolPathPrelude}\n${sshShellCompatibilityPrelude}`;
}
async function captureStdout(fn: () => Promise<number>): Promise<{ exitCode: number; stdout: string }> {
const originalWrite = process.stdout.write;
let stdout = "";
@@ -464,12 +469,12 @@ export async function runSshArgvGuidanceContract(): Promise<JsonRecord> {
const directScriptOneLiner = parseSshArgs(["script", "--", "cd /root/hwlab && git status --short --branch"]);
assertCondition(directScriptOneLiner.invocationKind === "helper", "script -- single-string command should run through a remote shell", directScriptOneLiner);
assertCondition(directScriptOneLiner.remoteCommand === shellArgv(["sh", "-c", `${sshShellCompatibilityPrelude}\ncd /root/hwlab && git status --short --branch`]), "script -- single-string command should match the intuitive remote shell one-liner form with the compatibility prelude", directScriptOneLiner);
assertCondition(directScriptOneLiner.remoteCommand === shellArgv(["sh", "-c", `${sshShellScriptPrelude()}\ncd /root/hwlab && git status --short --branch`]), "script -- single-string command should match the intuitive remote shell one-liner form with the compatibility prelude", directScriptOneLiner);
assertCondition(directScriptOneLiner.requiresStdin === false, "script -- single-string command should not wait for stdin", directScriptOneLiner);
const shellOneLiner = parseSshArgs(["shell", "sed -n '1,2p' a && sed -n '1,2p' b"]);
assertCondition(shellOneLiner.invocationKind === "helper", "shell one-liner must be a helper operation", shellOneLiner);
assertCondition(shellOneLiner.remoteCommand === shellArgv(["sh", "-c", `${sshShellCompatibilityPrelude}\nsed -n '1,2p' a && sed -n '1,2p' b`]), "shell one-liner must keep command operators inside the remote shell", shellOneLiner);
assertCondition(shellOneLiner.remoteCommand === shellArgv(["sh", "-c", `${sshShellScriptPrelude()}\nsed -n '1,2p' a && sed -n '1,2p' b`]), "shell one-liner must keep command operators inside the remote shell", shellOneLiner);
assertCondition(shellOneLiner.requiresStdin === false, "shell one-liner must not require stdin", shellOneLiner);
for (const shell of ["sh", "bash"]) {
@@ -491,7 +496,7 @@ export async function runSshArgvGuidanceContract(): Promise<JsonRecord> {
assertCondition(routeKubectl.parsed.remoteCommand === "'env' 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml' 'kubectl' 'get' 'pods' '-n' 'hwlab-dev'", "D601:k3s kubectl must map to kubectl argv", routeKubectl);
const routeK3sShell = parseSshInvocation("D601:k3s", ["shell", "kubectl get nodes && kubectl get pods -A"]);
assertCondition(routeK3sShell.parsed.remoteCommand === shellArgv(["env", "KUBECONFIG=/etc/rancher/k3s/k3s.yaml", "sh", "-c", `${sshShellCompatibilityPrelude}\nkubectl get nodes && kubectl get pods -A`]), "D601:k3s shell must run one-line shell logic on the k3s host with native kubeconfig", routeK3sShell);
assertCondition(routeK3sShell.parsed.remoteCommand === shellArgv(["env", "KUBECONFIG=/etc/rancher/k3s/k3s.yaml", "sh", "-c", `${sshShellScriptPrelude()}\nkubectl get nodes && kubectl get pods -A`]), "D601:k3s shell must run one-line shell logic on the k3s host with native kubeconfig", routeK3sShell);
const g14Guard = parseSshInvocation("G14:k3s", []);
assertCondition(g14Guard.providerId === "G14" && g14Guard.route.plane === "k3s", "G14:k3s must parse as a native k3s route", g14Guard);
@@ -512,25 +517,25 @@ export async function runSshArgvGuidanceContract(): Promise<JsonRecord> {
assertCondition(routeTargetArgv.parsed.remoteCommand === "'env' 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml' 'kubectl' 'exec' '-n' 'hwlab-dev' 'deployment/hwlab-cloud-api' '--' 'sh' '-c' 'printf ok'", "D601:k3s:<namespace>:<workload> argv must exec the argv payload instead of treating argv as a pod command", routeTargetArgv);
const routeTargetShell = parseSshInvocation("D601:k3s:hwlab-dev:hwlab-cloud-api/app", ["shell", "pwd && ls"]);
assertCondition(routeTargetShell.parsed.remoteCommand === shellArgv(["env", "KUBECONFIG=/etc/rancher/k3s/k3s.yaml", "kubectl", "exec", "-n", "hwlab-dev", "deployment/hwlab-cloud-api", "--", "sh", "-c", 'cd "$1" || exit; shift; exec "$@"', "unidesk-cwd", "/app", "sh", "-c", `${sshShellCompatibilityPrelude}\npwd && ls`]), "D601:k3s:<namespace>:<workload>/<workspace> shell must run shell logic after cd inside the pod", routeTargetShell);
assertCondition(routeTargetShell.parsed.remoteCommand === shellArgv(["env", "KUBECONFIG=/etc/rancher/k3s/k3s.yaml", "kubectl", "exec", "-n", "hwlab-dev", "deployment/hwlab-cloud-api", "--", "sh", "-c", 'cd "$1" || exit; shift; exec "$@"', "unidesk-cwd", "/app", "sh", "-c", `${sshShellScriptPrelude()}\npwd && ls`]), "D601:k3s:<namespace>:<workload>/<workspace> shell must run shell logic after cd inside the pod", routeTargetShell);
const routeScript = parseSshInvocation("D601:k3s:hwlab-dev:hwlab-cloud-api", ["script", "--shell", "bash", "--", "arg"]);
assertCondition(routeScript.parsed.requiresStdin === true, "k3s script operation must stream local stdin", routeScript);
assertCondition(routeScript.parsed.remoteCommand === "'env' 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml' 'kubectl' 'exec' '-i' '-n' 'hwlab-dev' 'deployment/hwlab-cloud-api' '--' 'bash' '-s' '--' 'arg'", "D601:k3s:<namespace>:<workload> script must map stdin to shell -s", routeScript);
assertCondition(routeScript.parsed.stdinPrefix === `${sshShellCompatibilityPrelude}\n`, "k3s script stdin must inject the shell compatibility prelude before user script text", routeScript);
assertCondition(routeScript.parsed.stdinPrefix === `${sshShellScriptPrelude()}\n`, "k3s script stdin must inject the shell compatibility prelude before user script text", routeScript);
const routeControlScript = parseSshInvocation("D601:k3s", ["script", "--shell", "bash", "--", "arg"]);
assertCondition(routeControlScript.parsed.requiresStdin === true, "k3s control-plane script operation must stream local stdin", routeControlScript);
assertCondition(routeControlScript.parsed.remoteCommand === "'env' 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml' 'bash' '-s' '--' 'arg'", "D601:k3s script must inject native kubeconfig without manual export", routeControlScript);
assertCondition(routeControlScript.parsed.stdinPrefix === `${sshShellCompatibilityPrelude}\n`, "k3s control-plane script stdin must inject the shell compatibility prelude before user script text", routeControlScript);
assertCondition(routeControlScript.parsed.stdinPrefix === `${sshShellScriptPrelude()}\n`, "k3s control-plane script stdin must inject the shell compatibility prelude before user script text", routeControlScript);
const routeControlScriptOneLiner = parseSshInvocation("D601:k3s", ["script", "--", "echo k3s-script-ok"]);
assertCondition(routeControlScriptOneLiner.parsed.requiresStdin === false, "k3s control-plane script -- one-liner must not wait for stdin", routeControlScriptOneLiner);
assertCondition(routeControlScriptOneLiner.parsed.remoteCommand === shellArgv(["env", "KUBECONFIG=/etc/rancher/k3s/k3s.yaml", "sh", "-c", `${sshShellCompatibilityPrelude}\necho k3s-script-ok`]), "k3s control-plane script -- one-liner must run through the native kubeconfig shell path", routeControlScriptOneLiner);
assertCondition(routeControlScriptOneLiner.parsed.remoteCommand === shellArgv(["env", "KUBECONFIG=/etc/rancher/k3s/k3s.yaml", "sh", "-c", `${sshShellScriptPrelude()}\necho k3s-script-ok`]), "k3s control-plane script -- one-liner must run through the native kubeconfig shell path", routeControlScriptOneLiner);
const routePodScriptOneLiner = parseSshInvocation("D601:k3s:hwlab-dev:hwlab-cloud-api", ["script", "--", "echo pod-script-ok"]);
assertCondition(routePodScriptOneLiner.parsed.requiresStdin === false, "k3s workload script -- one-liner must not wait for stdin", routePodScriptOneLiner);
assertCondition(routePodScriptOneLiner.parsed.remoteCommand === shellArgv(["env", "KUBECONFIG=/etc/rancher/k3s/k3s.yaml", "kubectl", "exec", "-n", "hwlab-dev", "deployment/hwlab-cloud-api", "--", "sh", "-c", `${sshShellCompatibilityPrelude}\necho pod-script-ok`]), "k3s workload script -- one-liner must run as sh -c inside the workload", routePodScriptOneLiner);
assertCondition(routePodScriptOneLiner.parsed.remoteCommand === shellArgv(["env", "KUBECONFIG=/etc/rancher/k3s/k3s.yaml", "kubectl", "exec", "-n", "hwlab-dev", "deployment/hwlab-cloud-api", "--", "sh", "-c", `${sshShellScriptPrelude()}\necho pod-script-ok`]), "k3s workload script -- one-liner must run as sh -c inside the workload", routePodScriptOneLiner);
const topLevelScriptSeparator = extractRemoteCliOptions(["ssh", "D601:/tmp", "script", "--", "sed", "-n", "1,2p", "file.txt"]);
assertCondition(JSON.stringify(topLevelScriptSeparator.args) === JSON.stringify(["ssh", "D601:/tmp", "script", "--", "sed", "-n", "1,2p", "file.txt"]), "top-level remote option parser must preserve command-local -- after the command starts", topLevelScriptSeparator);
@@ -724,6 +729,25 @@ export async function runSshArgvGuidanceContract(): Promise<JsonRecord> {
assertCondition(truncatedLargeReadV2.files["large-read.txt"] === largeOriginal, "v2 must keep the original file when bridge stdout truncates a read block", truncatedLargeReadV2);
assertCondition(!truncatedLargeReadV2.commands.some((command) => command.startsWith("write-b64")), "v2 must not write after read integrity fails", truncatedLargeReadV2.commands);
const missingUpdateShellV2 = await applyPatchV2ActualShellFixtureAttempt([
"*** Begin Patch",
"*** Update File: missing-dist.js",
"@@",
"-old",
"+new",
"*** End Patch",
"",
].join("\n"), {});
const missingUpdateShellError = missingUpdateShellV2.error instanceof Error ? missingUpdateShellV2.error : null;
assertCondition(missingUpdateShellError !== null, "v2 real shell stat should reject missing update targets", missingUpdateShellV2);
assertCondition(
missingUpdateShellError.message.includes("remote apply-patch v2 operation failed")
&& JSON.stringify((missingUpdateShellError as { details?: unknown }).details ?? {}).includes("file not found: missing-dist.js"),
"v2 real shell stat must expose file-not-found instead of invalid metadata",
missingUpdateShellError,
);
assertCondition(!missingUpdateShellV2.commands.some((command) => command.startsWith("read-b64") || command.startsWith("write-b64")), "missing update target must fail before remote read/write", missingUpdateShellV2.commands);
const truncatedLargeWriteV2 = await applyPatchV2ActualShellFixtureAttempt([
"*** Begin Patch",
"*** Update File: large.txt",