{"record":{"id":"9b34312d5c1f5acb","repo":"paperclipai/paperclip","slug":"railway-operation-unconfirmed","errorCode":"railway_operation_unconfirmed","errorMessage":"Railway did not confirm a resulting deployment. Inspect deployment status before retrying.","messagePattern":"Railway did not confirm a resulting deployment\\. Inspect deployment status before retrying\\.","errorType":"error_code","errorClass":"RailwayError","httpStatus":null,"severity":"error","filePath":"server/src/services/railway.ts","lineNumber":289,"sourceCode":"            const lines = data[args.kind === \"build\" ? \"buildLogs\" : \"deploymentLogs\"];\n            if (!Array.isArray(lines)) throw new RailwayError(\"railway_invalid_response\", \"Railway returned invalid log data.\");\n            let bytes = 0;\n            let messageTruncated = false;\n            const bounded = [];\n            for (const line of lines.slice(0, args.limit)) {\n              const message = String(line.message ?? \"\");\n              if (message.length > 8192) messageTruncated = true;\n              const safe = redact({ timestamp: line.timestamp, severity: line.severity, message: message.slice(0, 8192) });\n              bytes += Buffer.byteLength(JSON.stringify(safe));\n              if (bytes > 64 * 1024) break;\n              bounded.push(safe);\n            }\n            result = { deploymentId: args.deploymentId, kind: args.kind, lines: bounded, truncated: messageTruncated || bounded.length < lines.length, limitReached: lines.length >= args.limit }; break;\n          }\n          case \"redeploy\":\n            if (!deployment?.canRedeploy) throw new RailwayError(\"railway_deployment_ineligible\", \"Railway does not allow this deployment to be redeployed.\", 409);\n            result = await query(RAILWAY_QUERIES.redeploy, { deploymentId: args.deploymentId });\n            if (!id.safeParse(record(record(result).deploymentRedeploy).id).success) throw new RailwayError(\"railway_operation_unconfirmed\", \"Railway did not confirm a resulting deployment. Inspect deployment status before retrying.\");\n            break;\n          case \"restart\":\n            result = await query(RAILWAY_QUERIES.restart, { deploymentId: args.deploymentId });\n            if (record(result).deploymentRestart !== true) throw new RailwayError(\"railway_operation_unconfirmed\", \"Railway did not confirm the restart. Inspect deployment status before retrying.\");\n            result = { ...record(result), targetDeploymentId: args.deploymentId };\n            break;\n          case \"rollback\":\n            if (!deployment?.canRollback) throw new RailwayError(\"railway_deployment_ineligible\", \"Railway does not allow rollback to this deployment.\", 409);\n            result = await query(RAILWAY_QUERIES.rollback, { deploymentId: args.deploymentId });\n            if (record(result).deploymentRollback !== true) throw new RailwayError(\"railway_operation_unconfirmed\", \"Railway did not confirm the rollback. Inspect deployment status before retrying.\");\n            result = { ...record(result), targetDeploymentId: args.deploymentId };\n            break;\n          case \"run-command\":\n            if (!Array.isArray(deployment?.instances) || !deployment.instances.some((entry: { id: string }) => entry.id === args.deploymentInstanceId) || deployment.status !== \"SUCCESS\") throw new RailwayError(\"railway_target_mismatch\", \"The container instance is not part of the selected running deployment.\", 403);\n            if (!options.runCommand) throw new RailwayError(\"railway_ssh_setup_required\", \"Configure Container access on this Railway connection before running commands.\", 422);\n            result = await options.runCommand({ deploymentInstanceId: args.deploymentInstanceId, command: args.command, timeoutSeconds: args.timeoutSeconds, signal: options.signal }); break;\n        }\n      }","sourceCodeStart":271,"sourceCodeEnd":307,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/railway.ts#L271-L307","documentation":"After the Railway GraphQL `deploymentRedeploy` mutation executes, the library validates that the returned `deploymentRedeploy.id` parses as a valid ID. If Railway accepted the request but returned no parseable resulting deployment ID, this `railway_operation_unconfirmed` error is thrown — the redeploy may or may not have actually started, so the caller must inspect deployment status before retrying to avoid duplicate deployments.","triggerScenarios":"Calling `railway_redeploy` where the mutation returns null/undefined `deploymentRedeploy`, an object without an `id`, or an `id` failing the `id.safeParse` format check — e.g. Railway-side race where the deployment was deleted mid-mutation, transient API inconsistency, or an unexpected response shape from a Railway API change.","commonSituations":"Redeploying a deployment concurrently deleted by a teammate or automation; Railway API version drift changing the mutation payload; retry storms after a network failure where the second attempt hits an already-redeployed deployment; intermittent Railway API inconsistencies during incidents.","solutions":["Do NOT immediately retry the mutation — call `railway_list-deployments` or `railway_deployment-status` to check whether a new deployment was already created.","If no new deployment exists after a status check, retry the redeploy once.","If the error repeats, verify the deployment still exists and is eligible via `railway_deployment-status`, then check Railway's status page for API incidents.","Capture the raw response (enable logging) and report if the mutation payload shape changed."],"exampleFix":"// before\ntry {\n  await railwayCall(\"railway_redeploy\", args);\n} catch (e) {\n  await railwayCall(\"railway_redeploy\", args); // risk: duplicate deployment\n}\n// after\ntry {\n  await railwayCall(\"railway_redeploy\", args);\n} catch (e) {\n  const deployments = await railwayCall(\"railway_list-deployments\", { projectId, environmentId, serviceId, first: 5 });\n  const alreadyRedeployed = deployments.deployments.edges.some((e2) => e2.node.createdAt > startedAt);\n  if (!alreadyRedeployed) await railwayCall(\"railway_redeploy\", args);\n}","handlingStrategy":"try-catch","validationCode":"const d = await railwayCall(\"railway_deployment-status\", { projectId, environmentId, serviceId, deploymentId });\nif (!d || d.canRedeploy !== true) throw new Error(\"Deployment not redeployable; skipping redeploy\");","typeGuard":"function hasConfirmedRedeploy(result) {\n  return typeof result?.deploymentRedeploy?.id === \"string\" && result.deploymentRedeploy.id.length > 0;\n}","tryCatchPattern":"try {\n  await railwayCall(\"railway_redeploy\", args);\n} catch (e) {\n  if (e?.code === \"railway_operation_unconfirmed\") {\n    // verify before retrying — the mutation may have succeeded\n    const created = await pollForNewDeployment(projectId, environmentId, serviceId, startedAt);\n    if (!created) await railwayCall(\"railway_redeploy\", args);\n  } else throw e;\n}","preventionTips":["Never retry unconfirmed mutations without first checking for the resulting deployment.","Record a start timestamp so you can detect deployments created by a possibly-successful mutation.","Keep redeploy invocations single-flight per deployment (idempotency key/lock).","Monitor Railway status for API incidents before assuming client-side bugs."],"tags":["railway","deployment","unconfirmed-mutation","idempotency"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}