Tracing ZON parser failure

Not sure if this is a bug or not, I remember some parser leak was fixed in 0.14.x. Trying to handle deprecated config fields in a zon file, but getting a leak I cannot figure how to plug.

All ZON parser calls are freed via parse.free().

pub fn migrateConfig(
    allocator: std.mem.Allocator,
    config_path: []const u8,
) !void {
    const MigrationConfig = MigrationType(Config);
    const file = try std.fs.cwd().openFile(config_path, .{});
    defer file.close();

    // null-terminated
    const content = try file.readToEndAllocOptions(
        allocator,
        1024 * 1024,
        null,
        @enumFromInt(@alignOf(u8)),
        0,
    );

    defer allocator.free(content);

    const old_config = try std.zon.parse.fromSlice(
        MigrationConfig,
        allocator,
        content,
        null,
        .{},
    );

    defer std.zon.parse.free(allocator, old_config);

    var ignore_list: [][]const u8 = &[_][]const u8{};

    if (old_config.ignore_list) |v| {
        ignore_list = try allocator.alloc([]const u8, v.len);
        var i: usize = 0;
        for (v) |item| {
            ignore_list[i] = try allocator.dupe(u8, item);
            i += 1;
        }
    }

    var new_config = Config{
        .repository = if (old_config.repository) |v| v else "",
        .source = if (old_config.source) |v| v else "",
        .target = if (old_config.target) |v|
            v
        else if (old_config.destination) |v|
            v
        else
            "",
        .logging = if (old_config.logging) |v| v else false,
        .ignore_list = ignore_list,
    };

    try new_config.write(allocator, config_path);

    for (ignore_list) |item| {
        allocator.free(item);
    }

    allocator.free(ignore_list);
}

custom migration config type:

fn MigrationType(comptime T: type) type {
    const config_fields = std.meta.fields(T);
    // deprecated config fields
    const deprecated_fields = [_]std.builtin.Type.StructField{
        .{
            .name = "destination",
            .type = ?[]const u8,
            .default_value_ptr = &@as(?[]const u8, null),
            .is_comptime = false,
            .alignment = @alignOf(?[]const u8),
        },
    };

    var fields: [
        config_fields.len +
            deprecated_fields.len
    ]std.builtin.Type.StructField =
        undefined;

    inline for (config_fields, 0..) |field, i| {
        const OptionalType = @Type(.{
            .optional = .{
                .child = field.type,
            },
        });

        const default_value = @as(OptionalType, null);

        fields[i] = .{
            .name = field.name,
            .type = OptionalType,
            .default_value_ptr = &default_value,
            .is_comptime = false,
            .alignment = @alignOf(OptionalType),
        };
    }

    inline for (deprecated_fields, 0..) |field, i| {
        fields[config_fields.len + i] = field;
    }

    return @Type(.{
        .@"struct" = .{
            .layout = .auto,
            .fields = &fields,
            .decls = &.{},
            .is_tuple = false,
        },
    });
}

I thought I messed up custom config type, but everything works just fine and values from destination are migrated to target as expected. Everything seems to be fine and final config looks as it should but:

Updating config
Config updated
SYNC STARTED

Source is /home/charlie/test1/
Target is /home/charlie/test2/

TOTAL: 120
UPDATED: 0
TEMPLATES: 0
RENDERS: 107
BINARIES: 13
ERRORS: 0
DONE
error(gpa): memory address 0x37cdc8200000 leaked:
/home/charlie/Downloads/zig-x86_64-freebsd-0.15.1/lib/std/array_list.zig:692:52: 0x10da2dd in toOwnedSlice (std.zig)
            const new_memory = try gpa.alignedAlloc(T, alignment, self.items.len);
                                                   ^
/home/charlie/Downloads/zig-x86_64-freebsd-0.15.1/lib/std/zon/parse.zig:1082:52: 0x1704bb6 in failUnexpected__anon_65322 (std.zig)
                        .msg = try buf.toOwnedSlice(gpa),
                                                   ^
/home/charlie/Downloads/zig-x86_64-freebsd-0.15.1/lib/std/zon/parse.zig:767:43: 0x14232d2 in parseStruct__anon_65144 (std.zig)
                return self.failUnexpected(T, "field", node, i, name);
                                          ^
/home/charlie/Downloads/zig-x86_64-freebsd-0.15.1/lib/std/zon/parse.zig:458:40: 0x142ca01 in parseExprInner__anon_58310 (std.zig)
                return self.parseStruct(T, node),
                                       ^
/home/charlie/Downloads/zig-x86_64-freebsd-0.15.1/lib/std/zon/parse.zig:417:35: 0x13a56fc in parseExpr__anon_49556 (std.zig)
        return self.parseExprInner(T, node) catch |err| switch (err) {
                                  ^
/home/charlie/Downloads/zig-x86_64-freebsd-0.15.1/lib/std/zon/parse.zig:333:28: 0x13353af in fromZoirNode__anon_39410 (std.zig)
    return parser.parseExpr(T, node);

Tried to configure the parser but ignoring unknown fields does not help.
Rest of the code dfs/src/config.zig at config · charlesrocket/dfs · GitHub

Zig 0.15.1/0.15.2

Found it

diff --git a/src/config.zig b/src/config.zig
index 184f084..bdc589e 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -113,7 +113,7 @@ pub fn open(
         allocator,
         config_data,
         null,
-        .{},
+        .{.ignore_unknown_fields = true},
     ) catch {
         const data = try allocator.dupeZ(u8, config_data);
         return ConfigResult{ .parse_error = data };

just needs to ignore deprecated fields on the first zon parse only