Skip to content

Commit 23baa9e

Browse files
zimegClaude
andauthored
chore(experiment): remove defaulted bolt and bolt-install experiments (#363)
Co-authored-by: Claude <svc-devxp-claude@slack-corp.com>
1 parent 8700073 commit 23baa9e

File tree

9 files changed

+130
-217
lines changed

9 files changed

+130
-217
lines changed

cmd/app/add.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"github.com/slackapi/slack-cli/internal/app"
2121
"github.com/slackapi/slack-cli/internal/cmdutil"
2222
"github.com/slackapi/slack-cli/internal/config"
23-
"github.com/slackapi/slack-cli/internal/experiment"
2423
"github.com/slackapi/slack-cli/internal/pkg/apps"
2524
"github.com/slackapi/slack-cli/internal/prompts"
2625
"github.com/slackapi/slack-cli/internal/shared"
@@ -83,9 +82,6 @@ func preRunAddCommand(ctx context.Context, clients *shared.ClientFactory, cmd *c
8382
if err != nil {
8483
return err
8584
}
86-
if !clients.Config.WithExperimentOn(experiment.BoltFrameworks) {
87-
return nil
88-
}
8985
clients.Config.SetFlags(cmd)
9086
return nil
9187
}

cmd/app/add_test.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"github.com/slackapi/slack-cli/internal/cache"
2424
"github.com/slackapi/slack-cli/internal/cmdutil"
2525
"github.com/slackapi/slack-cli/internal/config"
26-
"github.com/slackapi/slack-cli/internal/experiment"
2726
"github.com/slackapi/slack-cli/internal/iostreams"
2827
"github.com/slackapi/slack-cli/internal/prompts"
2928
"github.com/slackapi/slack-cli/internal/shared"
@@ -85,25 +84,21 @@ func TestAppAddCommandPreRun(t *testing.T) {
8584
cf.SDKConfig.WorkingDirectory = "."
8685
},
8786
},
88-
"proceeds if manifest.source is local with the bolt experiment": {
87+
"proceeds if manifest.source is local": {
8988
ExpectedError: nil,
9089
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
9190
cf.SDKConfig.WorkingDirectory = "."
9291
cm.AddDefaultMocks()
93-
cm.Config.ExperimentsFlag = append(cm.Config.ExperimentsFlag, string(experiment.BoltFrameworks))
94-
cm.Config.LoadExperiments(ctx, cm.IO.PrintDebug)
9592
mockProjectConfig := config.NewProjectConfigMock()
9693
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
9794
cm.Config.ProjectConfig = mockProjectConfig
9895
},
9996
},
100-
"proceeds if manifest.source is remote with the bolt experiment": {
97+
"proceeds if manifest.source is remote": {
10198
ExpectedError: nil,
10299
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
103100
cf.SDKConfig.WorkingDirectory = "."
104101
cm.AddDefaultMocks()
105-
cm.Config.ExperimentsFlag = append(cm.Config.ExperimentsFlag, string(experiment.BoltFrameworks))
106-
cm.Config.LoadExperiments(ctx, cm.IO.PrintDebug)
107102
mockProjectConfig := config.NewProjectConfigMock()
108103
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceRemote, nil)
109104
cm.Config.ProjectConfig = mockProjectConfig
@@ -207,6 +202,7 @@ func TestAppAddCommand(t *testing.T) {
207202
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
208203
Return(nil)
209204
mockProjectConfig := config.NewProjectConfigMock()
205+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
210206
mockProjectConfig.On("Cache").Return(mockProjectCache)
211207
cm.Config.ProjectConfig = mockProjectConfig
212208
},
@@ -279,6 +275,7 @@ func TestAppAddCommand(t *testing.T) {
279275
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
280276
Return(nil)
281277
mockProjectConfig := config.NewProjectConfigMock()
278+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
282279
mockProjectConfig.On("Cache").Return(mockProjectCache)
283280
cm.Config.ProjectConfig = mockProjectConfig
284281
},
@@ -343,7 +340,7 @@ func TestAppAddCommand(t *testing.T) {
343340
nil,
344341
)
345342

346-
// Mock existing and updated cache
343+
// Mock existing and updated cache - hashes must match for update to proceed
347344
cm.API.On(
348345
"ExportAppManifest",
349346
mock.Anything,
@@ -357,10 +354,11 @@ func TestAppAddCommand(t *testing.T) {
357354
mockProjectCache.On("GetManifestHash", mock.Anything, mock.Anything).
358355
Return(cache.Hash("b4b4"), nil)
359356
mockProjectCache.On("NewManifestHash", mock.Anything, mock.Anything).
360-
Return(cache.Hash("xoxo"), nil)
357+
Return(cache.Hash("b4b4"), nil) // matching hash allows update to proceed
361358
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
362359
Return(nil)
363360
mockProjectConfig := config.NewProjectConfigMock()
361+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
364362
mockProjectConfig.On("Cache").Return(mockProjectCache)
365363
cm.Config.ProjectConfig = mockProjectConfig
366364
},
@@ -428,6 +426,7 @@ func TestAppAddCommand(t *testing.T) {
428426
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
429427
Return(nil)
430428
mockProjectConfig := config.NewProjectConfigMock()
429+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
431430
mockProjectConfig.On("Cache").Return(mockProjectCache)
432431
cm.Config.ProjectConfig = mockProjectConfig
433432
},
@@ -515,6 +514,7 @@ func TestAppAddCommand(t *testing.T) {
515514
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
516515
Return(nil)
517516
mockProjectConfig := config.NewProjectConfigMock()
517+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
518518
mockProjectConfig.On("Cache").Return(mockProjectCache)
519519
cm.Config.ProjectConfig = mockProjectConfig
520520
},
@@ -599,6 +599,7 @@ func TestAppAddCommand(t *testing.T) {
599599
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
600600
Return(nil)
601601
mockProjectConfig := config.NewProjectConfigMock()
602+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
602603
mockProjectConfig.On("Cache").Return(mockProjectCache)
603604
cm.Config.ProjectConfig = mockProjectConfig
604605
},
@@ -685,6 +686,7 @@ func TestAppAddCommand(t *testing.T) {
685686
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
686687
Return(nil)
687688
mockProjectConfig := config.NewProjectConfigMock()
689+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
688690
mockProjectConfig.On("Cache").Return(mockProjectCache)
689691
cm.Config.ProjectConfig = mockProjectConfig
690692
},
@@ -760,6 +762,7 @@ func TestAppAddCommand(t *testing.T) {
760762
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
761763
Return(nil)
762764
mockProjectConfig := config.NewProjectConfigMock()
765+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
763766
mockProjectConfig.On("Cache").Return(mockProjectCache)
764767
cm.Config.ProjectConfig = mockProjectConfig
765768
},
@@ -816,6 +819,7 @@ func TestAppAddCommand(t *testing.T) {
816819
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
817820
Return(nil)
818821
mockProjectConfig := config.NewProjectConfigMock()
822+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
819823
mockProjectConfig.On("Cache").Return(mockProjectCache)
820824
cm.Config.ProjectConfig = mockProjectConfig
821825
},
@@ -872,6 +876,7 @@ func TestAppAddCommand(t *testing.T) {
872876
mockProjectCache.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).
873877
Return(nil)
874878
mockProjectConfig := config.NewProjectConfigMock()
879+
mockProjectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
875880
mockProjectConfig.On("Cache").Return(mockProjectCache)
876881
cm.Config.ProjectConfig = mockProjectConfig
877882
},

docs/reference/experiments.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,14 @@ The Slack CLI has an experiment (`-e`) flag behind which we put features current
66

77
The following is a list of currently available experiments. We'll remove experiments from this page if we decide they are no longer needed or once they are released, in which case we'll make an announcement about the feature's general availability in the [developer changelog](https://docs.slack.dev/changelog).
88

9-
- `bolt-install`: enables creating, installing, and running Bolt projects that manage their app manifest on app settings (remote manifest).
10-
- `slack create` and `slack init` now set manifest source to "app settings" (remote) for Bolt JS & Bolt Python projects ([PR#96](https://github.com/slackapi/slack-cli/pull/96)).
11-
- `slack run` and `slack install` support creating and installing Bolt Framework apps that have the manifest source set to "app settings (remote)" ([PR#111](https://github.com/slackapi/slack-cli/pull/111), [PR#154](https://github.com/slackapi/slack-cli/pull/154)).
129
- `charm`: shows beautiful prompts ([PR#348](https://github.com/slackapi/slack-cli/pull/348)).
1310
- `sandboxes`: enables users who have joined the Slack Developer Program to manage their sandboxes ([PR#379](https://github.com/slackapi/slack-cli/pull/379)).
1411

1512
## Experiments changelog
1613

1714
Below is a list of updates related to experiments.
1815

16+
- **March 2026**: Concluded the `bolt` and `bolt-install` experiments with full Bolt framework support now enabled by default in the Slack CLI. All Bolt project features including remote manifest management are now standard functionality. See the announcement [here](https://slack.dev/slackcli-supports-bolt-apps/).
1917
- **February 2026**: Added the `charm` experiment.
2018
- **December 2025**: Concluded the `read-only-collaborators` experiment with full support introduced to the Slack CLI. See the changelog announcement [here](https://docs.slack.dev/changelog/2025/12/04/slack-cli).
2119
- **June 2025**:

internal/experiment/experiment.go

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,6 @@ type Experiment string
3030
// e.g. --experiment=first-toggle,second-toggle
3131

3232
const (
33-
// BoltFrameworks experiment adds CLI support for Bolt JavaScript & Bolt Python.
34-
// These frameworks will be introducing remote function support.
35-
BoltFrameworks Experiment = "bolt"
36-
37-
// BoltInstall experiment enables developerInstall to work with Bolt projects that
38-
// manage their app manifest on app settings (remote manifest).
39-
BoltInstall Experiment = "bolt-install"
40-
4133
// Charm experiment enables beautiful prompts.
4234
Charm Experiment = "charm"
4335

@@ -51,19 +43,14 @@ const (
5143
// AllExperiments is a list of all available experiments that can be enabled
5244
// Please also add here 👇
5345
var AllExperiments = []Experiment{
54-
BoltFrameworks,
55-
BoltInstall,
5646
Charm,
5747
Placeholder,
5848
Sandboxes,
5949
}
6050

6151
// EnabledExperiments is a list of experiments that are permanently enabled
6252
// Please also add here 👇
63-
var EnabledExperiments = []Experiment{
64-
BoltFrameworks,
65-
BoltInstall,
66-
}
53+
var EnabledExperiments = []Experiment{}
6754

6855
// Includes checks that a supplied experiment is included within AllExperiments
6956
func Includes(expToCheck Experiment) bool {

internal/experiment/experiment_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ func Test_Includes(t *testing.T) {
2525
require.Equal(t, true, Includes(Experiment(Placeholder)))
2626

2727
// Test expected experiments
28-
require.Equal(t, true, Includes(Experiment("bolt")))
2928
require.Equal(t, true, Includes(Experiment("charm")))
3029

3130
// Test invalid experiment

internal/pkg/apps/install.go

Lines changed: 21 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"github.com/opentracing/opentracing-go"
2525
"github.com/slackapi/slack-cli/internal/api"
2626
"github.com/slackapi/slack-cli/internal/config"
27-
"github.com/slackapi/slack-cli/internal/experiment"
2827
"github.com/slackapi/slack-cli/internal/pkg/manifest"
2928
"github.com/slackapi/slack-cli/internal/shared"
3029
"github.com/slackapi/slack-cli/internal/shared/types"
@@ -54,12 +53,6 @@ func Install(ctx context.Context, clients *shared.ClientFactory, auth types.Slac
5453
return types.App{}, "", err
5554
}
5655

57-
if !clients.Config.WithExperimentOn(experiment.BoltInstall) {
58-
if !manifestUpdates && !manifestCreates {
59-
return app, "", nil
60-
}
61-
}
62-
6356
// Get the token for the authenticated workspace
6457
apiInterface := clients.API()
6558
token := auth.Token
@@ -83,31 +76,21 @@ func Install(ctx context.Context, clients *shared.ClientFactory, auth types.Slac
8376
app.EnterpriseID = *authSession.EnterpriseID
8477
}
8578

86-
// When the BoltInstall experiment is enabled, we need to get the manifest from the local file
87-
// if the manifest source is local or if we are creating a new app. After an app is created,
88-
// app settings becomes the source of truth for remote manifests, so updates and installs always
89-
// get the latest manifest from app settings.
90-
// When the BoltInstall experiment is disabled, we get the manifest from the local file because
91-
// this is how the original implementation worked.
79+
// Get the manifest from the local file if the manifest source is local or if we are creating
80+
// a new app. After an app is created, app settings becomes the source of truth for remote
81+
// manifests, so updates and installs always get the latest manifest from app settings.
9282
var slackManifest types.SlackYaml
93-
if clients.Config.WithExperimentOn(experiment.BoltInstall) {
94-
manifestSource, err := clients.Config.ProjectConfig.GetManifestSource(ctx)
83+
manifestSource, err := clients.Config.ProjectConfig.GetManifestSource(ctx)
84+
if err != nil {
85+
return app, "", err
86+
}
87+
if manifestSource.Equals(config.ManifestSourceLocal) || manifestCreates {
88+
slackManifest, err = clients.AppClient().Manifest.GetManifestLocal(ctx, clients.SDKConfig, clients.HookExecutor)
9589
if err != nil {
9690
return app, "", err
9791
}
98-
if manifestSource.Equals(config.ManifestSourceLocal) || manifestCreates {
99-
slackManifest, err = clients.AppClient().Manifest.GetManifestLocal(ctx, clients.SDKConfig, clients.HookExecutor)
100-
if err != nil {
101-
return app, "", err
102-
}
103-
} else {
104-
slackManifest, err = clients.AppClient().Manifest.GetManifestRemote(ctx, auth.Token, app.AppID)
105-
if err != nil {
106-
return app, "", err
107-
}
108-
}
10992
} else {
110-
slackManifest, err = clients.AppClient().Manifest.GetManifestLocal(ctx, clients.SDKConfig, clients.HookExecutor)
93+
slackManifest, err = clients.AppClient().Manifest.GetManifestRemote(ctx, auth.Token, app.AppID)
11194
if err != nil {
11295
return app, "", err
11396
}
@@ -374,12 +357,6 @@ func InstallLocalApp(ctx context.Context, clients *shared.ClientFactory, orgGran
374357
return types.App{}, api.DeveloperAppInstallResult{}, "", err
375358
}
376359

377-
if !clients.Config.WithExperimentOn(experiment.BoltInstall) {
378-
if !manifestUpdates && !manifestCreates {
379-
return app, api.DeveloperAppInstallResult{}, "", nil
380-
}
381-
}
382-
383360
apiInterface := clients.API()
384361
token := auth.Token
385362
authSession, err := apiInterface.ValidateSession(ctx, token)
@@ -403,31 +380,21 @@ func InstallLocalApp(ctx context.Context, clients *shared.ClientFactory, orgGran
403380
// app.EnterpriseID = *authSession.EnterpriseID
404381
}
405382

406-
// When the BoltInstall experiment is enabled, we need to get the manifest from the local file
407-
// if the manifest source is local or if we are creating a new app. After an app is created,
408-
// app settings becomes the source of truth for remote manifests, so updates and installs always
409-
// get the latest manifest from app settings.
410-
// When the BoltInstall experiment is disabled, we get the manifest from the local file because
411-
// this is how the original implementation worked.
383+
// Get the manifest from the local file if the manifest source is local or if we are creating
384+
// a new app. After an app is created, app settings becomes the source of truth for remote
385+
// manifests, so updates and installs always get the latest manifest from app settings.
412386
var slackManifest types.SlackYaml
413-
if clients.Config.WithExperimentOn(experiment.BoltInstall) {
414-
manifestSource, err := clients.Config.ProjectConfig.GetManifestSource(ctx)
387+
manifestSource, err := clients.Config.ProjectConfig.GetManifestSource(ctx)
388+
if err != nil {
389+
return app, api.DeveloperAppInstallResult{}, "", err
390+
}
391+
if manifestSource.Equals(config.ManifestSourceLocal) || manifestCreates {
392+
slackManifest, err = clients.AppClient().Manifest.GetManifestLocal(ctx, clients.SDKConfig, clients.HookExecutor)
415393
if err != nil {
416394
return app, api.DeveloperAppInstallResult{}, "", err
417395
}
418-
if manifestSource.Equals(config.ManifestSourceLocal) || manifestCreates {
419-
slackManifest, err = clients.AppClient().Manifest.GetManifestLocal(ctx, clients.SDKConfig, clients.HookExecutor)
420-
if err != nil {
421-
return app, api.DeveloperAppInstallResult{}, "", err
422-
}
423-
} else {
424-
slackManifest, err = clients.AppClient().Manifest.GetManifestRemote(ctx, auth.Token, app.AppID)
425-
if err != nil {
426-
return app, api.DeveloperAppInstallResult{}, "", err
427-
}
428-
}
429396
} else {
430-
slackManifest, err = clients.AppClient().Manifest.GetManifestLocal(ctx, clients.SDKConfig, clients.HookExecutor)
397+
slackManifest, err = clients.AppClient().Manifest.GetManifestRemote(ctx, auth.Token, app.AppID)
431398
if err != nil {
432399
return app, api.DeveloperAppInstallResult{}, "", err
433400
}
@@ -712,27 +679,11 @@ func updateIcon(ctx context.Context, clients *shared.ClientFactory, iconPath, ap
712679
// shouldCreateManifest decides if an app manifest needs to be created for an
713680
// app to exist
714681
func shouldCreateManifest(ctx context.Context, clients *shared.ClientFactory, app types.App) (bool, error) {
715-
if !clients.Config.WithExperimentOn(experiment.BoltFrameworks) {
716-
return app.AppID == "", nil
717-
}
718-
719-
// When the BoltInstall experiment is enabled, apps can always be created with any manifest source.
720-
if clients.Config.WithExperimentOn(experiment.BoltInstall) {
721-
return app.AppID == "", nil
722-
}
723-
724-
manifestSource, err := clients.Config.ProjectConfig.GetManifestSource(ctx)
725-
if err != nil {
726-
return false, err
727-
}
728-
return app.AppID == "" && manifestSource == config.ManifestSourceLocal, nil
682+
return app.AppID == "", nil
729683
}
730684

731685
// shouldCacheManifest decides if an app manifest hash should be saved to cache
732686
func shouldCacheManifest(ctx context.Context, clients *shared.ClientFactory, app types.App) (bool, error) {
733-
if !clients.Config.WithExperimentOn(experiment.BoltFrameworks) {
734-
return false, nil
735-
}
736687
manifestSource, err := clients.Config.ProjectConfig.GetManifestSource(ctx)
737688
if err != nil {
738689
return false, err
@@ -765,9 +716,6 @@ func shouldUpdateManifest(ctx context.Context, clients *shared.ClientFactory, ap
765716
if app.AppID == "" {
766717
return false, nil
767718
}
768-
if !clients.Config.WithExperimentOn(experiment.BoltFrameworks) {
769-
return true, nil
770-
}
771719
manifestSource, err := clients.Config.ProjectConfig.GetManifestSource(ctx)
772720
if err != nil {
773721
return false, err

0 commit comments

Comments
 (0)