Skip to content

fix(invoke): prevent crash in getTokenInfo/getAuthorizedUser functions#54

Merged
JDetmar merged 3 commits intomainfrom
fix/invoke-function-crash
Jan 13, 2026
Merged

fix(invoke): prevent crash in getTokenInfo/getAuthorizedUser functions#54
JDetmar merged 3 commits intomainfrom
fix/invoke-function-crash

Conversation

@JDetmar
Copy link
Copy Markdown
Owner

@JDetmar JDetmar commented Jan 13, 2026

Summary

  • Fix invoke functions (getTokenInfo, getAuthorizedUser) crashing with gRPC connection closing error
  • Add safeGetConfigToken() helper that wraps infer.GetConfig with recover() to handle panics
  • Refactor GetHTTPClient() to check environment variable first (safe path), then fall back to config
  • Add 7 unit tests covering the fix

Root Cause

The infer.GetConfig[*Config](ctx) function panics (not returns nil) when provider config is not available in the context. For invoke functions, which may be called before Configure() completes asynchronously, the config might not be injected into the context yet.

The previous code incorrectly assumed GetConfig returns nil when config is unavailable:

config := infer.GetConfig[*Config](ctx)  // PANICS!
if config != nil { ... }  // Never reached

Test plan

  • All 7 new unit tests pass
  • All 418 existing provider tests pass
  • Linter passes with 0 issues
  • Codegen verified (no schema changes needed - internal refactor)
  • Manual test: Run getTokenInfo invoke function with WEBFLOW_API_TOKEN env var set

🤖 Generated with Claude Code

The invoke functions were crashing with "grpc: the client connection is
closing" because infer.GetConfig() panics (not returns nil) when provider
config is not available in the context. This can happen for invoke functions
called before Configure() completes.

Changes:
- Add safeGetConfigToken() helper with recover() to catch GetConfig panics
- Refactor GetHTTPClient() to check env var first (safe), then config
- Add 7 unit tests covering the fix and edge cases

Fixes issue #3 in ISSUES-TO-FIX.md.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings January 13, 2026 05:05
@github-actions
Copy link
Copy Markdown
Contributor

Does the PR have any schema changes?

Looking good! No breaking changes found.
No new resources/functions.

All 6 issues have been fixed:
1. RegisteredScript Update Returns 404 - PR #51
2. SiteCustomCode Script ID Format - Resolved
3. getTokenInfo/getAuthorizedUser Invoke Crash - PR #54
4. RegisteredScript Version Diff - PR #51
5. Asset Variants Parsing Error - PR #53
6. CollectionItem Slug Uniqueness - PR #49

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a crash in the getTokenInfo and getAuthorizedUser invoke functions by adding panic recovery when accessing provider configuration. The issue occurred because infer.GetConfig panics (rather than returning an error) when called in contexts where provider configuration hasn't been initialized yet, such as during invoke function calls before Configure() completes asynchronously.

Changes:

  • Added safeGetConfigToken() helper function with panic recovery to safely access provider config
  • Refactored GetHTTPClient() to check environment variable first (safe path), then fall back to config with panic recovery
  • Added 7 comprehensive unit tests covering the fix and various edge cases

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
provider/config.go Core fix: adds safeGetConfigToken() with panic recovery and refactors GetHTTPClient() to prioritize env var over config
provider/config_test.go New test file with 7 unit tests validating the fix, including empty context, nil context, env var handling, and panic prevention
ISSUES-TO-FIX.md Updates issue #3 status from "NOT YET INVESTIGATED" to "FIXED" with root cause and solution documentation

