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

  1. Escape every literal @ in the string as @@ (e.g. 'user@@example.com').
  2. Ensure every variable reference is properly closed: '@name@' not '@name'.
  3. If the value comes from user input that must not be interpolated, pass it through without interpolation or pre-escape '@' -> '@@'.
  4. 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

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.