{"record":{"id":"6ddce567d0520985","repo":"nektos/act","slug":"errshortref","errorCode":"ErrShortRef","errorMessage":"short SHA references are not supported","messagePattern":"short SHA references are not supported","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/common/git/git.go","lineNumber":33,"sourceCode":"\t\"github.com/go-git/go-git/v5/config\"\n\t\"github.com/go-git/go-git/v5/plumbing\"\n\t\"github.com/go-git/go-git/v5/plumbing/storer\"\n\t\"github.com/go-git/go-git/v5/plumbing/transport/http\"\n\t\"github.com/mattn/go-isatty\"\n\tlog \"github.com/sirupsen/logrus\"\n\n\t\"github.com/nektos/act/pkg/common\"\n)\n\nvar (\n\tcodeCommitHTTPRegex = regexp.MustCompile(`^https?://git-codecommit\\.(.+)\\.amazonaws.com/v1/repos/(.+)$`)\n\tcodeCommitSSHRegex  = regexp.MustCompile(`ssh://git-codecommit\\.(.+)\\.amazonaws.com/v1/repos/(.+)$`)\n\tgithubHTTPRegex     = regexp.MustCompile(`^https?://.*github.com.*/(.+)/(.+?)(?:.git)?$`)\n\tgithubSSHRegex      = regexp.MustCompile(`github.com[:/](.+)/(.+?)(?:.git)?$`)\n\n\tcloneLock sync.Mutex\n\n\tErrShortRef = errors.New(\"short SHA references are not supported\")\n\tErrNoRepo   = errors.New(\"unable to find git repo\")\n)\n\ntype Error struct {\n\terr    error\n\tcommit string\n}\n\nfunc (e *Error) Error() string {\n\treturn e.err.Error()\n}\n\nfunc (e *Error) Unwrap() error {\n\treturn e.err\n}\n\nfunc (e *Error) Commit() string {\n\treturn e.commit","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/nektos/act/blob/4f411281417e88660bea1c1a1749aa71ae0bd60f/pkg/common/git/git.go#L15-L51","documentation":"This panic comes from act's expression pre-processor, rewriteSubExpression (pkg/runner/expression.go:416). Before evaluating, act rewrites every string that contains both '${{' and '}}' into a format(...) call by scanning for '${{' ... '}}' pairs while tracking single-quoted string literals. The panic at line 456 fires when the scanner is inside an expression (it has consumed a '${{') but can find neither a closing '}}' nor a single-quote string start ahead of the current position, so the expression can never be terminated. It is raised via panic(), not returned as an error, so it crashes the whole act run with a stack trace instead of producing a per-step failure.","triggerScenarios":"Specifically: (1) the input contains a '${{' whose matching '}}' appears BEFORE it, e.g. 'echo }} ${{ github.ref' — the cheap Contains pre-check at line 417 passes because '}}' exists somewhere, but after the '${{' there is no closer and no quote → panic. (2) The expression opens a single-quoted literal but never closes the expression, e.g. \"${{ contains(github.ref, 'refs/\" — after the string literal is consumed, no '}}' and no further quote remain → panic. Note a plain '${{ failure()' with NO '}}' anywhere does NOT panic here; it slips past the pre-check and fails later in the exprparser with a normal error.","commonSituations":"Hand-edited workflow YAML in .github/workflows/ with a missing '}}' (often after copy-paste or a brace-eating template tool); shell steps whose script body contains a literal '}}' (awk blocks, heredocs, 'cp foo{,}}'-style text) plus a '{{' that never got closed; composite action inputs or env values interpolating '${{' inside single quotes that are themselves unbalanced; regressions when migrating workflows where a linter stripped one brace of a pair; any place act evaluates user-supplied strings (run, if, env, with, name) through the interpolation path.","solutions":["Inspect the exact string in the panic's stack context (the run/if/env/with value act was rewriting) and add the missing '}}' to close the '${{' expression, e.g. '${{ github.ref' → '${{ github.ref }}'.","If the '}}' the scanner saw was a literal one from shell text (awk '{{ ... }}', heredoc), quote or rewrite that literal text so it does not appear before an unclosed '${{', or close the '${{' properly so pairing is unambiguous.","Check single-quote balance inside the expression: every ' literal must be closed before the '}}', e.g. \"${{ contains(github.ref, 'refs/heads') }}\".","Run 'act' with the workflow after fixing and confirm the step proceeds; as a guard, grep your workflows for '${{' occurrences lacking a following '}}' on the same string (yq '.jobs[].steps[] | .run, .if, .env' style extraction helps).","If you are embedding '${{' as literal text (not meant to be evaluated), you cannot leave it unclosed in the same string with a stray '}}' — restructure the value or generate the brace at runtime from shell (e.g. printing '${' twice) so act never sees '${{{' patterns."],"exampleFix":"# before (workflow YAML)\nsteps:\n  - name: broken\n    run: |\n      awk '{{ print $1 }}' file.txt   # stray }} from awk braces\n      echo \"${{ env.BRANCH\"            # unclosed ${{  → panic(\"unclosed expression.\")\n\n# after\nsteps:\n  - name: fixed\n    run: |\n      awk '{{ print $1 }}' file.txt\n      echo \"${{ env.BRANCH }}\"         # expression closed, pairs match","handlingStrategy":"validation","validationCode":"// Validate a workflow string before act evaluates it: every '${{' must be\n// followed (after balanced single quotes) by a '}}'.\nfunc hasUnclosedExpression(s string) bool {\n    if !strings.Contains(s, \"${{\") {\n        return false\n    }\n    pos := 0\n    for pos < len(s) {\n        start := strings.Index(s[pos:], \"${{\")\n        if start == -1 {\n            return false // no more expressions\n        }\n        pos += start + 3\n        inString := false\n        for pos < len(s) {\n            if inString {\n                q := strings.IndexByte(s[pos:], '\\'')\n                if q == -1 {\n                    return true // unclosed string inside expression\n                }\n                pos += q + 1\n                inString = false\n                continue\n            }\n            if strings.HasPrefix(s[pos:], \"}}\") {\n                pos += 2\n                break\n            }\n            if s[pos] == '\\'' {\n                inString = true\n                pos++\n                continue\n            }\n            pos++\n        }\n        if inString || (pos >= len(s) && !strings.HasSuffix(s[:pos], \"}}\")) {\n            // loop ran off the end without finding }}\n            if !strings.Contains(s[pos:], \"}}\") {\n                return true\n            }\n        }\n    }\n    return false\n}\n\n// usage: reject the step value before running act\nif hasUnclosedExpression(step.Run) {\n    return fmt.Errorf(\"step %q has an unclosed ${{ expression\", step.Name)\n}","typeGuard":"// Simplest sufficient guard for this panic: the closer '}}' must appear\n// AFTER the last '${{' opener (quote handling aside, this catches the\n// scanner-panic shape where the only '}}' precedes the '${{').\nfunc expressionLooksClosed(s string) bool {\n    lastOpen := strings.LastIndex(s, \"${{\")\n    if lastOpen == -1 {\n        return true\n    }\n    return strings.Contains(s[lastOpen:], \"}}\")\n}","tryCatchPattern":"// rewriteSubExpression panics instead of returning an error, so Go callers\n// who feed strings into act's evaluation path (EvalBool/EvalString/evaluators)\n// can only contain it with recover:\nfunc safeEvalBool(ctx context.Context, ev ExpressionEvaluator, expr string, d exprparser.DefaultStatusCheck) (b bool, err error) {\n    defer func() {\n        if r := recover(); r != nil {\n            err = fmt.Errorf(\"invalid expression %q: %v\", expr, r)\n        }\n    }()\n    return EvalBool(ctx, ev, expr, d)\n}\n// Prefer pre-validating the string (see validationCode) over relying on recover;\n// treat a panic here as a workflow-authoring bug to fix in the YAML, not a\n// runtime condition to retry.","preventionTips":["Lint workflows for balanced delimiters: every '${{' needs a matching '}}' in the same string value; add this check to CI (actionlint also catches malformed expressions).","When embedding shell text that legitimately contains '}}' (awk, brace expansion), make sure any '${{' in the same string is fully closed before it or restructure the script so the two never coexist in one value.","Keep single quotes inside expressions balanced ('${{ contains(x, 'y') }}', never a dangling quote).","Never paste '${{' into run/if/env/with as literal text expecting it to pass through; act always tries to interpolate it.","If you maintain code that passes user input into act's expression evaluation, run the closer-after-opener guard (typeGuard) before evaluation and reject with a clear error instead of letting act panic."],"tags":["github-actions","expression-parsing","yaml","panic","act","workflow-syntax"],"backgroundTag":null,"analyzedSha":"4f411281417e88660bea1c1a1749aa71ae0bd60f","analyzedAt":"2026-08-15T09:19:46.307Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}