Skip to content

Workflowcheck enforce anonymous func isn't used as local activity#2299

Open
Quinn-With-Two-Ns wants to merge 2 commits intotemporalio:mainfrom
Quinn-With-Two-Ns:issue-1341
Open

Workflowcheck enforce anonymous func isn't used as local activity#2299
Quinn-With-Two-Ns wants to merge 2 commits intotemporalio:mainfrom
Quinn-With-Two-Ns:issue-1341

Conversation

@Quinn-With-Two-Ns
Copy link
Copy Markdown
Contributor

@Quinn-With-Two-Ns Quinn-With-Two-Ns commented Apr 21, 2026

What was changed

Change workflowcheck tool to check any ExecuteLocalActivity in a workflow to make sure a user isn't trying to use an anonymous function as a local activity. Check is essentially best effort

Why?

Anonymous function names are not deterministic so they cannot be used as local activities in workflows.

Checklist

  1. Closes WorkflowChecker should flag anonymous functions in local activities as non deterministic #1341

  2. How was this tested:

  1. Any docs updates needed?

Note

Medium Risk
Adds new static-analysis rules and AST flow heuristics that may introduce false positives/negatives when determining whether a variable holds an anonymous function at a call site.

Overview
workflowcheck now treats passing an anonymous function to workflow.ExecuteLocalActivity as non-deterministic by adding a configurable rule (RejectsAnonymousFuncArgs) to the determinism analyzer and emitting a new ReasonAnonymousFunc diagnostic.

The analysis includes best-effort tracking of whether an identifier argument could contain a func literal via prior assignments/decls (and conservatively through branches), and expands ignore handling to cover ignored RHS assignment call expressions. Testdata is extended with many ExecuteLocalActivity scenarios plus stub ExecuteLocalActivity definitions in workflow/internal packages to compile the new cases.

Reviewed by Cursor Bugbot for commit 356a149. Bugbot is set up for automated code reviews on this repo. Configure here.

@Quinn-With-Two-Ns Quinn-With-Two-Ns requested a review from a team as a code owner April 21, 2026 05:12
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d397981ca4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread contrib/tools/workflowcheck/determinism/checker.go
return false
}
// Don't descend into function literals — assignments inside closures
// that are only defined (not invoked) should not count.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment isn't entirely accurate, right? The function literal could, in fact, be invoked down the line? In the tests below, we're missing this scenario, which I think would fail due to us unconditionally ignoring function literals

  f := myLocalActivity
  g := func() {
      f = func(ctx context.Context) error { return nil }
  }
  g()
  workflow.ExecuteLocalActivity(ctx, f)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is true, but this would add significant complexity to the check since we would need to check if the closure is ever invoked and that can get very complex.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, that's valid. But I would recommend making this comment a little more clear that we're consciously choosing not to evaluate inside of closures, regardless of if they're invoked or not, due to complexity

Comment on lines +157 to +162
if true {
f := func(ctx context.Context) error { return nil }
_ = f
}
// f here is a different variable; this uses myLocalActivity directly
workflow.ExecuteLocalActivity(ctx, myLocalActivity)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test isn't doing what was intended, it seems like you wanted to test shadowing f, but ExecuteLocalActivity is calling myLocalActivity directly, not an outer f

Suggested change
if true {
f := func(ctx context.Context) error { return nil }
_ = f
}
// f here is a different variable; this uses myLocalActivity directly
workflow.ExecuteLocalActivity(ctx, myLocalActivity)
f := myLocalActivity
if true {
f := func(ctx context.Context) error { return nil }
_ = f
}
// f here is a different variable; this uses myLocalActivity directly
workflow.ExecuteLocalActivity(ctx, f)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WorkflowChecker should flag anonymous functions in local activities as non deterministic

2 participants