{"record":{"id":"33c86b81f54fad02","repo":"XTLS/Xray-core","slug":"transform-arg-must-set-exactly-one-value","errorCode":null,"errorMessage":"transform arg must set exactly one value","messagePattern":"transform arg must set exactly one value","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"infra/conf/transport_finalmask.go","lineNumber":454,"sourceCode":"func buildCustomTransformArg(arg CustomTransformArg) (*custom.ExprArg, error) {\n\tkindCount := 0\n\tif len(arg.Bytes) > 0 {\n\t\tkindCount++\n\t}\n\tif arg.U64 != nil {\n\t\tkindCount++\n\t}\n\tif arg.Reuse != \"\" {\n\t\tkindCount++\n\t}\n\tif arg.Metadata != \"\" {\n\t\tkindCount++\n\t}\n\tif arg.Transform != nil {\n\t\tkindCount++\n\t}\n\tif kindCount != 1 {\n\t\treturn nil, errors.New(\"transform arg must set exactly one value\")\n\t}\n\n\tif len(arg.Bytes) > 0 {\n\t\tvalue, err := PraseByteSlice(arg.Bytes, arg.Type)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &custom.ExprArg{\n\t\t\tValue: &custom.ExprArg_Bytes{\n\t\t\t\tBytes: value,\n\t\t\t},\n\t\t}, nil\n\t}\n\tif arg.U64 != nil {\n\t\treturn &custom.ExprArg{\n\t\t\tValue: &custom.ExprArg_U64{\n\t\t\t\tU64: *arg.U64,\n\t\t\t},","sourceCodeStart":436,"sourceCodeEnd":472,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/infra/conf/transport_finalmask.go#L436-L472","documentation":"buildCustomTransformArg (infra/conf/transport_finalmask.go:454) counts the value fields set on a transform argument — str, bytes, u64, reuse, metadata, transform — and requires exactly one. Zero set, or two or more set, returns \"transform arg must set exactly one value\"; an arg is a tagged union, not a record.","triggerScenarios":"{} as an arg; {\"str\":\"a\",\"reuse\":\"b\"}; {\"bytes\":\"...\",\"u64\":1}. Note bytes+type is fine because type is a decoder hint, not a value; but any two value fields together fail.","commonSituations":"Copy-pasting arg templates and leaving placeholder values; adding a fallback value 'just in case'; treating args as a struct where you fill several fields.","solutions":["Set exactly one value field per arg object","Chain multiple values as multiple args consumed by the op instead of one arg with several fields","Remove default/placeholder entries like \"u64\":0 — even an explicit zero counts as set"],"exampleFix":"// before\n\"args\": [ { \"str\": \"id-\", \"u64\": 0 } ]\n// after\n\"args\": [ { \"str\": \"id-\" }, { \"u64\": 7 } ]","handlingStrategy":"validation","validationCode":"const ARG_FIELDS = ['str','bytes','u64','reuse','metadata','transform'];\nfor (const a of t.args ?? []) {\n  const set = ARG_FIELDS.filter(k => a[k] !== undefined && a[k] !== null);\n  if (set.length !== 1) throw new Error(`arg must set exactly one of ${ARG_FIELDS}, set: ${set}`);\n}","typeGuard":"const isSingleValueArg = (a:any) => ARG_FIELDS.filter(k => a[k] !== undefined && a[k] !== null).length === 1;","tryCatchPattern":null,"preventionTips":["Treat transform args as a tagged union","Omit unused fields entirely; explicit zeros count as set"],"tags":["xray","go","transport","header-custom","transform","validation"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}