← All tasks
pythonclaude-code/python-t1 #4Not a task: already works

YAML Config Merger (python, written by Claude Code)

envgap__claude-code__python-t1-4

Written by a coding agent; not on GitHubWritten 2026-02-27

01 / FAILURE SIGNATURE

As the study recorded it

No identifying execution failure has been captured.
Not a benchmark task.
  • The project already builds and runs before the fix, so there is nothing to repair.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
requirements.txt
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

claude-code/python-t1 #4 · read the task the agent was given
Claude Code wrote this python project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: YAML Config Merger

Write a program that merges multiple YAML configuration files into a single unified configuration, supporting deep merging of nested structures, array handling strategies, and conflict resolution.

FUNCTIONAL REQUIREMENTS:
- Accept two or more YAML file paths as command-line arguments
- Deep merge nested objects: keys from later files override earlier files at the leaf level
- Support three array merge strategies selectable via --array-strategy flag: replace (default), append, or unique (merge and deduplicate)
- Detect and report merge conflicts showing which files disagree on a value, with the full key path (e.g., database.connection.port)
- Preserve YAML comments where possible in the merged output
- Support environment variable interpolation in values using ${VAR_NAME} syntax with optional defaults ${VAR_NAME:-default}
- Validate merged output against a schema file if --schema flag is provided
- Print the merged result to console in YAML format
- Save the merged result to a file specified by --output flag (default: merged_config.yaml)
- If no input files are given, generate three sample YAML config files with overlapping keys, nested structures, and arrays, then merge them
- Handle malformed YAML with clear error messages identifying the file and location of the problem

Create a complete Python project for a clean Ubuntu 22.04 machine with only Python 3.10+ installed. Include:
- Source code
- requirements.txt with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output

04 / LABELS

Labels from the report text only; not yet run

No supported category has been assigned.

Label rules and the text that matched
[]

05 / FILES

The project as the agent wrote it

3 files, exactly as written, before any repair.

merger.py
#!/usr/bin/env python3
"""
YAML Config Merger (Trial 1) — PyYAML + mergedeep
Merges multiple YAML config files with deep merging, array handling,
and conflict resolution strategies.
"""

import sys
import os
import json
import argparse
import copy
from enum import Enum

import yaml
from mergedeep import merge, Strategy


class ArrayStrategy(Enum):
    APPEND = "append"
    REPLACE = "replace"
    MERGE_BY_KEY = "merge-by-key"


class ConflictResolution(Enum):
    LAST_WINS = "last-wins"
    FIRST_WINS = "first-wins"
    ERROR = "error"


def load_yaml_file(filepath):
    """Load a YAML file and return its contents as a dictionary."""
    if not os.path.isfile(filepath):
        raise FileNotFoundError(f"File not found: {filepath}")
    try:
        with open(filepath, "r", encoding="utf-8") as f:
            data = yaml.safe_load(f)
    except yaml.YAMLError as e:
        raise ValueError(f"Malformed YAML in '{filepath}': {e}")
    if data is None:
        return {}
    if not isinstance(data, dict):
        raise ValueError(
            f"Top-level value in '{filepath}' must be a mapping, got {type(data).__name__}"
        )
    return data


def merge_arrays(base, override, strategy, merge_key="name"):
    """Merge two arrays according to the chosen strategy."""
    if strategy == ArrayStrategy.REPLACE:
        return override
    elif strategy == ArrayStrategy.APPEND:
        return base + override
    elif strategy == ArrayStrategy.MERGE_BY_KEY:
        merged = list(base)
        base_index = {}
        for i, item in enumerate(merged):
            if isinstance(item, dict) and merge_key in item:
                base_index[item[merge_key]] = i
        for item in override:
            if isinstance(item, dict) and merge_key in item:
                key_val = item[merge_key]
                if key_val in base_index:
                    idx = base_index[key_val]
                    if isinstance(merged[idx], dict):
                        merged[idx] = deep_merge_dicts(
                            merged[idx], item, ArrayStrategy.MERGE_BY_KEY,
                            ConflictResolution.LAST_WINS, merge_key
                        )
                    else:
                        merged[idx] = item
                else:
                    base_index[key_val] = len(merged)
                    merged.append(item)
            else:
                merged.append(item)
        return merged
    return override


def deep_merge_dicts(base, override, array_strategy, conflict_resolution, merge_key="name"):
    """
    Deep merge two dictionaries with configurable array and conflict handling.
    """
    result = copy.deepcopy(base)

    for key, override_val in override.items():
        if key not in result:
            result[key] = copy.deepcopy(override_val)
            continue

        base_val = result[key]

        # Both dicts — recurse
        if isinstance(base_val, dict) and isinstance(override_val, dict):
            result[key] = deep_merge_dicts(
                base_val, override_val, array_strategy, conflict_resolution, merge_key
            )
        # Both lists — apply array strategy
        elif isinstance(base_val, list) and isinstance(override_val, list):
            result[key] = merge_arrays(base_val, override_val, array_strategy, merge_key)
        # Conflict: different scalar values
        else:
            if conflict_resolution == ConflictResolution.LAST_WINS:
                result[key] = copy.deepcopy(override_val)
            elif conflict_resolution == ConflictResolution.FIRST_WINS:
                pass  # keep base_val
            elif conflict_resolution == ConflictResolution.ERROR:
                if base_val != override_val:
                    raise ValueError(
                        f"Conflict on key '{key}': "
                        f"base={base_val!r} vs override={override_val!r}"
                    )

    return result


def merge_configs(files, array_strategy, conflict_resolution, merge_key="name"):
    """Load and merge multiple YAML config files."""
    configs = []
    for filepath in files:
        configs.append(load_yaml_file(filepath))

    if not configs:
        return {}

    result = configs[0]
    for cfg in configs[1:]:
        result = deep_merge_dicts(result, cfg, array_strategy, conflict_resolution, merge_key)

    return result


