{"record":{"id":"c53d4cd3d41cf48b","repo":"nektos/act","slug":"unclosed-string","errorCode":null,"errorMessage":"unclosed string.","messagePattern":"unclosed string\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"pkg/runner/expression.go","lineNumber":431,"sourceCode":"\treturn strings.ReplaceAll(strings.ReplaceAll(in, \"{\", \"{{\"), \"}\", \"}}\")\n}\n\nfunc rewriteSubExpression(ctx context.Context, in string, forceFormat bool) (string, error) {\n\tif !strings.Contains(in, \"${{\") || !strings.Contains(in, \"}}\") {\n\t\treturn in, nil\n\t}\n\n\tstrPattern := regexp.MustCompile(\"(?:''|[^'])*'\")\n\tpos := 0\n\texprStart := -1\n\tstrStart := -1\n\tvar results []string\n\tformatOut := \"\"\n\tfor pos < len(in) {\n\t\tif strStart > -1 {\n\t\t\tmatches := strPattern.FindStringIndex(in[pos:])\n\t\t\tif matches == nil {\n\t\t\t\tpanic(\"unclosed string.\")\n\t\t\t}\n\n\t\t\tstrStart = -1\n\t\t\tpos += matches[1]\n\t\t} else if exprStart > -1 {\n\t\t\texprEnd := strings.Index(in[pos:], \"}}\")\n\t\t\tstrStart = strings.Index(in[pos:], \"'\")\n\n\t\t\tif exprEnd > -1 && strStart > -1 {\n\t\t\t\tif exprEnd < strStart {\n\t\t\t\t\tstrStart = -1\n\t\t\t\t} else {\n\t\t\t\t\texprEnd = -1\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif exprEnd > -1 {\n\t\t\t\tformatOut += fmt.Sprintf(\"{%d}\", len(results))","sourceCodeStart":413,"sourceCodeEnd":449,"githubUrl":"https://github.com/nektos/act/blob/4f411281417e88660bea1c1a1749aa71ae0bd60f/pkg/runner/expression.go#L413-L449","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Fix the workflow expression so every opening ' inside ${{ }} has a closing quote","Escape single quotes inside YAML single-quoted scalars by doubling them ('')","Move literal text containing }} out of the expression or into a shell variable","If you maintain act, replace the panic with a returned error in rewriteSubExpression"],"exampleFix":"# before:\nrun: echo ${{ github.event_name == 'push }} more\n# after:\nrun: echo ${{ github.event_name == 'push' }} more","handlingStrategy":"validation","validationCode":"// Block the process boundary: recover from panics in expression rewriting\ndefer func() {\n    if r := recover(); r != nil {\n        err = fmt.Errorf(\"expression rewrite failed (likely unclosed quote): %v\", r)\n    }\n}()\nout, err := rewriteSubExpression(ctx, in, forceFormat)","typeGuard":null,"tryCatchPattern":"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.","preventionTips":["Ensure every ' inside ${{ }} has its closing ' — }} inside a quoted string does not end the expression","Double single-quotes ('') inside single-quoted YAML scalars","Lint with actionlint before running act; it flags unterminated strings without crashing","Never leave a lone backslash-quote at the end of expression values"],"tags":["go","act","expressions","panic","string-escaping","yaml"],"backgroundTag":null,"analyzedSha":"4f411281417e88660bea1c1a1749aa71ae0bd60f","analyzedAt":"2026-08-15T09:19:46.307Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}