{"record":{"id":"6cc2dd50c43d0dad","repo":"theonedev/onedev","slug":"last-appearance-of-is-a-surprise-to-me-either-u","errorCode":null,"errorMessage":"Last appearance of @ is a surprise to me. Either use @...@ to reference a variable, or use @@ for literal @: ${value}","messagePattern":"Last appearance of @ is a surprise to me\\. Either use @\\.\\.\\.@ to reference a variable, or use @@ for literal @: (.+?)","errorType":"validation","errorClass":"ExplicitException","httpStatus":null,"severity":"error","filePath":"server-core/src/main/java/io/onedev/server/util/interpolative/Interpolative.java","lineNumber":42,"sourceCode":"\n\tprivate static final long serialVersionUID = 1L;\n\t\n\tprivate final List<Segment> segments;\n\t\n\tpublic Interpolative(List<Segment> segments) {\n\t\tthis.segments = segments;\n\t}\n\t\n\tpublic static Interpolative parse(String value) {\n\t\tCharStream is = CharStreams.fromString(value); \n\t\tInterpolativeLexer lexer = new InterpolativeLexer(is);\n\t\tlexer.removeErrorListeners();\n\t\tlexer.addErrorListener(new BaseErrorListener() {\n\n\t\t\t@Override\n\t\t\tpublic void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line,\n\t\t\t\t\tint charPositionInLine, String msg, RecognitionException e) {\n\t\t\t\tthrow new ExplicitException(\"Last appearance of @ is a surprise to me. Either use @...@ to reference \"\n\t\t\t\t\t\t+ \"a variable, or use @@ for literal @: \" + value);\n\t\t\t}\n\t\t\t\n\t\t});\n\t\tCommonTokenStream tokens = new CommonTokenStream(lexer);\n\t\tInterpolativeParser parser = new InterpolativeParser(tokens);\n\t\tparser.removeErrorListeners();\n\t\tparser.setErrorHandler(new BailErrorStrategy());\n\t\t\n\t\tList<Segment> segments = new ArrayList<>();\n\t\tfor (SegmentContext segment: parser.interpolative().segment()) {\n\t\t\tif (segment.Literal() != null) \n\t\t\t\tsegments.add(new Segment(Type.LITERAL, segment.Literal().getText().replace(\"@@\", \"@\")));\n\t\t\telse\n\t\t\t\tsegments.add(new Segment(Type.VARIABLE, FenceAware.unfence(segment.Variable().getText())));\n\t\t}\n\t\treturn new Interpolative(segments);\t\n\t}","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/util/interpolative/Interpolative.java#L24-L60","documentation":"Interpolative.syntaxError is the ANTLR error listener for the interpolative expression grammar (used for @...@ variable references). When the lexer/parser hits invalid syntax — typically an '@' that does not open a '@...@' reference and is not escaped as '@@' — it throws ExplicitException with this message. The library's syntax requires every literal '@' to be doubled.","triggerScenarios":"Interpolating a string (e.g. build/CI property or job variable expression via Interpolative) that contains a single unescaped '@' not forming a '@variable@' reference — such as an email address 'user@domain' or 'a@b' in the expression.","commonSituations":"Users putting email addresses or '@' symbols in job names, commit messages, or property values that get interpolated; forgetting to escape a literal @; typos like '@variable' missing the closing @; migrating text that previously was not interpolated.","solutions":["Escape every literal @ in the string as @@ (e.g. 'user@@example.com').","Ensure every variable reference is properly closed: '@name@' not '@name'.","If the value comes from user input that must not be interpolated, pass it through without interpolation or pre-escape '@' -> '@@'.","Check the full 'value' shown in the message for stray @ characters before resubmitting."],"exampleFix":"// before\nString expr = \"notify admin@company.com on failure\";\nString result = new Interpolative(...).interpolate(expr);\n// after\nString expr = \"notify admin@@company.com on failure\";\nString result = new Interpolative(...).interpolate(expr);","handlingStrategy":"try-catch","validationCode":"boolean balancedAtSigns(String s) {\n    int count = 0;\n    for (int i = 0; i < s.length(); i++) {\n        if (s.charAt(i) == '@') {\n            if (i + 1 < s.length() && s.charAt(i + 1) == '@') i++;\n            else count++;\n        }\n    }\n    return count % 2 == 0;\n}","typeGuard":null,"tryCatchPattern":"try {\n    result = interpolative.interpolate(value);\n} catch (ExplicitException e) {\n    if (e.getMessage().startsWith(\"Last appearance of @ is a surprise\")) {\n        value = value.replace(\"@\", \"@@\"); // or reject input\n        result = interpolative.interpolate(value);\n    } else throw e;\n}","preventionTips":["Escape every literal @ as @@ in strings that will be interpolated.","Keep variable references balanced: @name@.","Pre-escape user-provided text before interpolation."],"tags":["string-interpolation","escaping","syntax"],"backgroundTag":"invalid-escape-sequence","analyzedSha":"d44925c47c37992c828ea673a5f9620539bc3ff2","analyzedAt":"2026-09-06T07:18:27.995Z","contentChangedAt":"2026-09-06T07:18:27.995Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}