@@ -55,12 +55,20 @@ func NewProjectService(
5555}
5656
5757// reloadAndCacheProjectConfig reloads the project configuration from disk and updates the lazy cache.
58- // It preserves the EventDispatcher from the previous instance to maintain event handler continuity.
58+ // It preserves the EventDispatcher from the previous instance to maintain event handler continuity
59+ // for both the project and all services.
60+ //
61+ // Event dispatchers must be preserved because they contain event handlers that were registered by:
62+ // - azure.yaml hooks (prepackage, postdeploy, etc.)
63+ // - azd extensions that registered custom event handlers
64+ //
65+ // Without preserving these dispatchers, any event handlers registered before the reload would be lost,
66+ // causing hooks and extension-registered handlers to stop working after configuration updates.
5967func (s * projectService ) reloadAndCacheProjectConfig (ctx context.Context , projectPath string ) error {
60- // Get the current config to preserve the EventDispatcher
68+ // Get the current config to preserve the EventDispatchers
6169 oldConfig , err := s .lazyProjectConfig .GetValue ()
6270 if err != nil {
63- // If we can't get old config, just reload without preserving dispatcher
71+ // If we can't get old config, just reload without preserving dispatchers
6472 reloadedConfig , err := project .Load (ctx , projectPath )
6573 if err != nil {
6674 return err
@@ -75,11 +83,20 @@ func (s *projectService) reloadAndCacheProjectConfig(ctx context.Context, projec
7583 return err
7684 }
7785
78- // Preserve the EventDispatcher from the old config
86+ // Preserve the EventDispatcher from the old project config
7987 if oldConfig .EventDispatcher != nil {
8088 reloadedConfig .EventDispatcher = oldConfig .EventDispatcher
8189 }
8290
91+ // Preserve the EventDispatcher for each service
92+ if oldConfig .Services != nil && reloadedConfig .Services != nil {
93+ for serviceName , oldService := range oldConfig .Services {
94+ if reloadedService , exists := reloadedConfig .Services [serviceName ]; exists && oldService .EventDispatcher != nil {
95+ reloadedService .EventDispatcher = oldService .EventDispatcher
96+ }
97+ }
98+ }
99+
83100 // Update the lazy cache
84101 s .lazyProjectConfig .SetValue (reloadedConfig )
85102 return nil
@@ -143,33 +160,9 @@ func (s *projectService) Get(ctx context.Context, req *azdext.EmptyRequest) (*az
143160 }
144161 }
145162
146- project := & azdext.ProjectConfig {
147- Name : projectConfig .Name ,
148- ResourceGroupName : projectConfig .ResourceGroupName .MustEnvsubst (envKeyMapper ),
149- Path : projectConfig .Path ,
150- Infra : & azdext.InfraOptions {
151- Provider : string (projectConfig .Infra .Provider ),
152- Path : projectConfig .Infra .Path ,
153- Module : projectConfig .Infra .Module ,
154- },
155- Services : map [string ]* azdext.ServiceConfig {},
156- }
157-
158- if projectConfig .Metadata != nil {
159- project .Metadata = & azdext.ProjectMetadata {
160- Template : projectConfig .Metadata .Template ,
161- }
162- }
163-
164- for name , service := range projectConfig .Services {
165- var protoService * azdext.ServiceConfig
166-
167- // Use mapper with environment variable resolver
168- if err := mapper .WithResolver (envKeyMapper ).Convert (service , & protoService ); err != nil {
169- return nil , fmt .Errorf ("converting service config to proto: %w" , err )
170- }
171-
172- project .Services [name ] = protoService
163+ var project * azdext.ProjectConfig
164+ if err := mapper .WithResolver (envKeyMapper ).Convert (projectConfig , & project ); err != nil {
165+ return nil , fmt .Errorf ("converting project config to proto: %w" , err )
173166 }
174167
175168 return & azdext.GetProjectResponse {
0 commit comments