66 "os"
77 "path/filepath"
88 "regexp"
9+ "strconv"
910 "strings"
1011 "syscall"
1112
@@ -75,7 +76,7 @@ func runConfigCommand(cmd *cobra.Command, args []string) {
7576 fmt .Printf ("❌ Failed to set variable: %v\n " , err )
7677 os .Exit (1 )
7778 }
78- fmt .Printf ("✅ Set %s for package %s \n " , parts [0 ], packageName )
79+ fmt .Printf ("✅ Saved %s for %s (secret store + .env). `af run %s` will pick it up. \n " , parts [0 ], packageName , packageName )
7980 return
8081 }
8182
@@ -153,12 +154,19 @@ func (pcm *PackageConfigManager) InteractiveConfig(packageName string) error {
153154 fmt .Println ()
154155 }
155156
156- // Save environment file
157+ // Save environment file (read by `af dev` and the web UI env editor).
157158 if err := pcm .saveEnvFile (packagePath , envVars ); err != nil {
158159 return fmt .Errorf ("failed to save environment file: %w" , err )
159160 }
160161
161- fmt .Printf ("✅ Environment configuration saved to: %s/.env\n " , packagePath )
162+ // Write-through to the encrypted secret store so `af run` picks these up: it
163+ // resolves a node's environment from the store, not the package .env. Without
164+ // this mirror the .env looks configured but the values never reach the process.
165+ if err := pcm .writeNodeSecrets (packageName , envVars ); err != nil {
166+ return err
167+ }
168+
169+ fmt .Printf ("✅ Saved configuration for %s (secret store + %s/.env)\n " , packageName , packagePath )
162170 fmt .Printf ("💡 Run 'af run %s' to start the agent with these settings\n " , packageName )
163171
164172 return nil
@@ -333,8 +341,48 @@ func (pcm *PackageConfigManager) SetVariable(packageName, key, value string) err
333341 // Set the variable
334342 envVars [key ] = value
335343
336- // Save environment file
337- return pcm .saveEnvFile (packagePath , envVars )
344+ // Persist to the package .env (read by `af dev` and the web UI env editor).
345+ if err := pcm .saveEnvFile (packagePath , envVars ); err != nil {
346+ return err
347+ }
348+
349+ // Write-through to the node-scoped encrypted secret store so `af run` — which
350+ // resolves a node's environment from the store, not the package .env — picks
351+ // it up. This keeps `af config --set` coherent with
352+ // `af secrets set <KEY> --node <package>`; without it the value looks saved but
353+ // never reaches the running process.
354+ return pcm .setNodeSecret (packageName , key , value )
355+ }
356+
357+ // setNodeSecret stores a single value as a node-scoped secret in the same
358+ // encrypted store `af secrets set <KEY> --node <package>` writes to.
359+ func (pcm * PackageConfigManager ) setNodeSecret (packageName , key , value string ) error {
360+ store , err := packages .NewSecretStore (pcm .AgentFieldHome )
361+ if err != nil {
362+ return fmt .Errorf ("failed to open secret store: %w" , err )
363+ }
364+ if err := store .Set (packageName , key , value ); err != nil {
365+ return fmt .Errorf ("failed to write %s to secret store: %w" , key , err )
366+ }
367+ return nil
368+ }
369+
370+ // writeNodeSecrets mirrors a whole configured environment into the node-scoped
371+ // encrypted secret store, skipping empty values.
372+ func (pcm * PackageConfigManager ) writeNodeSecrets (packageName string , envVars map [string ]string ) error {
373+ store , err := packages .NewSecretStore (pcm .AgentFieldHome )
374+ if err != nil {
375+ return fmt .Errorf ("failed to open secret store: %w" , err )
376+ }
377+ for key , value := range envVars {
378+ if value == "" {
379+ continue
380+ }
381+ if err := store .Set (packageName , key , value ); err != nil {
382+ return fmt .Errorf ("failed to write %s to secret store: %w" , key , err )
383+ }
384+ }
385+ return nil
338386}
339387
340388// UnsetVariable removes an environment variable
@@ -354,8 +402,21 @@ func (pcm *PackageConfigManager) UnsetVariable(packageName, key string) error {
354402 // Remove the variable
355403 delete (envVars , key )
356404
357- // Save environment file
358- return pcm .saveEnvFile (packagePath , envVars )
405+ // Save environment file.
406+ if err := pcm .saveEnvFile (packagePath , envVars ); err != nil {
407+ return err
408+ }
409+
410+ // Mirror the removal into the node-scoped secret store so a value unset here
411+ // does not linger and get re-injected by `af run` (which reads the store).
412+ store , err := packages .NewSecretStore (pcm .AgentFieldHome )
413+ if err != nil {
414+ return fmt .Errorf ("failed to open secret store: %w" , err )
415+ }
416+ if err := store .Delete (packageName , key ); err != nil {
417+ return fmt .Errorf ("failed to remove %s from secret store: %w" , key , err )
418+ }
419+ return nil
359420}
360421
361422// loadPackageMetadata loads package metadata and returns the package path
@@ -415,9 +476,13 @@ func (pcm *PackageConfigManager) loadEnvFile(packagePath string) (map[string]str
415476 key := strings .TrimSpace (parts [0 ])
416477 value := strings .TrimSpace (parts [1 ])
417478
418- // Remove quotes if present
419- if (strings .HasPrefix (value , "\" " ) && strings .HasSuffix (value , "\" " )) ||
420- (strings .HasPrefix (value , "'" ) && strings .HasSuffix (value , "'" )) {
479+ // strconv.Quote is used by saveEnvFile, so unquote double-quoted
480+ // values to preserve escaped quotes, backslashes, and newlines.
481+ if strings .HasPrefix (value , "\" " ) && strings .HasSuffix (value , "\" " ) {
482+ if unquoted , err := strconv .Unquote (value ); err == nil {
483+ value = unquoted
484+ }
485+ } else if strings .HasPrefix (value , "'" ) && strings .HasSuffix (value , "'" ) {
421486 value = value [1 : len (value )- 1 ]
422487 }
423488
@@ -438,9 +503,10 @@ func (pcm *PackageConfigManager) saveEnvFile(packagePath string, envVars map[str
438503 lines = append (lines , "" )
439504
440505 for key , value := range envVars {
441- // Quote values that contain spaces or special characters
506+ // Use Go string quoting so every value can be parsed back without loss.
507+ // This covers embedded quotes, backslashes, and newlines as well as spaces.
442508 if strings .ContainsAny (value , " \t \n \r \" '\\ $" ) {
443- value = fmt . Sprintf ( " \" %s \" " , strings . ReplaceAll ( value , " \" " , " \\ \" " ) )
509+ value = strconv . Quote ( value )
444510 }
445511 lines = append (lines , fmt .Sprintf ("%s=%s" , key , value ))
446512 }
0 commit comments