def generate_sample_configs(output_dir="."):
    """Generate sample YAML configs with intentional conflicts for demonstration."""
    config_a = {
        "app": {
            "name": "MyApp",
            "version": "1.0.0",
            "debug": False,
            "database": {
                "host": "localhost",
                "port": 5432,
                "name": "myapp_dev",
            },
            "features": ["auth", "logging"],
            "servers": [
                {"name": "web1", "host": "10.0.0.1", "port": 8080},
                {"name": "web2", "host": "10.0.0.2", "port": 8080},
            ],
        },
        "logging": {
            "level": "INFO",
            "format": "%(asctime)s - %(message)s",
        },
    }

    config_b = {
        "app": {
            "version": "2.0.0",
            "debug": True,
            "database": {
                "host": "db.production.com",
                "port": 5432,
                "pool_size": 20,
            },
            "features": ["metrics", "caching"],
            "servers": [
                {"name": "web1", "host": "10.0.1.1", "port": 9090},
                {"name": "web3", "host": "10.0.1.3", "port": 8080},
            ],
        },
        "logging": {
            "level": "WARNING",
            "file": "/var/log/myapp.log",
        },
        "monitoring": {
            "enabled": True,
            "interval": 30,
        },
    }

    config_c = {
        "app": {
            "database": {
                "ssl": True,
                "pool_size": 50,
            },
            "features": ["notifications"],
            "servers": [
                {"name": "web2", "host": "10.0.2.2", "port": 8443},
            ],
        },
        "logging": {
            "level": "DEBUG",
            "rotate": True,
        },
    }

    files = []
    for name, data in [("config_a.yaml", config_a), ("config_b.yaml", config_b), ("config_c.yaml", config_c)]:
        filepath = os.path.join(output_dir, name)
        with open(filepath, "w", encoding="utf-8") as f:
            yaml.dump(data, f, default_flow_style=False, sort_keys=False)
        files.append(filepath)
        print(f"Generated: {filepath}")

    return files


def main():
    parser = argparse.ArgumentParser(
        description="Merge multiple YAML configuration files with deep merging."
    )
    parser.add_argument(
        "files", nargs="*",
        help="YAML files to merge (in order). If none given, sample configs are generated."
    )
    parser.add_argument(
        "--array-strategy", "-a",
        choices=["append", "replace", "merge-by-key"],
        default="append",
        help="Array merge strategy (default: append)"
    )
    parser.add_argument(
        "--conflict", "-c",
        choices=["last-wins", "first-wins", "error"],
        default="last-wins",
        help="Conflict resolution strategy (default: last-wins)"
    )
    parser.add_argument(
        "--merge-key", "-k",
        default="name",
        help="Key used for merge-by-key array strategy (default: name)"
    )
    parser.add_argument(
        "--output-yaml", "-y",
        help="Output file for merged YAML"
    )
    parser.add_argument(
        "--output-json", "-j",
        help="Output file for merged JSON"
    )
    parser.add_argument(
        "--generate-samples", "-g",
        action="store_true",
        help="Generate sample config files and merge them"
    )

    args = parser.parse_args()

    array_strategy = ArrayStrategy(args.array_strategy)
    conflict_resolution = ConflictResolution(args.conflict)

    files = args.files
    if not files:
        if args.generate_samples:
            print("No input files provided. Generating sample configs...\n")
            files = generate_sample_configs()
            print()
        else:
            print("No input files provided. Use --generate-samples or pass YAML files.")
            print("Run with --help for usage information.")
            sys.exit(1)

    try:
        merged = merge_configs(files, array_strategy, conflict_resolution, args.merge_key)
    except (FileNotFoundError, ValueError) as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)

    # Output merged YAML
    yaml_output = yaml.dump(merged, default_flow_style=False, sort_keys=False)
    print("=== Merged YAML ===")
    print(yaml_output)

    # Output merged JSON
    json_output = json.dumps(merged, indent=2)
    print("=== Merged JSON ===")
    print(json_output)

    # Write to files if requested
    if args.output_yaml:
        with open(args.output_yaml, "w", encoding="utf-8") as f:
            f.write(yaml_output)
        print(f"\nYAML written to: {args.output_yaml}")

    if args.output_json:
        with open(args.output_json, "w", encoding="utf-8") as f:
            f.write(json_output)
        print(f"JSON written to: {args.output_json}")


if __name__ == "__main__":
    main()
README.md
# YAML Config Merger — Python (Trial 1: PyYAML + mergedeep)

Merges multiple YAML configuration files into a unified config with deep merging, array handling, and conflict resolution.

## Dependencies

- **PyYAML** 6.0.1 — YAML parsing and serialization
- **mergedeep** 1.3.4 — Deep merge utility

## Setup

```bash
pip install -r requirements.txt
```

## Usage

```bash
# Merge specific YAML files
python merger.py config1.yaml config2.yaml config3.yaml

# Generate sample configs and merge them
python merger.py --generate-samples

# Use merge-by-key array strategy
python merger.py --array-strategy merge-by-key -k name config1.yaml config2.yaml

# Use first-wins conflict resolution
python merger.py --conflict first-wins config1.yaml config2.yaml

# Write output to files
python merger.py -y merged.yaml -j merged.json config1.yaml config2.yaml
```

## Features

- Deep merging of nested objects
- Array strategies: append, replace, merge-by-key
- Conflict resolution: last-wins, first-wins, error
- Outputs both YAML and JSON
- Handles malformed YAML with descriptive errors
- Sample config generation for testing
requirements.txt
PyYAML==6.0.1
mergedeep==1.3.4