From ebedcb2c66b7c01234ce32cd3f8df87f3fbdb152 Mon Sep 17 00:00:00 2001 From: Elysia Date: Sat, 26 Sep 2026 19:30:50 +0800 Subject: [PATCH] fix: skip Agent configs that have nothing to migrate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every legacy migration renames an identifier OneAgent wrote, yet each config was parsed unconditionally on launch. OpenClaw's openclaw.json is JSON5, which hujson rejects (`invalid literal: models` for an unquoted key), so a user with no OneAgent history saw "无法迁移旧 BootAgent 配置" on every page. A file that never mentions oneagent is now left unread. Co-Authored-By: Claude Sonnet 5 --- internal/config/migrate.go | 14 ++++++++++++++ internal/config/migrate_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/internal/config/migrate.go b/internal/config/migrate.go index d03c62f8..0726e139 100644 --- a/internal/config/migrate.go +++ b/internal/config/migrate.go @@ -1,6 +1,7 @@ package config import ( + "bytes" "context" "crypto/sha256" "encoding/hex" @@ -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 @@ -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" diff --git a/internal/config/migrate_test.go b/internal/config/migrate_test.go index 8129b6a4..2e22c193 100644 --- a/internal/config/migrate_test.go +++ b/internal/config/migrate_test.go @@ -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) + } +}