Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions internal/config/migrate.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package config

import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
Expand Down Expand Up @@ -41,6 +42,9 @@ func MigrateLegacyAgentConfigs(ctx context.Context, home string, filesystem secu
failures = append(failures, fmt.Errorf("read %s: %w", migration.path, err))
continue
}
if !mentionsOneAgent(data) {
continue
}
updated, changed, err := migration.migrate(stripBOM(data))
if err != nil {
// Collected rather than returned: these files belong to different
Expand All @@ -63,6 +67,16 @@ func MigrateLegacyAgentConfigs(ctx context.Context, home string, filesystem secu
return nil
}

// mentionsOneAgent reports whether a config could hold anything to migrate.
// Every migration renames an identifier OneAgent wrote, so a file that never
// mentions it is left unparsed. This matters because these files belong to their
// Agents, not to BootAgent: OpenClaw's openclaw.json is JSON5, which hujson
// rejects, and parsing it anyway surfaced a migration failure on every launch for
// a file there was nothing to do with.
func mentionsOneAgent(data []byte) bool {
return bytes.Contains(bytes.ToLower(data), []byte("oneagent"))
}

// utf8BOM is the byte order mark Windows editors prepend when saving as UTF-8.
// Notepad writes it by default, and it is invisible in every editor that does.
const utf8BOM = "\ufeff"
Expand Down
26 changes: 26 additions & 0 deletions internal/config/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,29 @@ func TestMigrateContinuesPastAFileItCannotParse(t *testing.T) {
t.Errorf("zcode was skipped because an earlier file failed: %s", data)
}
}

// OpenClaw's own config format is JSON5. A file with no OneAgent entry has
// nothing to migrate, so it must be neither parsed nor reported -- this shape
// (unquoted top-level key, trailing comma) is what a real install contains.
func TestMigrateIgnoresAConfigWithNothingToMigrate(t *testing.T) {
home := t.TempDir()
path := filepath.Join(home, ".openclaw", "openclaw.json")
content := "{\n models: {\n \"mode\": \"merge\",\n \"providers\": {\n \"ppio\": {\"baseUrl\": \"https://api.ppio.com/openai\", \"models\": [{\"id\": \"m\"}]}\n }\n },\n}\n"
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
t.Fatal(err)
}
fs := securefs.New(securefs.Options{OS: "linux", BackupRoot: filepath.Join(home, ".bootagent", "backup")})
if err := MigrateLegacyAgentConfigs(context.Background(), home, fs); err != nil {
t.Fatalf("MigrateLegacyAgentConfigs() = %v, want nil for a file with nothing to migrate", err)
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
if string(data) != content {
t.Errorf("config was rewritten:\n%s", data)
}
}
Loading