diff --git a/packages/core/src/source/Source.ts b/packages/core/src/source/Source.ts
index 68d141803..ddc813b92 100644
--- a/packages/core/src/source/Source.ts
+++ b/packages/core/src/source/Source.ts
@@ -72,6 +72,16 @@ export class ReadonlySource {
 		return this.string.slice(this.innerCursor + offset, this.innerCursor + offset + length)
 	}
 
+	canRead(length = 1) {
+		const needed = this.innerCursor + length
+		const available = this.string.length
+		return this.innerCursor + length <= this.string.length
+	}
+
+	canReadInLine() {
+		return this.canRead() && this.peek() !== CR && this.peek() !== LF
+	}
+
 	/**
 	 * If the `expectedValue` is right after the cursor, returns `true`. Otherwise returns `false`.
 	 *
@@ -109,12 +119,12 @@ export class ReadonlySource {
 		return this.peekUntil(CR, LF)
 	}
 
-	peekRemaining(): string {
-		return this.string.slice(this.innerCursor)
+	peekRemaining(offset = 0): string {
+		return this.string.slice(this.innerCursor + offset)
 	}
 
-	matchPattern(regex: RegExp): boolean {
-		return regex.test(this.peekRemaining())
+	matchPattern(regex: RegExp, offset = 0): boolean {
+		return regex.test(this.peekRemaining(offset))
 	}
 
 	/**
@@ -172,14 +182,6 @@ export class Source extends ReadonlySource {
 		return ans
 	}
 
-	canRead(length = 1) {
-		return this.innerCursor + length <= this.string.length
-	}
-
-	canReadInLine() {
-		return this.canRead() && this.peek() !== CR && this.peek() !== LF
-	}
-
 	read() {
 		return this.string.charAt(this.innerCursor++)
 	}
diff --git a/packages/java-edition/src/mcfunction/completer/argument.ts b/packages/java-edition/src/mcfunction/completer/argument.ts
index be1b6c484..3d581b9d9 100644
--- a/packages/java-edition/src/mcfunction/completer/argument.ts
+++ b/packages/java-edition/src/mcfunction/completer/argument.ts
@@ -433,6 +433,7 @@ const scoreHolder: Completer<ScoreHolderNode> = (node, ctx) => {
 			ctx,
 		)
 		ans.push(
+			...completer.literal(LiteralNode.mock(node, { pool: ['*'] }), ctx),
 			...selector(
 				EntitySelectorNode.mock(node, { pool: EntitySelectorAtVariable.filterAvailable(ctx) }),
 				ctx,
diff --git a/packages/java-edition/src/mcfunction/parser/argument.ts b/packages/java-edition/src/mcfunction/parser/argument.ts
index a9e7f3359..c5a3fb234 100644
--- a/packages/java-edition/src/mcfunction/parser/argument.ts
+++ b/packages/java-edition/src/mcfunction/parser/argument.ts
@@ -1406,8 +1406,12 @@ export function scoreHolder(
 ): core.Parser<ScoreHolderNode> {
 	return core.map<core.LiteralNode | core.SymbolNode | EntitySelectorNode, ScoreHolderNode>(
 		core.select([
-			// Technically score holders can start with *, but this doesn't account for it
-			{ prefix: '*', parser: core.literal('*') },
+			{
+				predicate: (src) =>
+					src.peek() === '*'
+					&& (!src.canRead(2) || src.matchPattern(/^\s/, 1)),
+				parser: core.literal('*'),
+			},
 			{ prefix: '@', parser: selector() },
 			{ parser: scoreHolderFakeName(usageType) },
 		]),