From fb88a4d2be4d867bc29d74c8d020959014dd22f0 Mon Sep 17 00:00:00 2001 From: Michael Brooks Date: Mon, 28 Sep 2026 16:47:36 -0700 Subject: [PATCH 1/2] fix: stop polling activity logs after an auth error Tailing activity logs with `slack activity --tail` or `slack run` now returns when the activity API responds with invalid_auth, not_authed, token_expired, or token_revoked instead of polling again every few seconds until the idle timeout. --- internal/pkg/platform/activity.go | 18 +++++++++++- internal/pkg/platform/activity_test.go | 40 ++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/internal/pkg/platform/activity.go b/internal/pkg/platform/activity.go index 582acc32..655b3f49 100644 --- a/internal/pkg/platform/activity.go +++ b/internal/pkg/platform/activity.go @@ -108,9 +108,13 @@ func Activity( // Try to grab new logs using the last logs timestamp activityRequest.MinimumDateCreated = latestCreatedTimestamp + 1 - // Avoid exit on error from the activity API when tailing logs + // Avoid exit on error from the activity API when tailing logs, unless + // the token can no longer be used and every later request would fail newLatestCreatedTimestamp, count, err := printLatestActivity(ctx, clients, token, activityRequest, token) if err != nil { + if isAuthError(err) { + return err + } clients.IO.PrintDebug(ctx, "%s\n", err) } @@ -132,6 +136,18 @@ func Activity( } } +// isAuthError returns true if the error means the token is no longer valid +func isAuthError(err error) bool { + switch slackerror.ToSlackError(err).Code { + case slackerror.ErrInvalidAuth, + slackerror.ErrNotAuthed, + slackerror.ErrTokenExpired, + slackerror.ErrTokenRevoked: + return true + } + return false +} + func printLatestActivity(ctx context.Context, clients *shared.ClientFactory, token string, args types.ActivityRequest, xoxpToken string) (latestCreated int64, num int, e error) { var span opentracing.Span span, ctx = opentracing.StartSpanFromContext(ctx, "getLatestActivity") diff --git a/internal/pkg/platform/activity_test.go b/internal/pkg/platform/activity_test.go index e8542309..63fdfe67 100644 --- a/internal/pkg/platform/activity_test.go +++ b/internal/pkg/platform/activity_test.go @@ -246,6 +246,27 @@ func TestPlatformActivity_StreamingLogs(t *testing.T) { cm.API.AssertNumberOfCalls(t, "Activity", 1) }, }, + "should return error if TailArg is set and activity request fails with an auth error while polling": { + Args: types.ActivityArgs{ + TailArg: true, + IdleTimeoutM: 1, + PollingIntervalMS: 20, // poll activity every 20 ms + }, + Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) context.Context { + cm.API.On("Activity", mock.Anything, mock.Anything, mock.Anything).Return(api.ActivityResult{}, slackerror.New(slackerror.ErrTokenRevoked)) + ctx, cancel := context.WithCancel(ctx) + go func() { + time.Sleep(time.Millisecond * 50) // cancel activity in 50 ms + cancel() + }() + return ctx + }, + ExpectedError: slackerror.New(slackerror.ErrTokenRevoked), + ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) { + // the initial request and the first polling request, then polling stops + cm.API.AssertNumberOfCalls(t, "Activity", 2) + }, + }, "should return nil if TailArg is set and activity request fails while polling": { Args: types.ActivityArgs{ TailArg: true, @@ -909,6 +930,25 @@ func Test_functionExecutionStartedToString(t *testing.T) { assert.Contains(t, result, "Trace=trace1") } +func Test_isAuthError(t *testing.T) { + tests := map[string]struct { + err error + expected bool + }{ + "invalid auth": {err: slackerror.New(slackerror.ErrInvalidAuth), expected: true}, + "not authed": {err: slackerror.New(slackerror.ErrNotAuthed), expected: true}, + "token expired": {err: slackerror.New(slackerror.ErrTokenExpired), expected: true}, + "token revoked": {err: slackerror.New(slackerror.ErrTokenRevoked), expected: true}, + "internal error": {err: slackerror.New(slackerror.ErrInternal), expected: false}, + "unknown error": {err: slackerror.New("mock_broken_logs"), expected: false}, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tc.expected, isAuthError(tc.err)) + }) + } +} + func Test_triggerPayloadReceivedOutputToString(t *testing.T) { activity := api.Activity{ Level: types.INFO, From c6f5f57650a6134fa20c139a97661d2ea04cec2a Mon Sep 17 00:00:00 2001 From: Michael Brooks Date: Tue, 29 Sep 2026 09:55:37 -0700 Subject: [PATCH 2/2] chore: trigger ci with updated e2e tests