Comment on lines 62 to 70
func GetHTTPClient(ctx context.Context, version string) (*http.Client, error) {
// Get config from context
config := infer.GetConfig[*Config](ctx)
// Try environment variable first - this is safe and never panics
// This also handles invoke functions where config may not be available
token := getEnvToken()

// Try to get token from config, fall back to environment variable
token := ""
if config != nil && config.APIToken != "" {
token = config.APIToken
} else {
// Config not available or token empty, try environment variable
token = getEnvToken()
// If no env var, safely try to get from provider config
if token == "" {
token = safeGetConfigToken(ctx)
}
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

This change reverses the precedence order of token sources. Previously, provider config took precedence over the environment variable. Now the environment variable takes precedence over provider config.

This is a breaking change in behavior: if a user has both pulumi config set webflow:apiToken AND WEBFLOW_API_TOKEN environment variable set, the environment variable will now be used instead of the config value.

While this change makes sense for fixing the panic issue and aligns with common practice (env vars often take precedence), it should be documented as a breaking change or the precedence should be maintained by swapping the order back (checking config first with safe recovery, then falling back to env var).

Copilot uses AI. Check for mistakes.
Comment on lines +81 to +95
func TestGetHTTPClient_EnvVarTakesPrecedence(t *testing.T) {
// This test verifies that environment variable is checked first
// Even if config would panic, env var should work
t.Setenv("WEBFLOW_API_TOKEN", "env-token-12345678901234567890")

// Use context.Background() which has no config - this would cause
// infer.GetConfig to panic if we tried to call it directly
ctx := context.Background()

// This should NOT panic because env var is checked first
client, err := GetHTTPClient(ctx, "test-version")

assert.NoError(t, err, "GetHTTPClient should succeed with env var (config not needed)")
assert.NotNil(t, client, "HTTP client should not be nil")
}
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

This test is named "EnvVarTakesPrecedence" but doesn't actually test precedence when both config and env var are present. It only tests that env var works when config is unavailable (context.Background()).

To properly test precedence, you would need to:

  1. Create a context with provider config containing one token value
  2. Set WEBFLOW_API_TOKEN environment variable to a different value
  3. Verify that the environment variable value is used (i.e., takes precedence)

The current test is still valuable for verifying the panic fix, but the name is misleading. Consider renaming it to something like "TestGetHTTPClient_EnvVarWorksWithoutConfig" or adding a proper precedence test.

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +49
func safeGetConfigToken(ctx context.Context) (token string) {
defer func() {
if r := recover(); r != nil {
// GetConfig panicked - config not available in context
// This can happen for invoke functions called before Configure()
token = ""
}
}()
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

The panic recovery here will catch ALL panics that occur within this function, not just panics from infer.GetConfig. This means if there's a programming error elsewhere in this function (like a nil pointer dereference), it will be silently converted to an empty token return, making debugging harder.

Consider adding logging or being more specific about what panics are expected. For example, you could check the recovered value and only handle expected panic types, re-panicking for unexpected ones.

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +40
func TestSafeGetConfigToken_NilContext(t *testing.T) {
// Test with nil context - should not panic
// Note: This is an edge case that shouldn't happen in practice
defer func() {
if r := recover(); r != nil {
t.Errorf("safeGetConfigToken panicked with nil context: %v", r)
}
}()

//nolint:staticcheck // SA1012: intentionally passing nil context to test panic recovery
token := safeGetConfigToken(nil)
assert.Equal(t, "", token)
}

Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

Passing nil context violates the Go context best practices. While the test includes a nolint directive, testing with nil context may not accurately represent real-world behavior. The comment suggests this is an edge case that "shouldn't happen in practice" - if it truly shouldn't happen, consider removing this test case entirely, as it adds test complexity without value.

If you want to keep it for defensive programming, the current implementation is adequate with the staticcheck suppression.

Suggested change
func TestSafeGetConfigToken_NilContext(t *testing.T) {
// Test with nil context - should not panic
// Note: This is an edge case that shouldn't happen in practice
defer func() {
if r := recover(); r != nil {
t.Errorf("safeGetConfigToken panicked with nil context: %v", r)
}
}()
//nolint:staticcheck // SA1012: intentionally passing nil context to test panic recovery
token := safeGetConfigToken(nil)
assert.Equal(t, "", token)
}

Copilot uses AI. Check for mistakes.
- Remove TestSafeGetConfigToken_NilContext (edge case without value)
- Rename TestGetHTTPClient_EnvVarTakesPrecedence to
  TestGetHTTPClient_EnvVarWorksWithoutConfig (more accurate name)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@JDetmar JDetmar merged commit 336dbfa into main Jan 13, 2026
4 checks passed
@JDetmar JDetmar deleted the fix/invoke-function-crash branch January 13, 2026 05:20
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.

2 participants