{"record":{"id":"61cb34fe902d3339","repo":"windmill-labs/windmill","slug":"path-and-hash-are-mutually-exclusive","errorCode":null,"errorMessage":"path and hash_ are mutually exclusive","messagePattern":"path and hash_ are mutually exclusive","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"backend/windmill-runtime-nativets/src/windmill-client.js","lineNumber":9975,"sourceCode":"        method: \"POST\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n          Authorization: `Bearer ${getEnv(\"WM_TOKEN\")}`,\n        },\n        body: JSON.stringify({ args }),\n      }\n    );\n    let jobId = await req.text();\n    console.log(`Started task ${f.name} as job ${jobId}`);\n    let r = await waitJob(jobId);\n    console.log(`Task ${f.name} (${jobId}) completed`);\n    return r;\n  };\n}\nasync function runScriptAsync(path, hash_, args, scheduledInSeconds = null) {\n  !clientSet && setClient();\n  if (path && hash_) {\n    throw new Error(\"path and hash_ are mutually exclusive\");\n  }\n  args = args || {};\n  const params = {};\n  if (scheduledInSeconds) {\n    params[\"scheduled_in_secs\"] = scheduledInSeconds;\n  }\n  let parentJobId = getEnv(\"WM_JOB_ID\");\n  if (parentJobId !== void 0) {\n    params[\"parent_job\"] = parentJobId;\n  }\n  let rootJobId = getEnv(\"WM_ROOT_FLOW_JOB_ID\");\n  if (rootJobId != void 0 && rootJobId != \"\") {\n    params[\"root_job\"] = rootJobId;\n  }\n  let endpoint;\n  if (path) {\n    endpoint = `/w/${getWorkspace()}/jobs/run/p/${path}`;\n  } else if (hash_) {","sourceCodeStart":9957,"sourceCodeEnd":9993,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/backend/windmill-runtime-nativets/src/windmill-client.js#L9957-L9993","documentation":"runScriptAsync accepts either a script path or a deployed script hash to identify the script to run, but not both. Passing both arguments throws this error immediately, before any API call, because the two identifiers are alternative selectors for the same script.","triggerScenarios":"Calling runScriptAsync(path, hash_, args) with truthy values for both `path` and `hash_` — e.g. copying an example that sets both, or forwarding optional parameters without nulling the unused one.","commonSituations":"Dynamic code that computes both a path and a hash from config and doesn't clear the other; wrappers around runScriptAsync that pass through raw config objects.","solutions":["Pass only one identifier: set the other argument to null/undefined.","Prefer the path for readability, or the hash to pin an exact deployed version.","Add a validation/coalescing step upstream so only one of the two is ever forwarded."],"exampleFix":"// before\nawait runScriptAsync(cfg.path, cfg.hash, args); // throws when both set\n// after\nawait runScriptAsync(cfg.path ?? null, cfg.hash ?? null, args); // still validate one is set\n// or explicitly:\nawait runScriptAsync(cfg.hash ? null : cfg.path, cfg.hash, args);","handlingStrategy":"validation","validationCode":"function runScriptChecked(path, hash_, args) {\n  if (path && hash_) throw new TypeError('path and hash_ are mutually exclusive');\n  if (!path && !hash_) throw new TypeError('path or hash_ must be provided');\n  return runScriptAsync(path ?? null, hash_ ?? null, args);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Normalize identifiers at the config boundary: keep a discriminated { path } | { hash } object.","Pass null (not '') for the unused argument so truthiness checks behave.","Write a thin wrapper that enforces the constraint once instead of at every call site."],"tags":["argument-validation","api-client","scripts"],"backgroundTag":"mutually-exclusive-arguments","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}