nektos/act · critical

unclosed string.

Error message

unclosed string.

What it means

This is a panic, not a returned error: rewriteSubExpression (pkg/runner/expression.go:431) panics with 'unclosed string.' when, after entering a single-quoted string inside a ${{ }} expression, it cannot find a closing quote matching the strPattern `(?:''|[^'])*'` anywhere in the remaining input. The panic crashes the act process instead of surfacing as a workflow failure.

Source

Thrown at pkg/runner/expression.go:431

	return strings.ReplaceAll(strings.ReplaceAll(in, "{", "{{"), "}", "}}")
}

func rewriteSubExpression(ctx context.Context, in string, forceFormat bool) (string, error) {
	if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") {
		return in, nil
	}

	strPattern := regexp.MustCompile("(?:''|[^'])*'")
	pos := 0
	exprStart := -1
	strStart := -1
	var results []string
	formatOut := ""
	for pos < len(in) {
		if strStart > -1 {
			matches := strPattern.FindStringIndex(in[pos:])
			if matches == nil {
				panic("unclosed string.")
			}

			strStart = -1
			pos += matches[1]
		} else if exprStart > -1 {
			exprEnd := strings.Index(in[pos:], "}}")
			strStart = strings.Index(in[pos:], "'")

			if exprEnd > -1 && strStart > -1 {
				if exprEnd < strStart {
					strStart = -1
				} else {
					exprEnd = -1
				}
			}

			if exprEnd > -1 {
				formatOut += fmt.Sprintf("{%d}", len(results))

View on GitHub (pinned to 4f41128141)

Solutions

  1. Fix the workflow expression so every opening ' inside ${{ }} has a closing quote
  2. Escape single quotes inside YAML single-quoted scalars by doubling them ('')
  3. Move literal text containing }} out of the expression or into a shell variable
  4. If you maintain act, replace the panic with a returned error in rewriteSubExpression

Example fix

# before:
run: echo ${{ github.event_name == 'push }} more
# after:
run: echo ${{ github.event_name == 'push' }} more
Defensive patterns

Strategy: validation

Validate before calling

// Block the process boundary: recover from panics in expression rewriting
defer func() {
    if r := recover(); r != nil {
        err = fmt.Errorf("expression rewrite failed (likely unclosed quote): %v", r)
    }
}()
out, err := rewriteSubExpression(ctx, in, forceFormat)

Try / catch

Wrap the call site with defer/recover in Go (act itself panics rather than returning an error), convert to an error naming the offending step, then fix the quote in the workflow.

Prevention

When it happens

Trigger: An expression value containing an opening single quote inside ${{ }} with no terminating quote before end of input, e.g. `run: echo ${{ env.NAME == 'unclosed }}` — the }} inside the prospective string is treated as string content, so the scanner runs to EOF and panics. Any step field (run, if, env values) passing through rewriteSubExpression triggers it.

Common situations: Typos dropping a closing quote in expressions; YAML single-quote escaping mistakes (a lone ' instead of '') that leave the expression parser mid-string; strings that contain }} but were meant to be outside the expression.

Related errors


AI-assisted analysis of nektos/act@4f41128141 (2026-08-15). Data as JSON: /api/errors/c53d4cd3d41cf48b. Report an issue: GitHub.