theonedev/onedev · error · ExplicitException
Last appearance of @ is a surprise to me. Either use @...@ t
Error message
Last appearance of @ is a surprise to me. Either use @...@ to reference a variable, or use @@ for literal @: ${value} What it means
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.
Source
Thrown at server-core/src/main/java/io/onedev/server/util/interpolative/Interpolative.java:42
private static final long serialVersionUID = 1L;
private final List<Segment> segments;
public Interpolative(List<Segment> segments) {
this.segments = segments;
}
public static Interpolative parse(String value) {
CharStream is = CharStreams.fromString(value);
InterpolativeLexer lexer = new InterpolativeLexer(is);
lexer.removeErrorListeners();
lexer.addErrorListener(new BaseErrorListener() {
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line,
int charPositionInLine, String msg, RecognitionException e) {
throw new ExplicitException("Last appearance of @ is a surprise to me. Either use @...@ to reference "
+ "a variable, or use @@ for literal @: " + value);
}
});
CommonTokenStream tokens = new CommonTokenStream(lexer);
InterpolativeParser parser = new InterpolativeParser(tokens);
parser.removeErrorListeners();
parser.setErrorHandler(new BailErrorStrategy());
List<Segment> segments = new ArrayList<>();
for (SegmentContext segment: parser.interpolative().segment()) {
if (segment.Literal() != null)
segments.add(new Segment(Type.LITERAL, segment.Literal().getText().replace("@@", "@")));
else
segments.add(new Segment(Type.VARIABLE, FenceAware.unfence(segment.Variable().getText())));
}
return new Interpolative(segments);
}View on GitHub (pinned to d44925c47c)
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.
Example fix
// before String expr = "notify admin@company.com on failure"; String result = new Interpolative(...).interpolate(expr); // after String expr = "notify admin@@company.com on failure"; String result = new Interpolative(...).interpolate(expr);
Defensive patterns
Strategy: try-catch
Validate before calling
boolean balancedAtSigns(String s) {
int count = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '@') {
if (i + 1 < s.length() && s.charAt(i + 1) == '@') i++;
else count++;
}
}
return count % 2 == 0;
} Try / catch
try {
result = interpolative.interpolate(value);
} catch (ExplicitException e) {
if (e.getMessage().startsWith("Last appearance of @ is a surprise")) {
value = value.replace("@", "@@"); // or reject input
result = interpolative.interpolate(value);
} else throw e;
} Prevention
- Escape every literal @ as @@ in strings that will be interpolated.
- Keep variable references balanced: @name@.
- Pre-escape user-provided text before interpolation.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of theonedev/onedev@d44925c47c (2026-09-06).
Data as JSON: /api/errors/6cc2dd50c43d0dad.
Report an issue: GitHub.