Skip to content

sync pull can register a config without materializing its dir; sync push --force then plans deleting that remote config #472

Description

@Matejkys

Summary

When a project contains two configurations with the same name, kbagent sync pull can register a config in .keboola/manifest.json without materializing its directory on disk (the entry is written with an empty pull_hash). A subsequent kbagent sync push --force then sees remote-without-local and plans a DELETE of that remote configuration — silently destroying an unrelated config if the user pushes without inspecting the dry-run plan.

Observed on kbagent v0.59.0 (auto-update to v0.65.1 kept failing, so possibly fixed since — but worth verifying).

Reproduction (as observed, 2026-07-02)

Project 347 (us-east4.gcp stack, org 73) contains two configs named "My Telemetry Data Source" (keboola.ex-telemetry-data).

  1. kbagent sync pull --project <alias> -d <alias> --no-storage --no-jobs --force
    → manifest gains entries for both configs; one of them (id 1027245143, path ...my-telemetry-data-source-10272451) gets a manifest entry with "pull_hash": "" and no directory is written (verified via git history — the dir never existed).
  2. kbagent sync pull again → reports "Already up to date" (the phantom entry is not detected as missing).
  3. kbagent sync push --project <alias> -d <alias> --force --dry-run
    → plan includes DELETE of config 1027245143, even though nobody ever deleted it locally.

We caught it only because we gate every --force push behind a dry-run and the delete list had one extra row. Without that habit the push would have deleted a production config unrelated to the change being made.

Workaround used: reconstruct the phantom config locally from kbagent config detail, re-run the dry-run (clean), then push.

Expected behavior

  • sync pull must always materialize a directory for every manifest entry it writes (name collisions should get a deduplicated path — which the ...-10272451 suffix suggests is already the intent).
  • A manifest entry with an empty pull_hash and no directory should be treated as "never fetched", not as "locally deleted" — sync push --force should refuse to plan a delete for it (or at minimum warn loudly).

Related sync-state issues observed in the same session (can split into separate issues on request)

  1. sync pull drops parameters.packages when serializing kds-team.app-custom-python configs to local YAML. Remote keeps the key and the push-side comparison still treats local==remote, but the local file is silently incomplete — anyone templating from the local mirror ships a config that dies on ModuleNotFoundError (this bit us: job failed on import requests).
  2. Same-content conflict deadlock: when a config is changed remotely via kbagent config update and the same change is applied locally, sync enters a state where sync push says "Remote changes detected. Run 'sync pull' first" and sync pull --force says "resolve conflicts first" — with no resolve command available. Workaround: delete the config's manifest entry + local dir, then sync pull --force re-fetches it cleanly. A kbagent sync resolve --theirs/--ours <config> (or similar) would help.

Environment

  • kbagent v0.59.0 (uv tool install keboola-agent-cli; self-update to 0.65.1 failing repeatedly with "Auto-update failed; continuing with current version")
  • macOS (darwin), stack connection.us-east4.gcp.keboola.com

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions