diff --git a/cumulusci/tests/triage/README.md b/cumulusci/tests/triage/README.md new file mode 100644 index 0000000000..4fed49729c --- /dev/null +++ b/cumulusci/tests/triage/README.md @@ -0,0 +1,50 @@ +# `cumulusci/tests/triage/` + +Regression-repro tests for open issues. Each test file targets one +issue. + +## Conventions + +- File naming: `test_issue_.py` where `` is the GitHub + issue number. +- Every test in this directory uses `@pytest.mark.xfail(strict=False)` + by default. The xfail marker captures the expected failure mode and + is removed by the corresponding fix-PR. +- `strict=False` is intentional: if a bug resolves independently (a + different PR lands, an upstream dependency is fixed, etc.), the + test will `XPASS` rather than fail CI. A harvest pass periodically + converts `XPASS` issues to `NOT-REPRODUCED-on-dev` and either + rewrites the test or drops it. +- Tests MUST be fast (`< 2s`), import-only or mocked. No live + Salesforce org, no real network, no scratch-org creation. Use + `unittest.mock` / fixtures liberally. + +## Lifecycle + +1. Triage subagent verifies a bug reproduces on `origin/dev`. +2. Subagent writes `test_issue_.py` with `@pytest.mark.xfail` + + a code-level assertion that captures the bug. +3. Test is committed to this directory via the triage umbrella PR. +4. When the bug is fixed: + - Fix-PR removes the `@pytest.mark.xfail` marker. + - Fix-PR confirms the test now passes. + - Fix-PR moves the test out of this directory to its natural + home (e.g. `cumulusci/tasks//tests/`) - or leaves it + here with the marker removed, whichever is cleaner. + +## See also + +- `docs/triage/v5/repro-results.md` - narrative evidence per issue. +- `docs/triage/v5/fix-sketches/issue_.md` - proposed fix + approach per issue. +- `docs/triage/v5/proposals.md` - pass-1 classification matrix. + +## Running + +```bash +uv run pytest cumulusci/tests/triage/ -v +``` + +Expected outcome: every test reports `XFAIL`. If any reports `XPASS`, +that is signal that a bug resolved independently - see the harvest +xpass list in `docs/triage/v5/` (if present). diff --git a/cumulusci/tests/triage/__init__.py b/cumulusci/tests/triage/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cumulusci/tests/triage/test_issue_1348.py b/cumulusci/tests/triage/test_issue_1348.py new file mode 100644 index 0000000000..ae56c37ebd --- /dev/null +++ b/cumulusci/tests/triage/test_issue_1348.py @@ -0,0 +1,46 @@ +"""Regression repro for #1348. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: CumulusCI is GitHub-only. There are no `gitlab` or +`bitbucket` references in the `cumulusci/` package, and the +`ci_feature` flow still hardcodes GitHub-specific tasks +(`github_parent_pr_notes`, `github_automerge_feature`). The +2017 feature ask is to add a VCS abstraction so projects on +other providers can use CumulusCI. + +This test loads the universal cumulusci.yml and asserts that +`ci_feature` does NOT step into any GitHub-specific task - +which it does today, so this fails -> XFAIL. +""" + +import pytest + +from cumulusci.utils.yaml.cumulusci_yml import cci_safe_load +from pathlib import Path + +import cumulusci + + +@pytest.mark.xfail( + reason="repro for #1348 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_issue_1348(): + universal_yaml = Path(cumulusci.__file__).resolve().parent / "cumulusci.yml" + config = cci_safe_load(universal_yaml.open(), str(universal_yaml)) + flows = config.get("flows", {}) + ci_feature = flows.get("ci_feature") or {} + steps = ci_feature.get("steps", {}) + step_text = " ".join( + str(step.get("task") or step.get("flow") or "") for step in steps.values() + ) + has_github_specific = ( + "github_parent_pr_notes" in step_text or "github_automerge_feature" in step_text + ) + assert not has_github_specific, ( + "ci_feature still references GitHub-specific tasks; no VCS " + "abstraction exposed for GitLab/Bitbucket users (see #1348)." + ) diff --git a/cumulusci/tests/triage/test_issue_1432.py b/cumulusci/tests/triage/test_issue_1432.py new file mode 100644 index 0000000000..b4e9c78ee4 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_1432.py @@ -0,0 +1,50 @@ +"""Regression repro for #1432. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (no_reverify_needed). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: ``BaseTask._validate_options`` (cumulusci/core/tasks.py +around lines 187-197) only checks for *missing* required options when +the task uses the legacy ``task_options`` dict. Unknown keys are +silently accepted - passing ``-o colour red`` to a task that declares +``color`` results in no error. + +The new-style Pydantic ``Options`` class path *does* reject extras +(``"extra options"`` message in the same file), so the bug is partially +mitigated for tasks that opt in. Legacy ``task_options`` dict tasks +remain affected. + +The fix is to also reject unknown keys when validating the legacy +dict-style options. This test asserts the source of +``BaseTask._validate_options`` checks for unknown keys; on dev it fails +because only ``required`` is checked. +""" + +import inspect + +import pytest + +from cumulusci.core.tasks import BaseTask + + +@pytest.mark.xfail( + reason="repro for #1432 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_1432(): + src = inspect.getsource(BaseTask._validate_options) + has_unknown_check = any( + token in src + for token in ( + "not in self.task_options", + "not in task_options", + "unknown", + "unexpected", + "extra option", + ) + ) + assert has_unknown_check, ( + "BaseTask._validate_options still only checks for missing required " + "options; unknown task_options keys are silently accepted for legacy " + "task_options-dict tasks (see #1432)" + ) diff --git a/cumulusci/tests/triage/test_issue_1769.py b/cumulusci/tests/triage/test_issue_1769.py new file mode 100644 index 0000000000..3a80907746 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_1769.py @@ -0,0 +1,47 @@ +"""Regression repro for #1769. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci/tasks/bulkdata/tests/test_load.py still uses the +2020-vintage pattern `lookups["Id"] = MappingLookup(name="Id", +table="accounts", key_field="sf_id")` (line 739 and several siblings: +~754, 773, 801, 1119, 1187, 1255) to describe an after-step's +UPDATE-on-Id dependency. davidmreed acknowledged in 2020 that this +is a "horrible hack" he intended to clean up; six years later it +remains in the test file. + +A real fix would remove `Id` from the `lookups` dict (or stop using +`MappingLookup` to express the self-update relationship) and have +the after-step logic synthesize that relationship internally. + +This test asserts that the offending pattern is absent from test_load.py. +On dev the pattern is present, so the assertion fails -> XFAIL. +""" + +from pathlib import Path + +import pytest + +import cumulusci + + +@pytest.mark.xfail( + reason="repro for #1769 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_1769(): + test_load_path = ( + Path(cumulusci.__file__).parent + / "tasks" + / "bulkdata" + / "tests" + / "test_load.py" + ) + text = test_load_path.read_text() + bad_pattern = 'lookups["Id"] = MappingLookup(' + assert bad_pattern not in text, ( + f"test_load.py still contains the 2020-vintage smell {bad_pattern!r}; " + "expected the after-step Id-update relationship to be expressed " + "without injecting a MappingLookup keyed on 'Id'." + ) diff --git a/cumulusci/tests/triage/test_issue_2013.py b/cumulusci/tests/triage/test_issue_2013.py new file mode 100644 index 0000000000..c9571f51c7 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_2013.py @@ -0,0 +1,51 @@ +"""Regression repro for #2013. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci.tasks.bulkdata.utils.create_table_if_needed builds +a SQLAlchemy `Table(tablename, metadata, *fields)` before calling +`inspector.has_table()`. When two mapping steps share the same +sf_object name, the SQLAlchemy `Table()` constructor raises +`InvalidRequestError: Table 'X' is already defined ...` BEFORE the +intended `BulkDataException("Table already exists: ...")` is raised. + +The fix is to either (a) wrap the `Table()` call in a try/except and +re-raise as BulkDataException, or (b) validate uniqueness at +mapping-parse time. Either way, when the table already exists in the +metadata, callers should observe a CumulusCI-typed BulkDataException, +not a SQLAlchemy-typed InvalidRequestError. +""" + +from sqlalchemy import Column, MetaData, String, create_engine + +import pytest + +from cumulusci.core.exceptions import BulkDataException +from cumulusci.tasks.bulkdata.utils import create_table_if_needed + + +@pytest.mark.xfail( + reason="repro for #2013 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_2013(): + engine = create_engine("sqlite:///:memory:") + metadata = MetaData(bind=engine) + + create_table_if_needed( + "Account", metadata, [Column("sf_id", String(255), primary_key=True)] + ) + + raised = None + try: + create_table_if_needed( + "Account", metadata, [Column("sf_id", String(255), primary_key=True)] + ) + except BaseException as e: + raised = e + + assert isinstance(raised, BulkDataException), ( + "Expected BulkDataException for duplicate table; " + f"got {type(raised).__name__}: {raised}" + ) diff --git a/cumulusci/tests/triage/test_issue_2140.py b/cumulusci/tests/triage/test_issue_2140.py new file mode 100644 index 0000000000..3b781fde3b --- /dev/null +++ b/cumulusci/tests/triage/test_issue_2140.py @@ -0,0 +1,46 @@ +"""Regression repro for #2140. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: `CliRuntime.get_org()` in cumulusci/cli/runtime.py +hard-errors with `click.UsageError("No org specified and no +default org set.")` when the requested org does not exist in +the keychain (or `OrgNotFound` from `keychain.get_org()` +bubbles up to `cli/org.py` as a plain ClickException). The 2020 +ask is to instead surface an interactive prompt that lets the +user pick from configured scratch org configs. + +This test asserts that `get_org` source contains a prompt +mechanism (click.prompt / click.Choice / scratch-config-listing +helper). On dev it doesn't, so the assertion fails -> XFAIL. +""" + +import inspect + +import pytest + +from cumulusci.cli.runtime import CliRuntime + + +@pytest.mark.xfail( + reason="repro for #2140 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_issue_2140(): + src = inspect.getsource(CliRuntime.get_org) + has_prompt = any( + token in src + for token in ( + "click.prompt", + "click.confirm", + "click.Choice", + "scratch_configs", + "scratch configs", + ) + ) + assert has_prompt, ( + "CliRuntime.get_org still hard-errors when no org is found; " + "no interactive scratch-config picker (see #2140)." + ) diff --git a/cumulusci/tests/triage/test_issue_2153.py b/cumulusci/tests/triage/test_issue_2153.py new file mode 100644 index 0000000000..164ede92d6 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_2153.py @@ -0,0 +1,48 @@ +"""Regression repro for #2153. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: `MergeBranch._create_conflict_pull_request` +(cumulusci/tasks/github/merge.py:264-288) only calls +`self.repo.create_pull(...)` to open the auto-generated +"Merge into " PR. The original 2020 ask is to +also drop a comment on the source/child PR which tags the branch +subscribers so they get notified about the conflict. + +A repo-wide search for `create_comment`/`issue_comment` returns +only test-fixture hits - production GitHub task code never +opens a PR/issue comment as part of this conflict path. + +This test asserts that the merge task surface mentions a +comment-posting call. On dev it doesn't, so the assertion fails +-> XFAIL. +""" + +import inspect + +import pytest + +from cumulusci.tasks.github.merge import MergeBranch + + +@pytest.mark.xfail( + reason="repro for #2153 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_issue_2153(): + src = inspect.getsource(MergeBranch) + posts_comment = any( + token in src + for token in ( + "create_comment", + "issue_comment", + "post_comment", + ) + ) + assert posts_comment, ( + "MergeBranch still only opens an auto-merge PR on conflict; " + "no comment-on-original-PR path found to notify branch " + "subscribers (see #2153)." + ) diff --git a/cumulusci/tests/triage/test_issue_2325.py b/cumulusci/tests/triage/test_issue_2325.py new file mode 100644 index 0000000000..9b89733c85 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_2325.py @@ -0,0 +1,53 @@ +"""Regression repro for #2325. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (no_reverify_needed). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: CumulusCI ships ``disable_tdtm_trigger_handlers`` / +``restore_tdtm_trigger_handlers`` (cumulusci.yml:738-747) for triggers +and ``set_duplicate_rule_status`` for DuplicateRule, but offers no +analogous task for toggling Salesforce ValidationRules around a data +load. The user requested an OOTB ``set_validation_rule_status`` / +``disable_validation_rules`` task; ``cci task list | grep -i validation`` +returns only the duplicate-rule task on v4.10.0. + +The fix is to add a ``MetadataSingleEntityTransformTask`` subclass for +``entity = "ValidationRule"`` and wire it into ``cumulusci.yml`` with +both disable/restore (or set-status) flavours, mirroring the existing +TDTM/DuplicateRule pattern. + +This test asserts ``cumulusci.yml`` declares at least one validation-rule +toggle task; on dev it fails because no such task exists. +""" + +from pathlib import Path + +import pytest +import yaml + +import cumulusci + + +@pytest.mark.xfail( + reason="repro for #2325 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_2325(): + cci_root = Path(cumulusci.__file__).parent + with open(cci_root / "cumulusci.yml") as f: + data = yaml.safe_load(f) + + task_names = set(data.get("tasks", {}).keys()) + candidates = { + "set_validation_rule_status", + "disable_validation_rules", + "restore_validation_rules", + "activate_validation_rules", + "deactivate_validation_rules", + } + intersection = task_names & candidates + assert intersection, ( + "cumulusci.yml still ships no ValidationRule toggle task. Expected one " + f"of {sorted(candidates)} to mirror the disable_tdtm_trigger_handlers / " + "set_duplicate_rule_status pattern (see #2325)" + ) diff --git a/cumulusci/tests/triage/test_issue_2402.py b/cumulusci/tests/triage/test_issue_2402.py new file mode 100644 index 0000000000..13b23ab08b --- /dev/null +++ b/cumulusci/tests/triage/test_issue_2402.py @@ -0,0 +1,33 @@ +"""Regression repro for #2402. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: `cci flow run` (cumulusci/cli/flow.py:119-145) only exposes +`--delete-org`; there is no `--rebuild-org` switch that would delete +the scratch org before re-running the flow against a freshly-created +one. Users currently have to chain `cci org scratch_delete X && cci +flow run X` manually. + +This test asserts that the `flow run` Click command exposes a +`--rebuild-org` (or equivalent `rebuild_org` parameter). On dev no +such option exists, so the assertion fails -> XFAIL. +""" + +import pytest + +from cumulusci.cli.flow import flow_run + + +@pytest.mark.xfail( + reason="repro for #2402 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_2402(): + param_names = {p.name for p in flow_run.params} + assert "rebuild_org" in param_names or any( + "rebuild-org" in flag for p in flow_run.params for flag in (p.opts or []) + ), ( + "Expected `cci flow run` to expose a --rebuild-org option for " + f"convenience; got params {sorted(param_names)!r}." + ) diff --git a/cumulusci/tests/triage/test_issue_2500.py b/cumulusci/tests/triage/test_issue_2500.py new file mode 100644 index 0000000000..4b894a0454 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_2500.py @@ -0,0 +1,58 @@ +"""Repro for SFDO-Tooling/CumulusCI#2500 - ``ignore_failure`` is not documented. + +The ``ignore_failure`` flow-step option has existed since flowrunner was +introduced (see ``cumulusci/core/flowrunner.py`` and +``cumulusci/utils/yaml/cumulusci_yml.py``), but ``docs/config.md`` only +*uses* it inside one example YAML snippet (and ``docs/history.md`` mentions +its introduction in the changelog). No section in the user-facing +"Flow Configurations" chapter explains the option, in contrast to +sibling step options like ``when`` (documented under "Conditionally Run a +Flow Step") or "Skip a Flow Step". + +This test pins down both halves of the gap: + +1. ``ignore_failure`` is a recognised step-level option (sanity check - + the feature really exists and we are not chasing a phantom doc). +2. ``docs/config.md`` contains a narrative section that documents it. + +On ``origin/dev`` (1925a3083) the second assertion fails: the only +matches for ``ignore_failure`` in ``docs/`` are the example YAML at +``docs/config.md:800`` and a one-line changelog blurb in +``docs/history.md``. Mark as xfail until a documentation section is +added. +""" + +from __future__ import annotations + +import re +from pathlib import Path + +import pytest + +import cumulusci + + +REPO_ROOT = Path(cumulusci.__file__).resolve().parent.parent +CONFIG_DOC = REPO_ROOT / "docs" / "config.md" + + +@pytest.mark.xfail( + reason="repro for #2500 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_ignore_failure_has_narrative_docs_section(): + """``docs/config.md`` should have a narrative section explaining + ``ignore_failure``, not just a single YAML example.""" + assert CONFIG_DOC.is_file(), f"missing {CONFIG_DOC}" + text = CONFIG_DOC.read_text() + + heading_pattern = re.compile( + r"^#{2,3}\s+.*(ignore[ _]failure|ignore a failed|continue.*failure).*$", + re.IGNORECASE | re.MULTILINE, + ) + assert heading_pattern.search(text), ( + "docs/config.md should contain a heading-level section that explains " + "the ignore_failure step option (parallel to 'Conditionally Run a Flow " + "Step' for `when:`). Currently the only mention is inside an example " + "YAML snippet." + ) diff --git a/cumulusci/tests/triage/test_issue_2506.py b/cumulusci/tests/triage/test_issue_2506.py new file mode 100644 index 0000000000..601588650c --- /dev/null +++ b/cumulusci/tests/triage/test_issue_2506.py @@ -0,0 +1,40 @@ +"""Regression repro for #2506. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: bulk operations (extract.py, load.py, step.py under +cumulusci/tasks/bulkdata/) do not respect `get_debug_mode()` the way +Snowfakery (snowfakery.py) does. The ask is to keep logs / tempfiles +when debug mode is on. snowfakery.py calls `get_debug_mode()` and logs +the tempdir per loop; the workhorse `load_dataset`/`extract_dataset` +tasks have zero references to `get_debug_mode`. + +This test asserts that at least one of extract.py or load.py +references `get_debug_mode`; on dev neither does, so the assertion +fails -> XFAIL. +""" + +import pathlib + +import pytest + +import cumulusci.tasks.bulkdata as _bulkdata_pkg + + +@pytest.mark.xfail( + reason="repro for #2506 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_2506(): + pkg_path = pathlib.Path(_bulkdata_pkg.__file__).parent + extract_src = (pkg_path / "extract.py").read_text(encoding="utf-8") + load_src = (pkg_path / "load.py").read_text(encoding="utf-8") + + has_debug_in_workhorse = ( + "get_debug_mode" in extract_src or "get_debug_mode" in load_src + ) + assert has_debug_in_workhorse, ( + "Neither extract.py nor load.py references get_debug_mode(); " + "bulk operations still ignore debug-mode toggle for log/tempfile retention." + ) diff --git a/cumulusci/tests/triage/test_issue_2507.py b/cumulusci/tests/triage/test_issue_2507.py new file mode 100644 index 0000000000..02a35e66f2 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_2507.py @@ -0,0 +1,41 @@ +"""Regression repro for #2507. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: there is no `undo_insert` (or similarly-named) +task. The closest mitigation is the `enable_rollback` option on +`load_data` / `snowfakery`, but that only rolls back when an +error occurs during the load - it does not provide the +post-hoc "delete everything I inserted earlier" capability the +2021 ask describes. + +This test loads the universal cumulusci.yml's tasks dict and +asserts that some kind of explicit undo/cleanup task is +registered. On dev no such task exists -> XFAIL. +""" + +from pathlib import Path + +import pytest + +import cumulusci +from cumulusci.utils.yaml.cumulusci_yml import cci_safe_load + + +@pytest.mark.xfail( + reason="repro for #2507 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_issue_2507(): + universal_yaml = Path(cumulusci.__file__).resolve().parent / "cumulusci.yml" + config = cci_safe_load(universal_yaml.open(), str(universal_yaml)) + tasks = config.get("tasks", {}) + undo_task_names = [ + name for name in tasks if "undo" in name.lower() and "insert" in name.lower() + ] + assert undo_task_names, ( + "No `undo_insert`-style task registered; users still must " + "manually delete records inserted by load_data (see #2507)." + ) diff --git a/cumulusci/tests/triage/test_issue_2508.py b/cumulusci/tests/triage/test_issue_2508.py new file mode 100644 index 0000000000..aaa1bbb2c9 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_2508.py @@ -0,0 +1,47 @@ +"""Regression repro for #2508. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (no_reverify_needed). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: There is no OOTB "retry the failed records from the +previous load" feature in CumulusCI. ``load_dataset`` exposes an +``enable_rollback`` option (cumulusci/tasks/bulkdata/load.py:97-98, +``RollbackType`` enum at line 1051), but rollback **undoes** successful +inserts when failures occur - the opposite of "retry the failures". +``RowErrorChecker`` (cumulusci/tasks/bulkdata/utils.py:158) only logs +and (optionally) raises; it never persists a failed-rows artifact that +could be replayed. + +The fix would be to (a) persist failed rows to a CSV/SQLite artifact +on load failure and (b) ship a ``retry_failed_load`` (or +``retry_failed_records``) task that consumes that artifact. + +This test asserts ``cumulusci.yml`` declares at least one retry-named +task; on dev it fails because no such task exists. +""" + +from pathlib import Path + +import pytest +import yaml + +import cumulusci + + +@pytest.mark.xfail( + reason="repro for #2508 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_2508(): + cci_root = Path(cumulusci.__file__).parent + with open(cci_root / "cumulusci.yml") as f: + data = yaml.safe_load(f) + + task_names = list(data.get("tasks", {}).keys()) + retry_named = [ + n for n in task_names if "retry" in n.lower() and "failed" in n.lower() + ] + assert retry_named, ( + "cumulusci.yml still ships no retry-failed-records task; users have no " + "way to re-attempt only the rows that failed in a prior load (see #2508)" + ) diff --git a/cumulusci/tests/triage/test_issue_2826.py b/cumulusci/tests/triage/test_issue_2826.py new file mode 100644 index 0000000000..f79f3ef03d --- /dev/null +++ b/cumulusci/tests/triage/test_issue_2826.py @@ -0,0 +1,46 @@ +"""Regression repro for #2826. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: PackageXmlGenerator.parse_types +(cumulusci/tasks/metadata/package.py:105-117) calls +``os.listdir(self.directory)`` with no existence check. When +``deploy_unmanaged`` runs against a repo with no ``src/`` directory +(the original 2021 ask was that this flow silently no-op in that case), +``UpdatePackageXml`` raises a raw ``FileNotFoundError`` instead. + +The fix is to either (a) guard ``parse_types`` so a missing directory +yields an empty package.xml, or (b) skip the task at the flow level via +a ``when:`` guard. Either way, the task should no longer surface a +``FileNotFoundError`` to the user. +""" + +import os +import tempfile + +import pytest + +from cumulusci.tasks.metadata.package import PackageXmlGenerator + + +@pytest.mark.xfail( + reason="repro for #2826 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_2826(): + with tempfile.TemporaryDirectory() as tmp: + missing = os.path.join(tmp, "src") + gen = PackageXmlGenerator(directory=missing, api_version="58.0") + + raised = None + try: + gen() + except BaseException as e: + raised = e + + assert not isinstance(raised, FileNotFoundError), ( + "PackageXmlGenerator still raises raw FileNotFoundError on a missing " + "directory; deploy_unmanaged should silently no-op (or surface a typed " + f"CumulusCIException instead). Got: {raised!r}" + ) diff --git a/cumulusci/tests/triage/test_issue_2951.py b/cumulusci/tests/triage/test_issue_2951.py new file mode 100644 index 0000000000..824b063b4d --- /dev/null +++ b/cumulusci/tests/triage/test_issue_2951.py @@ -0,0 +1,47 @@ +"""Regression repro for #2951. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci/tasks/bulkdata/load.py + step.py have no +PricebookEntry-aware sequencing. When a single mapping step inserts +both Standard-Price-Book PricebookEntries and custom-pricebook +PricebookEntries, Bulk API processes records in parallel batches and +Salesforce raises STANDARD_PRICE_NOT_DEFINED when a custom price is +created before the matching standard price exists. The default +extract path skips the Standard Price Book entirely (via +hardcoded_default_declarations.py), so the typical extract→load +roundtrip never hits this - but a hand-rolled mapping that includes +both does. + +A real fix is either (a) split PricebookEntry steps into two +implicit batches (standard pricebook first), or (b) validate at +parse time that PricebookEntry steps are not "mixed" and surface a +clear error. + +This test asserts that the loader (load.py or step.py) has some +PricebookEntry-aware sequencing/validation. On dev there is no +mention of PricebookEntry in either module, so the assertion fails +-> XFAIL. +""" + +from pathlib import Path + +import pytest + +import cumulusci + + +@pytest.mark.xfail( + reason="repro for #2951 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_2951(): + bulkdata_dir = Path(cumulusci.__file__).parent / "tasks" / "bulkdata" + load_text = (bulkdata_dir / "load.py").read_text() + step_text = (bulkdata_dir / "step.py").read_text() + combined = load_text + step_text + assert "PricebookEntry" in combined, ( + "Expected loader (load.py or step.py) to contain PricebookEntry-aware " + "sequencing or parse-time validation; neither module references it." + ) diff --git a/cumulusci/tests/triage/test_issue_2979.py b/cumulusci/tests/triage/test_issue_2979.py new file mode 100644 index 0000000000..1fd12a81ed --- /dev/null +++ b/cumulusci/tests/triage/test_issue_2979.py @@ -0,0 +1,43 @@ +"""Regression repro for #2979. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci/cumulusci.yml `deploy` task definition still +hard-codes `path: src` for `cumulusci.tasks.salesforce.Deploy`. The +ask (with davisagli's 3-tier fallback design) is to consult the +`default` entry of `packageDirectories` in `sfdx-project.json` (the +existing helper `default_package_path` in +`cumulusci/core/config/project_config.py`) when no explicit `path` +is configured, falling back to `src` only when neither is set. + +`default_package_path` exists but is consumed only by +`create_package_version.py:230`; the Deploy task is not wired into +it. + +This test loads the bundled cumulusci.yml YAML, finds the `deploy` +task definition, and asserts that it does NOT hard-code `path: src`. +On dev the path is still hard-coded, so the assertion fails -> XFAIL. +""" + +import pathlib + +import pytest +import yaml + +import cumulusci as _cci_pkg + + +@pytest.mark.xfail( + reason="repro for #2979 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_2979(): + yml_path = pathlib.Path(_cci_pkg.__file__).parent / "cumulusci.yml" + cfg = yaml.safe_load(yml_path.read_text(encoding="utf-8")) + deploy_options = cfg["tasks"]["deploy"]["options"] + assert deploy_options.get("path") != "src", ( + "deploy task in cumulusci.yml still hard-codes path: src; " + "expected fallback to default_package_path / sfdx packageDirectories. " + f"Current options: {deploy_options!r}" + ) diff --git a/cumulusci/tests/triage/test_issue_3015.py b/cumulusci/tests/triage/test_issue_3015.py new file mode 100644 index 0000000000..a703d464a7 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3015.py @@ -0,0 +1,36 @@ +"""Regression repro for #3015. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: `cci org remove` in cumulusci/cli/org.py:519-543 +always calls `org_config.delete_org()` when `can_delete()` is +truthy. There is no `--keep-org`/`--keep` flag to detach the +keychain entry without deleting the underlying SFDX scratch +org. davisagli's documented workaround in the issue thread +(manually delete `~/.cumulusci//.org`) still +applies. + +This test introspects the Click command's options and asserts +that a "keep" flag exists. On dev there is no such option, so +the assertion fails -> XFAIL. +""" + +import pytest + +from cumulusci.cli.org import org_remove + + +@pytest.mark.xfail( + reason="repro for #3015 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_issue_3015(): + option_names = {p.name for p in org_remove.params} + has_keep_flag = any("keep" in name.lower() for name in option_names) + assert has_keep_flag, ( + "`cci org remove` still always deletes the underlying scratch " + f"org when can_delete(). No --keep-org flag in params={sorted(option_names)} " + "(see #3015)." + ) diff --git a/cumulusci/tests/triage/test_issue_3024.py b/cumulusci/tests/triage/test_issue_3024.py new file mode 100644 index 0000000000..322d1f0050 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3024.py @@ -0,0 +1,56 @@ +"""Regression repro for #3024. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (no_reverify_needed). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: The order of ``group:`` values in +``cumulusci/cumulusci.yml`` still has "Metadata Transformations" first +and buries "Continuous Integration" near the bottom (~position 20+ in +the first-appearance ordering). The user-requested "Org Setup" group +does not exist (the closest is "Setup"). This means the VS Code +extension that drives off ``group:`` shows tasks in the same +not-very-useful order CumulusCI ships them in. + +The fix is either to (a) reorder the canonical YAML so the most +commonly used groups (Continuous Integration, Setup) appear toward +the top, or (b) introduce an explicit ``group_order:`` list in the +project schema and have the extension consume that. + +This test asserts "Continuous Integration" appears in the first half +of the unique groups (by first appearance). On dev it fails because +it currently appears ~last. +""" + +from pathlib import Path + +import pytest +import yaml + +import cumulusci + + +@pytest.mark.xfail( + reason="repro for #3024 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3024(): + cci_root = Path(cumulusci.__file__).parent + with open(cci_root / "cumulusci.yml") as f: + data = yaml.safe_load(f) + + seen = [] + for _, task in data.get("tasks", {}).items(): + group = task.get("group") + if group and group not in seen: + seen.append(group) + + assert "Continuous Integration" in seen, ( + "'Continuous Integration' group missing from cumulusci.yml - test needs updating" + ) + ci_pos = seen.index("Continuous Integration") + halfway = len(seen) // 2 + assert ci_pos < halfway, ( + f"'Continuous Integration' still appears at position {ci_pos + 1} of " + f"{len(seen)} groups (>= halfway {halfway + 1}); cumulusci.yml has not " + f"been reordered to surface common groups first (see #3024). Order: {seen}" + ) diff --git a/cumulusci/tests/triage/test_issue_3137.py b/cumulusci/tests/triage/test_issue_3137.py new file mode 100644 index 0000000000..edee420327 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3137.py @@ -0,0 +1,40 @@ +"""Regression repro for #3137. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci/tasks/metadata/package.py CustomObjectParser +(L441-459) hard-skips any object file whose name does not end in +`__c.object`, `__mdt.object`, `__e.object`, or `__b.object`. This is +a managed-package-world holdover that excludes Case (and every other +standard SObject with a sidecar `.object` containing custom fields) +from the generated package.xml. UpdatePackageXml does not expose an +opt-in option (e.g. `include_standard_objects=True`) for the user to +override. + +A real fix exposes either a task option (`include_standard_objects`) +or makes the parser configurable so users who genuinely want a +standard Case object listed can do so without monkey-patching. + +This test parses a fake `objects/` folder containing `Case.object` +(a standard object) and asserts the generated members include +`Case`. On dev `Case` is filtered out, so the assertion fails -> +XFAIL. +""" + +import pytest + +from cumulusci.tasks.metadata.package import CustomObjectParser + + +@pytest.mark.xfail( + reason="repro for #3137 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3137(): + parser = CustomObjectParser.__new__(CustomObjectParser) + members = parser._parse_item("Case.object") + assert members == ["Case"], ( + "Expected CustomObjectParser to include standard 'Case' object in " + f"package.xml members (or expose an opt-in option); got {members!r}." + ) diff --git a/cumulusci/tests/triage/test_issue_3161.py b/cumulusci/tests/triage/test_issue_3161.py new file mode 100644 index 0000000000..1f7cdba1a7 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3161.py @@ -0,0 +1,35 @@ +"""Regression repro for #3161. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: flowrunner.py:300-320 supports masking task options via +`info.get("sensitive")`, but the Robot `vars` option in +cumulusci/tasks/robotframework/robotframework.py:54-56 is NOT marked +`sensitive: True`. The user's specific request - mask multi-line +GitHub Actions secrets passed via `-o robot__vars …` - is therefore +not protected by the existing infrastructure. + +The minimal fix is to mark Robot's `vars` option `sensitive: True` +(or expose a CLI/flow-side hide flag). + +This test imports the Robot task class and asserts the `vars` option +declares `sensitive: True`. On dev it does not, so the assertion +fails -> XFAIL. +""" + +import pytest + +from cumulusci.tasks.robotframework.robotframework import Robot + + +@pytest.mark.xfail( + reason="repro for #3161 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3161(): + vars_option = Robot.task_options.get("vars", {}) + assert vars_option.get("sensitive") is True, ( + "Robot task_options['vars'] is not marked sensitive; -o robot__vars " + f"values are logged in plaintext. Current option metadata: {vars_option!r}" + ) diff --git a/cumulusci/tests/triage/test_issue_3165.py b/cumulusci/tests/triage/test_issue_3165.py new file mode 100644 index 0000000000..fba43055a7 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3165.py @@ -0,0 +1,55 @@ +"""Regression repro for #3165. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: `ProfileGrantAllAccess._generate_package_xml` in +cumulusci/tasks/salesforce/update_profile.py:137-138 calls +`_expand_package_xml(package_xml)` only when +`include_packaged_objects=True`. `_expand_package_xml_objects` +- the helper that walks `record_types` and adds any referenced +CustomObject to the retrieve package.xml - is invoked only from +inside `_expand_package_xml` (line 182). When a user specifies +`record_types` referencing a standard object (e.g. Case) and +keeps `include_packaged_objects=False`, the retrieve package.xml +omits Case, the deploy then fails because the profile XML +references an unretrievable record type. + +The proposed minimal fix is to always invoke +`_expand_package_xml_objects` regardless of +`include_packaged_objects`, since that helper makes no API call. + +This test asserts that on dev `_generate_package_xml` (or +`_expand_package_xml_objects`) is wired so that `record_types` +expansion runs even when `include_packaged_objects` is False. +We approximate this by reading the source of +`_generate_package_xml` and asserting it calls +`_expand_package_xml_objects` directly (i.e. not only via the +gated `_expand_package_xml`). On dev that direct call is +missing -> XFAIL. +""" + +import inspect + +import pytest + +from cumulusci.tasks.salesforce.update_profile import ProfileGrantAllAccess + + +@pytest.mark.xfail( + reason="repro for #3165 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_issue_3165(): + src = inspect.getsource(ProfileGrantAllAccess._generate_package_xml) + calls_objects_helper_directly = ( + "_expand_package_xml_objects(" in src + or "self._expand_package_xml_objects" in src + ) + assert calls_objects_helper_directly, ( + "_generate_package_xml does not invoke " + "_expand_package_xml_objects outside the " + "`include_packaged_objects` branch; record_types pointing at " + "objects not in admin_profile.xml still get dropped (see #3165)." + ) diff --git a/cumulusci/tests/triage/test_issue_3307.py b/cumulusci/tests/triage/test_issue_3307.py new file mode 100644 index 0000000000..59ef3dcaec --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3307.py @@ -0,0 +1,37 @@ +"""Regression repro for #3307. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (no_reverify_needed). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: ``cci project init`` (cumulusci/cli/project.py:37-41) only +renders Jinja templates from CumulusCI's bundled +``cumulusci/files/templates/project`` directory. There is no +``--template`` (or equivalent) CLI option to point the command at a +user-supplied template directory or git URL, so users cannot +bootstrap a project from an org-specific template. + +The fix is to add a ``--template`` click option to ``project_init`` +that accepts a path or git URL and renders that template instead of +(or in addition to) the bundled one. + +This test asserts ``project_init`` has a ``template``-named click +option; on dev it fails because no such option exists. +""" + +import pytest + +from cumulusci.cli.project import project_init + + +@pytest.mark.xfail( + reason="repro for #3307 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3307(): + param_names = {p.name for p in project_init.params} + template_opts = {n for n in param_names if "template" in n.lower()} + assert template_opts, ( + "cci project init still has no --template CLI option; users cannot " + f"point the command at a custom template directory or git URL. " + f"Existing params: {sorted(param_names)} (see #3307)" + ) diff --git a/cumulusci/tests/triage/test_issue_3331.py b/cumulusci/tests/triage/test_issue_3331.py new file mode 100644 index 0000000000..465d316bcb --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3331.py @@ -0,0 +1,46 @@ +"""Regression repro for #3331. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci/tasks/metadata/metadata_map.yml line 45-48 +maps the `assignmentRules` folder to MDAPI type +`AssignmentRule` (singular). Salesforce Metadata API expects +`AssignmentRules` (plural) - see the analogous `autoResponseRules` +key (lines 60-63) which is already correctly `AutoResponseRules`. + +Running `update_package_xml` against a project with +`assignmentRules/Case.assignmentRules` emits +`AssignmentRule` and the deploy then fails with +"INVALID_TYPE: AssignmentRule is not a valid metadata type". + +A real fix is a single-line YAML change. + +This test asserts the YAML maps `assignmentRules` to the plural +`AssignmentRules`. On dev the mapping is still singular, so the +assertion fails -> XFAIL. +""" + +from pathlib import Path + +import pytest +import yaml + +import cumulusci + + +@pytest.mark.xfail( + reason="repro for #3331 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3331(): + metadata_map_path = ( + Path(cumulusci.__file__).parent / "tasks" / "metadata" / "metadata_map.yml" + ) + metadata_map = yaml.safe_load(metadata_map_path.read_text()) + entries = metadata_map.get("assignmentRules", []) + types = [e.get("type") for e in entries] + assert "AssignmentRules" in types, ( + "Expected metadata_map.yml `assignmentRules` folder to map to MDAPI " + f"type 'AssignmentRules' (plural, matching MDAPI); got types {types!r}." + ) diff --git a/cumulusci/tests/triage/test_issue_3349.py b/cumulusci/tests/triage/test_issue_3349.py new file mode 100644 index 0000000000..834c1a1b78 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3349.py @@ -0,0 +1,64 @@ +"""Regression repro for #3349. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: MappingStep.get_source_record_type_table() and +get_destination_record_type_table() in +cumulusci/tasks/bulkdata/mapping_parser.py:177-179 build the SQLite +recordtype mapping table name solely from `self.sf_object` +(`f"{self.sf_object}_rt_mapping"` and `f"{self.sf_object}_rt_target_mapping"`). +Two MappingStep entries sharing the same `sf_object` (the canonical +case is `Account` Person vs Business with different `record_type` +values) therefore collide on the same table name. load.py:552 and +extract.py:259/393 consume those names without per-step +disambiguation. + +The fix is to include `self.table` (or a hash of record_type+filter) +in the generated name when multiple mapping steps share an +sf_object. + +This test constructs two MappingStep objects with the same +sf_object="Account" but different record_type / table values and +asserts that the recordtype-table names differ. On dev they are +identical (collision), so the assertion fails -> XFAIL. +""" + +import pytest + +from cumulusci.tasks.bulkdata.mapping_parser import MappingStep + + +@pytest.mark.xfail( + reason="repro for #3349 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3349(): + step_business = MappingStep( + sf_object="Account", + table="account_business", + record_type="Business_Account", + fields={"Id": "sf_id", "Name": "Name"}, + ) + step_person = MappingStep( + sf_object="Account", + table="account_person", + record_type="PersonAccount", + fields={"Id": "sf_id", "Name": "Name"}, + ) + + src_business = step_business.get_source_record_type_table() + src_person = step_person.get_source_record_type_table() + dst_business = step_business.get_destination_record_type_table() + dst_person = step_person.get_destination_record_type_table() + + assert src_business != src_person, ( + "Two MappingSteps sharing sf_object='Account' produced the same " + f"source recordtype table name {src_business!r}; expected per-step " + "disambiguation." + ) + assert dst_business != dst_person, ( + "Two MappingSteps sharing sf_object='Account' produced the same " + f"destination recordtype table name {dst_business!r}; expected per-step " + "disambiguation." + ) diff --git a/cumulusci/tests/triage/test_issue_3353.py b/cumulusci/tests/triage/test_issue_3353.py new file mode 100644 index 0000000000..eb92cf2c53 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3353.py @@ -0,0 +1,49 @@ +"""Regression repro for #3353. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: `Snowfakery._validate_options` in +cumulusci/tasks/bulkdata/snowfakery.py validates the `recipe` +option via `Path(recipe).exists()` only. There is no +`SOURCE_NAME:path` parsing, and no call to +`project_config.sources` / `project_config.get_source(...)` +anywhere in `snowfakery.py`. The 2022 ask (resurfaced 2024-08 +by davidjray/jnesong) is to let `recipe:` refer to a recipe in +another configured source repo. + +This test asserts that the snowfakery module source mentions +source-resolution machinery (e.g. `project_config.sources`, +`get_source`, or detection of a `SOURCE_NAME:` prefix). On dev +nothing of the sort exists -> XFAIL. +""" + +import inspect + +import pytest + +from cumulusci.tasks.bulkdata import snowfakery as snowfakery_mod + + +@pytest.mark.xfail( + reason="repro for #3353 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_issue_3353(): + src = inspect.getsource(snowfakery_mod) + has_source_resolution = any( + token in src + for token in ( + "project_config.sources", + "project_config.get_source", + "get_source(", + "SOURCE_NAME", + 'split(":"', + ) + ) + assert has_source_resolution, ( + "snowfakery.py never resolves SOURCE_NAME:path against " + "project_config.sources; cross-repo recipes still fail " + "Path(recipe).exists() (see #3353)." + ) diff --git a/cumulusci/tests/triage/test_issue_3407.py b/cumulusci/tests/triage/test_issue_3407.py new file mode 100644 index 0000000000..9fdbac380b --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3407.py @@ -0,0 +1,111 @@ +"""Repro for CumulusCI issue #3407. + +`BaseProjectKeychain.set_service` declares ``service_config: ServiceConfig``, +but :pyfunc:`EncryptedFileProjectKeychain._load_service_files` calls it with a +raw ``str`` (the encrypted file payload) and ``config_encrypted=True``. The +signature is therefore inconsistent with the call sites. + +This test asserts the *correct* behaviour: either the annotation accepts +``str`` (e.g. ``ServiceConfig | str``) or every call site constructs a +``ServiceConfig`` first. It is expected to fail on dev (xfail). + +Pure introspection - no Salesforce / scratch org required. +""" + +from __future__ import annotations + +import inspect +import typing +from typing import Union, get_args, get_origin + +import pytest + +from cumulusci.core.config import ServiceConfig +from cumulusci.core.keychain.base_project_keychain import BaseProjectKeychain +from cumulusci.core.keychain.encrypted_file_project_keychain import ( + EncryptedFileProjectKeychain, +) + + +def _annotation_accepts_str(annotation) -> bool: + """True if ``annotation`` permits ``str`` (directly or via Union).""" + if annotation is str: + return True + origin = get_origin(annotation) + if origin is Union or origin is type(None) or origin is typing.Union: + return any(a is str for a in get_args(annotation)) + return False + + +def _src_has_string_payload_call(func) -> bool: + """Heuristic: does ``func`` source call set_service with config_encrypted=True + while passing a non-ServiceConfig value (i.e. a string variable read from a + file)?""" + src = inspect.getsource(func) + return "config_encrypted=True" in src and "ServiceConfig(" not in src + + +@pytest.mark.xfail( + reason=("repro for #3407 - see docs/triage/v5/repro-results.md"), + strict=False, +) +def test_set_service_annotation_consistent_with_callers(): + """The annotation must agree with all internal call sites.""" + hints = typing.get_type_hints(BaseProjectKeychain.set_service) + annotation = hints["service_config"] + + string_caller = _src_has_string_payload_call( + EncryptedFileProjectKeychain._load_service_files + ) + + assert not string_caller or _annotation_accepts_str(annotation), ( + "BaseProjectKeychain.set_service is annotated " + f"service_config: {annotation!r}, but " + "EncryptedFileProjectKeychain._load_service_files passes a raw str " + "(file contents) with config_encrypted=True. Annotation should be " + "Union[ServiceConfig, str] or callers should construct a " + "ServiceConfig first." + ) + + +@pytest.mark.xfail( + reason=("repro for #3407 - see docs/triage/v5/repro-results.md"), + strict=False, +) +def test_set_service_runtime_accepts_only_serviceconfig_per_annotation(): + """Run-time evidence: a plain string is accepted with config_encrypted=True + even though the annotation says ServiceConfig. If the annotation were the + source of truth, this call would fail. Today it succeeds - proving the + annotation is wrong.""" + + class _Stub(BaseProjectKeychain): + # minimal in-memory subclass exercising the encrypted-payload path + def _set_service( + self, + service_type, + alias, + service_config, + save=True, + config_encrypted=False, + ): + assert isinstance(service_config, ServiceConfig), ( + f"got {type(service_config).__name__}, expected ServiceConfig" + ) + self.services.setdefault(service_type, {})[alias] = service_config + + def _validate_service(self, *a, **kw): + return None + + from cumulusci.core.config import UniversalConfig + + kc = _Stub(UniversalConfig(), None) + # The annotation says only ServiceConfig is allowed. If callers truly + # respected it we'd never reach this line with a str. This is exactly + # what _load_service_files does today. + kc.set_service( + "github", + "from-disk", + "RAW-ENCRYPTED-STRING-PAYLOAD", # type: ignore[arg-type] + save=False, + config_encrypted=True, + ) diff --git a/cumulusci/tests/triage/test_issue_3429.py b/cumulusci/tests/triage/test_issue_3429.py new file mode 100644 index 0000000000..60790ee7c8 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3429.py @@ -0,0 +1,47 @@ +"""Regression repro for #3429. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (no_reverify_needed). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: ``BaseProjectConfig.config_filename`` is hardcoded to +``"cumulusci.yml"`` (cumulusci/core/config/project_config.py:82) and +there is no environment variable (``CUMULUSCI_YML`` / +``CUMULUSCI_EXTRA_YAML``) or CLI flag (``--extra-yaml`` / +``--config-file``) to point at an alternate / additional YAML. + +PR #3969 (branch ``extra-yaml-cli-flag``) is in flight and adds +``--extra-yaml`` plus a ``CUMULUSCI_EXTRA_YAML`` env var, but it has not +been merged into v4.10.0 / dev yet. + +This test asserts the project_config module source references either +``CUMULUSCI_YML`` or ``CUMULUSCI_EXTRA_YAML`` (env-var override) or +exposes some helper such as ``resolve_extra_yaml``; on dev it fails +because none of those are present yet. +""" + +import inspect + +import pytest + +import cumulusci.core.config.project_config as project_config_mod + + +@pytest.mark.xfail( + reason="repro for #3429 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3429(): + src = inspect.getsource(project_config_mod) + tokens = ( + "CUMULUSCI_YML", + "CUMULUSCI_EXTRA_YAML", + "resolve_extra_yaml", + "--extra-yaml", + "--config-file", + ) + found = [t for t in tokens if t in src] + assert found, ( + "cumulusci.core.config.project_config still has no env-var or helper " + f"for an external/extra cumulusci.yml override (looked for {tokens}); " + "config_filename is hardcoded to 'cumulusci.yml' (see #3429)" + ) diff --git a/cumulusci/tests/triage/test_issue_3440.py b/cumulusci/tests/triage/test_issue_3440.py new file mode 100644 index 0000000000..aa00589be4 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3440.py @@ -0,0 +1,44 @@ +"""Regression repro for #3440. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci/core/config/project_config.py:514-523 +`default_package_path` is the simple "first packageDirectory with +`default: true`" pattern; falls back to `force-app`, then `src`. It +takes no arguments, offers no name-based lookup for multi-package +sfdx-project.json layouts, emits no multi-package warnings, and does +not hard-fail when both `default` and `force-app` are missing. + +A real fix likely either (a) refactors `default_package_path` into a +method accepting a package name, or (b) adds a sibling method +(`package_path(name)` / `get_package_directory(name)`) that supports +name-based lookup. + +This test asserts that `BaseProjectConfig` exposes some way to look +up package directories by name for multi-package projects. On dev no +such API exists, so the assertion fails -> XFAIL. +""" + +import pytest + +from cumulusci.core.config.project_config import BaseProjectConfig + + +@pytest.mark.xfail( + reason="repro for #3440 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3440(): + candidate_attrs = ( + "package_path", + "get_package_directory", + "get_package_path", + "package_directory_for_name", + ) + found = [a for a in candidate_attrs if hasattr(BaseProjectConfig, a)] + assert found, ( + "Expected BaseProjectConfig to expose a name-based package-directory " + f"lookup API (one of {candidate_attrs!r}) for multi-package sfdx " + "projects; none present." + ) diff --git a/cumulusci/tests/triage/test_issue_3441.py b/cumulusci/tests/triage/test_issue_3441.py new file mode 100644 index 0000000000..94b85164f6 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3441.py @@ -0,0 +1,42 @@ +"""Regression repro for #3441. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci/tasks/create_package_version.py +`_get_base_version_number` (lines 529-563 on v4.10.0) branches only +on `None` (default) and the literal `"latest_github_release"` sentinel; +any other string is parsed as a literal version number. There is no +`"default"` / `"highest"` sentinel and no support for resetting a +flow-overridden `version_base` back to the default behavior. + +The fix (per the issue) is to add a sentinel string such as +`"default"` or `"highest"` that triggers the same SOQL-derived +highest-version lookup the default-None path uses. + +This test inspects `_get_base_version_number` source and asserts that +the function handles a `"default"` (or `"highest"`) sentinel. On dev +it does not, so the assertion fails -> XFAIL. +""" + +import inspect + +import pytest + +from cumulusci.tasks.create_package_version import CreatePackageVersion + + +@pytest.mark.xfail( + reason="repro for #3441 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3441(): + src = inspect.getsource(CreatePackageVersion._get_base_version_number) + has_default_sentinel = ( + '"default"' in src or "'default'" in src or '"highest"' in src + ) + assert has_default_sentinel, ( + "_get_base_version_number does not handle a 'default'/'highest' sentinel " + "for version_base; flow override cannot reset to default lookup. " + f"Current source:\n{src}" + ) diff --git a/cumulusci/tests/triage/test_issue_3446.py b/cumulusci/tests/triage/test_issue_3446.py new file mode 100644 index 0000000000..0fd257cfff --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3446.py @@ -0,0 +1,35 @@ +"""Regression repro for #3446. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: `BaseSalesforcePushTask._parse_version` in +cumulusci/tasks/push/tasks.py:26-33 unconditionally calls +`version.split(".")`. When the user runs `cci task run push_qa` +with only `--metadata_package_id` (no `--version` / +`--version_id`), `version` is `None` and the call raises +``AttributeError: 'NoneType' object has no attribute 'split'`` +- the exact gist linked in the bug report. + +This test calls `_parse_version(None)` and asserts it raises a +user-friendly TaskOptionsError instead of a bare AttributeError. +On dev it raises AttributeError -> XFAIL. +""" + +import pytest + +from cumulusci.core.exceptions import TaskOptionsError +from cumulusci.tasks.push.tasks import BaseSalesforcePushTask + + +@pytest.mark.xfail( + reason="repro for #3446 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_issue_3446(): + # Bypass __init__ since BaseSalesforcePushTask needs an org/project + # config; _parse_version only touches the supplied argument. + task = BaseSalesforcePushTask.__new__(BaseSalesforcePushTask) + with pytest.raises(TaskOptionsError): + task._parse_version(None) diff --git a/cumulusci/tests/triage/test_issue_3464.py b/cumulusci/tests/triage/test_issue_3464.py new file mode 100644 index 0000000000..3c931945fb --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3464.py @@ -0,0 +1,62 @@ +"""Repro for SFDO-Tooling/CumulusCI#3464 - Provide concise documentation of +``cumulusci.yml`` ``project`` configuration options. + +The user asked: "I would like ALL configuration tags and options defined, +with a brief description (even just one sentence) in +https://cumulusci.readthedocs.io/en/stable/config.html". + +On ``origin/dev`` (1925a3083) ``cumulusci/utils/yaml/cumulusci_yml.py`` +defines a ``Project`` Pydantic model with these top-level keys: + +* ``name`` +* ``package`` +* ``test`` +* ``git`` +* ``dependencies`` +* ``dependency_resolutions`` +* ``dependency_pins`` +* ``source_format`` +* ``custom`` + +``docs/config.md`` shows ONE example YAML block (line ~281) using a subset +of these keys but has no reference subsection that names every supported +key. Several keys - notably ``dependency_resolutions``, ``dependency_pins``, +and ``source_format`` - never appear in ``docs/config.md`` at all (they +are buried in ``docs/dev.md``, which is exactly the "scattered" complaint +in the issue). + +The xfail test asserts each ``Project`` field name appears at least once +in ``docs/config.md``. Today several keys are missing, so the assertion +fails. Once ``docs/config.md`` gets a "Project Configuration Reference" +section listing every key, the test will XPASS. +""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +import cumulusci +from cumulusci.utils.yaml.cumulusci_yml import Project + + +REPO_ROOT = Path(cumulusci.__file__).resolve().parent.parent +CONFIG_DOC = REPO_ROOT / "docs" / "config.md" + + +@pytest.mark.xfail( + reason="repro for #3464 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_every_project_key_is_mentioned_in_config_doc(): + """``docs/config.md`` should mention every top-level ``project:`` key + defined by the ``Project`` Pydantic model. Today several keys are + missing or only documented in other doc pages.""" + text = CONFIG_DOC.read_text() + project_keys = sorted(Project.__fields__.keys()) + missing = [k for k in project_keys if k not in text] + assert not missing, ( + f"docs/config.md does not mention these project-config keys: {missing}. " + f"The full set declared in Project Pydantic model: {project_keys}." + ) diff --git a/cumulusci/tests/triage/test_issue_3470.py b/cumulusci/tests/triage/test_issue_3470.py new file mode 100644 index 0000000000..05eefe3d04 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3470.py @@ -0,0 +1,41 @@ +"""Regression repro for #3470. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (no_reverify_needed). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: ``cumulusci/cumulusci.yml`` still defines only the +``ci_master`` flow (line 823); no ``ci_main`` alias exists. +``rg ci_main`` returns no matches. davidmreed's 2022 reply indicates +this needs flow-aliasing infrastructure first. + +The fix is either (a) to add a flow-aliasing mechanism and then +register ``ci_main`` as an alias for ``ci_master``, or (b) ship an +inclusive-named ``ci_main`` flow alongside ``ci_master`` (perhaps +deprecating ``ci_master`` over time). + +This test asserts ``ci_main`` is a recognized flow name in +cumulusci.yml; on dev it fails because only ``ci_master`` exists. +""" + +from pathlib import Path + +import pytest +import yaml + +import cumulusci + + +@pytest.mark.xfail( + reason="repro for #3470 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3470(): + cci_root = Path(cumulusci.__file__).parent + with open(cci_root / "cumulusci.yml") as f: + data = yaml.safe_load(f) + + flow_names = set(data.get("flows", {}).keys()) + assert "ci_main" in flow_names, ( + "cumulusci.yml still defines only ci_master; no ci_main alias / flow " + f"exists. Flows present: {sorted(flow_names)} (see #3470)" + ) diff --git a/cumulusci/tests/triage/test_issue_3471.py b/cumulusci/tests/triage/test_issue_3471.py new file mode 100644 index 0000000000..1afd4f0240 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3471.py @@ -0,0 +1,44 @@ +"""Regression repro for #3471. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery; reverified on +origin/dev@1925a3083 - only ruff refactor since v4.10.0). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci/tasks/github/merge.py `_merge` (L241-262) logs +"Merged {compare.behind_by} commits into branch: {branch_name}". +`compare.behind_by` comes from github3's CompareCommits which surfaces +GitHub's compare-com API; for effectively no-op content merges +(e.g. README/test.txt where downstream content already matches via +merge-base) the API returns 0, even though `self.repo.merge(...)` at +L249 just shipped a real merge commit. The "Merged 0 commits" +message is therefore confusing to users monitoring the auto-merge +output. + +A real fix is to report either the SHA returned from +`self.repo.merge(...)` or `len(list(compare.commits))` instead of +`compare.behind_by`. + +This test asserts the misleading `compare.behind_by` reference is +absent from merge.py. On dev it is still present at L251, so the +assertion fails -> XFAIL. +""" + +from pathlib import Path + +import pytest + +import cumulusci + + +@pytest.mark.xfail( + reason="repro for #3471 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3471(): + merge_path = Path(cumulusci.__file__).parent / "tasks" / "github" / "merge.py" + text = merge_path.read_text() + assert "compare.behind_by" not in text, ( + "Expected merge.py to no longer report `compare.behind_by` in the " + "'Merged N commits into branch' log line (use compare.commits or the " + "merge SHA instead); the misleading reference is still present." + ) diff --git a/cumulusci/tests/triage/test_issue_3485.py b/cumulusci/tests/triage/test_issue_3485.py new file mode 100644 index 0000000000..e6b6eddcb2 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3485.py @@ -0,0 +1,60 @@ +"""Regression repro for #3485. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci/tasks/apex/testrunner.py:802-833 writes a JUnit +`test_results.xml` that: +- has no `` declaration +- writes a bare `` element, not the standard `` + wrapper that JUnit consumers (Jenkins, GitHub Actions, etc.) expect + +Per the user's issue body, downstream JUnit parsers reject this +malformed XML. The minimal fix is to emit the XML declaration and the +top-level `` wrapper. + +This test invokes `_write_output` directly with mocked results and +asserts the generated content starts with an XML declaration and +contains a `` element. On dev neither is true, so the +assertion fails -> XFAIL. +""" + +import pathlib +import tempfile +from unittest import mock + +import pytest + +from cumulusci.tasks.apex.testrunner import RunApexTests + + +@pytest.mark.xfail( + reason="repro for #3485 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3485(): + with tempfile.TemporaryDirectory() as tmpdir: + junit_path = pathlib.Path(tmpdir) / "test_results.xml" + task = RunApexTests.__new__(RunApexTests) + task.options = {"junit_output": str(junit_path), "json_output": ""} + task.logger = mock.MagicMock() + test_results = [ + { + "ClassName": "FooTest", + "Method": "method_a", + "Outcome": "Pass", + "Stats": {"duration": "0.1"}, + "Message": None, + "StackTrace": None, + } + ] + task._write_output(test_results) + content = junit_path.read_text(encoding="utf-8") + + assert content.lstrip().startswith(" declaration. " + f"First 80 chars: {content[:80]!r}" + ) + assert " wrapper. Full content:\n{content}" + ) diff --git a/cumulusci/tests/triage/test_issue_3492.py b/cumulusci/tests/triage/test_issue_3492.py new file mode 100644 index 0000000000..85052386c0 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3492.py @@ -0,0 +1,46 @@ +"""Regression repro for #3492. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: `cci flow run -o key value` in +cumulusci/cli/flow.py parses each `-o` pair by doing +`task_name, option_name = key.split("__")` - an exact 2-way +unpack. Passing the user-desired form +`-o project__custom__myattr value` triggers +``ValueError: too many values to unpack (expected 2)`` because +`split("__")` returns three elements. There is no separate +project-level option override path either. + +This test imports the inner parse loop body and asserts that it +tolerates 3+ underscore-separated path segments (i.e. project- +scoped attributes). On dev the unpack still hard-fails -> XFAIL. +""" + +import inspect + +import pytest + +from cumulusci.cli import flow as flow_cli + + +@pytest.mark.xfail( + reason="repro for #3492 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_issue_3492(): + src = inspect.getsource(flow_cli.flow_run) + has_strict_two_part_unpack = ( + 'task_name, option_name = key.split("__")' in src + or "task_name, option_name = key.split('__')" in src + ) + has_project_scoped_path = ( + "project__custom" in src or "maxsplit" in src or "project_config.config" in src + ) + assert not has_strict_two_part_unpack or has_project_scoped_path, ( + "flow_run still naïvely unpacks key.split('__') into exactly two " + "parts; no project-scoped override path. " + "`-o project__custom__attr value` will crash with " + "'too many values to unpack' (see #3492)." + ) diff --git a/cumulusci/tests/triage/test_issue_3506.py b/cumulusci/tests/triage/test_issue_3506.py new file mode 100644 index 0000000000..60adfd017a --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3506.py @@ -0,0 +1,46 @@ +"""Regression repro for #3506. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: ``FlowCoordinator._visit_step`` +(cumulusci/core/flowrunner.py) only wires ``when=step_config.get("when")`` +on the ``task:`` branch (around line 669). The ``flow:`` branch +(around lines 674-697) recurses into nested steps without ever reading +``step_config.get("when")`` - so a ``when:`` clause attached to a +``flow:`` step is silently dropped. + +The fix is to propagate the parent flow-step's ``when:`` down to the +nested StepSpecs (most simply by AND-ing it into each child's ``when``, +or by gating the recursive ``_visit_step`` call on it). This test +asserts the source of ``_visit_step`` references ``step_config.get(\"when\")`` +inside the ``"flow" in step_config`` branch; on dev it fails because +that branch does not read ``when``. +""" + +import inspect + +import pytest + +from cumulusci.core.flowrunner import FlowCoordinator + + +@pytest.mark.xfail( + reason="repro for #3506 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3506(): + src = inspect.getsource(FlowCoordinator._visit_step) + flow_idx = src.find('if "flow" in step_config') + assert flow_idx != -1, ( + "Source of _visit_step no longer has the recognizable " + '`if "flow" in step_config:` branch; test needs updating.' + ) + flow_branch = src[flow_idx:] + has_when = ('step_config.get("when"' in flow_branch) or ( + "step_config.get('when'" in flow_branch + ) + assert has_when, ( + "flow:-step branch in FlowCoordinator._visit_step still ignores " + "step_config.get('when'); when: clauses on flow steps are silently dropped" + ) diff --git a/cumulusci/tests/triage/test_issue_3518.py b/cumulusci/tests/triage/test_issue_3518.py new file mode 100644 index 0000000000..a9d158980d --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3518.py @@ -0,0 +1,43 @@ +"""Regression repro for #3518. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci/tasks/metadata_etl/picklists.py line 177 reads: + + default = str(process_bool_arg(entry.get("default", False))).lower + +The `.lower` is referenced as an attribute, not invoked - so +`default` ends up bound to the `str.lower` method itself (a callable +object, always truthy). The subsequent guard at line 214 +(`if default:`) therefore always runs the default-clobbering loop, +marking the new entry as default for every record type regardless of +the user's intent. + +A real fix is a one-character change: `.lower` -> `.lower()`. + +This test asserts that when a user passes `default: False` for a new +picklist entry, the produced `default` value is a falsy string (not +a bound method). On dev `default` is `str.lower` (a method), which +is truthy, so the assertion fails -> XFAIL. +""" + +import pytest + +from cumulusci.core.utils import process_bool_arg + + +@pytest.mark.xfail( + reason="repro for #3518 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3518(): + # Mirrors the exact expression at picklists.py:177 + default = str(process_bool_arg(False)).lower + assert not callable(default), ( + "Expected picklists.py to compute the lowercase string (e.g. via " + "`.lower()`), not the bound `str.lower` method. The current " + "expression yields a callable, so the `if default:` guard at " + "L214 is always truthy and every record-type default is " + f"clobbered. Got {default!r}." + ) diff --git a/cumulusci/tests/triage/test_issue_3541.py b/cumulusci/tests/triage/test_issue_3541.py new file mode 100644 index 0000000000..1024504200 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3541.py @@ -0,0 +1,82 @@ +"""Repro for CumulusCI issue #3541 - scratch org alias becomes ``None__``. + +When :pyfunc:`BaseProjectKeychain.create_scratch_org` builds the SFDX alias it +does:: + + scratch_config["sfdx_alias"] = ( + f"{self.project_config.project__name}__{org_name}" + ) + +If ``project__name`` evaluates to ``None`` (no ``project.name`` in the loaded +config, or the project hasn't fully resolved yet - e.g. during the eager +``_load_scratch_orgs`` pass on keychain construction) the resulting alias is +the literal string ``"None__"``. That string is then passed to +``sfdx force config set target-org=None__dev`` which is exactly what +the reporter (and at least one other commenter) saw. + +Correct behaviour: either fall back to ``org_name`` alone or raise a clear +CumulusCIException - *never* embed the literal Python repr of ``None`` in a +shell argument. + +No scratch org / network required. +""" + +from __future__ import annotations + +import pytest + +from cumulusci.core.config import BaseProjectConfig, UniversalConfig +from cumulusci.core.keychain import BaseProjectKeychain + + +@pytest.mark.xfail( + reason=("repro for #3541 - see docs/triage/v5/repro-results.md"), + strict=False, +) +def test_create_scratch_org_without_project_name_does_not_yield_None_alias(): + universal_config = UniversalConfig() + project_config = BaseProjectConfig( + universal_config, + {"orgs": {"scratch": {"dev": {}}}}, + ) + assert project_config.project__name is None, ( + "precondition: this repro models a project config where project.name " + "is unset, matching the reporter's symptom" + ) + + keychain = BaseProjectKeychain(project_config, key=None) + keychain.create_scratch_org("dev", "dev") + alias = keychain.get_org("dev").config["sfdx_alias"] + + assert "None" not in alias.split("__"), ( + f"sfdx_alias is {alias!r} - literal 'None' should never appear in a " + "shell-bound SFDX alias. Expected fallback to 'dev' or a raised " + "CumulusCIException." + ) + + +@pytest.mark.xfail( + reason=("repro for #3541 - see docs/triage/v5/repro-results.md"), + strict=False, +) +def test_load_scratch_orgs_on_keychain_init_does_not_yield_None_alias(): + """During keychain construction ``_load_scratch_orgs`` eagerly invokes + ``create_scratch_org`` for every scratch config in cumulusci.yml. If + project.name resolves to None at that moment, every eagerly-created org + inherits a ``None__`` alias - and the user only notices when + ``cci org info dev`` finally runs sfdx with target-org=None__dev.""" + universal_config = UniversalConfig() + project_config = BaseProjectConfig( + universal_config, + {"orgs": {"scratch": {"dev": {}, "qa": {}}}}, + ) + keychain = BaseProjectKeychain(project_config, key=None) + + bad = { + name: keychain.get_org(name).config["sfdx_alias"] + for name in ("dev", "qa") + if "None" in keychain.get_org(name).config["sfdx_alias"].split("__") + } + assert not bad, ( + f"Eagerly-loaded scratch orgs produced None__-prefixed aliases: {bad!r}" + ) diff --git a/cumulusci/tests/triage/test_issue_3543.py b/cumulusci/tests/triage/test_issue_3543.py new file mode 100644 index 0000000000..0c46cb6e47 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3543.py @@ -0,0 +1,35 @@ +"""Regression repro for #3543. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci/tasks/dx_convert_from.py only exposes the +`extra` and `src_dir` task options. The issue asks for a +`load_sfdx_project_paths` (a.k.a. `resolve_sfdx_package_dirs`) option +that auto-discovers source directories from `sfdx-project.json` so +multi-package projects can convert all sources in one invocation. + +This test imports `DxConvertFrom` and asserts that +`load_sfdx_project_paths` (or `resolve_sfdx_package_dirs`) is a +declared task option. On dev neither key is present, so the +assertion fails -> XFAIL. +""" + +import pytest + +from cumulusci.tasks.dx_convert_from import DxConvertFrom + + +@pytest.mark.xfail( + reason="repro for #3543 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3543(): + options = DxConvertFrom.task_options + has_option = ( + "load_sfdx_project_paths" in options or "resolve_sfdx_package_dirs" in options + ) + assert has_option, ( + "DxConvertFrom is missing a load_sfdx_project_paths / " + f"resolve_sfdx_package_dirs option. Current options: {list(options.keys())}" + ) diff --git a/cumulusci/tests/triage/test_issue_3549.py b/cumulusci/tests/triage/test_issue_3549.py new file mode 100644 index 0000000000..c966967e23 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3549.py @@ -0,0 +1,37 @@ +"""Regression repro for #3549. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: `Deploy` in cumulusci/tasks/salesforce/Deploy.py +exposes `test_level` / `specified_tests` and pipes them through +to the Metadata API call but never captures or writes +runTestResult / runTestsResult into a JUnit (or JSON) file. The +2022 ask is to surface CI-consumable test artefacts directly +from `cci task run deploy`. + +This test asserts that `Deploy.task_options` declares a +JUnit/test-output option key. On dev no such option exists -> +XFAIL. +""" + +import pytest + +from cumulusci.tasks.salesforce.Deploy import Deploy + + +@pytest.mark.xfail( + reason="repro for #3549 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_issue_3549(): + options = Deploy.task_options + has_test_output_option = any( + any(token in key for token in ("junit", "test_output", "test_result")) + for key in options + ) + assert has_test_output_option, ( + "Deploy task still has no junit/test_output/test_result option; " + f"only options: {sorted(options)} (see #3549)." + ) diff --git a/cumulusci/tests/triage/test_issue_3570.py b/cumulusci/tests/triage/test_issue_3570.py new file mode 100644 index 0000000000..e85684f0e0 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3570.py @@ -0,0 +1,53 @@ +"""Regression repro for #3570. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery; reverified on +origin/dev@1925a3083 - only ruff refactor since v4.10.0). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci/core/flowrunner.py exposes only per-step +`ignore_failure` (mapped to `StepSpec.allow_failure`); there is no +flow-step type for `finally:` / `on_error:` / `on_failure:` / +`always_run` / `cleanup` to express "this cleanup/rollback step +should always run after the flow regardless of success or failure". +The only `finally:` in flowrunner.py is the Python `try/finally` in +FlowCoordinator.run that invokes the `post_flow` callback - +internal-only, not user-configurable. + +A real fix introduces a step-level metadata flag (e.g. +`always_run: true` or a sibling `on_error:` block) and threads it +through the FlowCoordinator step-execution loop. + +This test asserts that `StepSpec` exposes an "always-run" / +"on-error" attribute. On dev no such attribute exists, so the +assertion fails -> XFAIL. +""" + +import pytest + +from cumulusci.core.flowrunner import StepSpec + + +@pytest.mark.xfail( + reason="repro for #3570 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3570(): + candidate_attrs = ( + "always_run", + "on_error", + "on_failure", + "finally_step", + "cleanup", + ) + field_names = ( + set(StepSpec.__dataclass_fields__.keys()) + if hasattr(StepSpec, "__dataclass_fields__") + else set(dir(StepSpec)) + ) + found = [a for a in candidate_attrs if a in field_names] + assert found, ( + "Expected flowrunner.StepSpec to expose an always-run / on-error " + f"affordance (one of {candidate_attrs!r}) so users can declare " + "cleanup / rollback steps in a flow definition; none present. " + f"Existing fields: {sorted(field_names)!r}" + ) diff --git a/cumulusci/tests/triage/test_issue_3585.py b/cumulusci/tests/triage/test_issue_3585.py new file mode 100644 index 0000000000..bd36cb3e94 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3585.py @@ -0,0 +1,73 @@ +"""Regression repro for #3585. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (no_reverify_needed). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: When ``update_package_xml`` runs against an ``objects/`` +folder where a ``.object`` file contains an element using the +``xsi:nil="true"`` shorthand without declaring ``xmlns:xsi``, +``PackageXmlGenerator`` invokes ``MetadataXmlElementParser`` parsers +(metadata_map.yml maps ``objects:`` to parsers for ListView, +CustomField, etc.) that call ``elementtree_parse_file`` +(cumulusci/utils/xml/__init__.py:10), and Python's ``xml.etree`` +raises ``ParseError: unbound prefix`` because ``xsi:`` is undeclared. + +Salesforce's Metadata API often emits this shorthand, so users who +retrieve metadata and feed it back through ``update_package_xml`` hit +a hard failure they cannot fix by editing the file (it round-trips +this way). + +The fix is to either (a) add an ``xmlns:xsi`` shim before parsing, or +(b) pre-strip ``xsi:nil`` attributes / use a tolerant lxml parser. + +This test writes a tiny ``objects/Foo__c.object`` with an unbound +``xsi:nil`` attribute and asserts ``PackageXmlGenerator`` does not +raise; on dev it fails with an ``unbound prefix`` parse error. +""" + +import os +import tempfile + +import pytest + +from cumulusci.tasks.metadata.package import PackageXmlGenerator + + +XSI_NIL_OBJECT = """ + + + + All + + + +""" + + +@pytest.mark.xfail( + reason="repro for #3585 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3585(): + with tempfile.TemporaryDirectory() as tmp: + objects_dir = os.path.join(tmp, "objects") + os.makedirs(objects_dir) + with open(os.path.join(objects_dir, "Foo__c.object"), "w") as f: + f.write(XSI_NIL_OBJECT) + + gen = PackageXmlGenerator(directory=tmp, api_version="58.0") + + raised = None + try: + gen() + except BaseException as e: + raised = e + + msg = str(raised) if raised else "" + is_unbound = raised is not None and ( + "unbound prefix" in msg or "not well-formed" in msg or "xsi" in msg + ) + assert not is_unbound, ( + f"PackageXmlGenerator still raises XML parse error on .object files " + f"using unbound xsi:nil; got: {raised!r}" + ) diff --git a/cumulusci/tests/triage/test_issue_3593.py b/cumulusci/tests/triage/test_issue_3593.py new file mode 100644 index 0000000000..ec9cc364a0 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3593.py @@ -0,0 +1,48 @@ +"""Regression repro for #3593. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: SFDXOrgTask._get_command in cumulusci/tasks/sfdx.py:47-52 +unconditionally appends ` -o {username}` whenever the org_config is a +ScratchOrgConfig. Some sf subcommands (e.g. `project convert source`) +do not accept a target-org flag, so the resulting command is rejected +by the sf CLI. There is no opt-out option. + +The fix is either (a) a `pass_org: False` / `no_org_command` task +option, or (b) a curated whitelist of no-org sf subcommands. Either +way the bug shape is: there is currently no way to disable the +unconditional `-o` append. + +This test inspects SFDXOrgTask `_get_command` source and asserts the +unconditional append has been replaced with a conditional opt-out +path (e.g., a `pass_org` option or whitelist check). On dev the +append is still unconditional, so the assertion fails -> XFAIL. +""" + +import inspect + +import pytest + +from cumulusci.tasks.sfdx import SFDXOrgTask + + +@pytest.mark.xfail( + reason="repro for #3593 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3593(): + src = inspect.getsource(SFDXOrgTask._get_command) + options = SFDXOrgTask.task_options + has_opt_out_option = ( + "pass_org" in options or "no_org_command" in options or "skip_org" in options + ) + has_opt_out_in_source = ( + "pass_org" in src or "no_org_command" in src or "skip_org" in src + ) + assert has_opt_out_option or has_opt_out_in_source, ( + "SFDXOrgTask still unconditionally appends ' -o {username}' for " + "ScratchOrgConfig with no opt-out option. Need pass_org/no_org_command " + f"toggle. Current _get_command:\n{src}\n" + f"Current task_options keys: {list(options.keys())}" + ) diff --git a/cumulusci/tests/triage/test_issue_3602.py b/cumulusci/tests/triage/test_issue_3602.py new file mode 100644 index 0000000000..2d4eaf7d3a --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3602.py @@ -0,0 +1,73 @@ +"""Regression marker for SFDO-Tooling/CumulusCI#3602. + +The ``Open Test Browser`` keyword (both the Selenium ``.robot`` keyword and +the Playwright Python implementation) accepts only ``size``, ``alias`` / +``useralias``, ``wait`` and (Playwright only) ``record_video``. There is no +way to pass through Chrome / Firefox / Playwright browser options such as +extensions, ``--incognito``, download directory, ``--accept-ssl-errors`` etc. + +The reporter asks for a hook to forward browser capabilities / options. This +test asserts the Playwright implementation surfaces a ``browser_options`` +(or ``extra_options``) keyword argument; the test fails today because the +signature has no such hook. +""" + +import inspect +import sys +import types +from unittest import mock + +import pytest + + +@pytest.fixture(autouse=True) +def _stub_browser_library(): + browser_mod = types.ModuleType("Browser") + browser_mod.SupportedBrowsers = mock.MagicMock(name="SupportedBrowsers") + utils_mod = types.ModuleType("Browser.utils") + data_types_mod = types.ModuleType("Browser.utils.data_types") + data_types_mod.KeyAction = mock.MagicMock(name="KeyAction") + data_types_mod.PageLoadStates = mock.MagicMock(name="PageLoadStates") + + added = [] + for name, mod in ( + ("Browser", browser_mod), + ("Browser.utils", utils_mod), + ("Browser.utils.data_types", data_types_mod), + ): + if name not in sys.modules: + sys.modules[name] = mod + added.append(name) + try: + yield + finally: + for name in added: + sys.modules.pop(name, None) + + +@pytest.mark.xfail( + reason=("repro for #3602 - see docs/triage/v5/repro-results.md"), + strict=False, +) +def test_open_test_browser_exposes_browser_options_hook(): + """The Playwright ``open_test_browser`` should expose a kwarg that lets + callers forward browser options/capabilities (extensions, incognito, + download dir, accept-ssl, etc.). + """ + from cumulusci.robotframework.SalesforcePlaywright import SalesforcePlaywright + + sig = inspect.signature(SalesforcePlaywright.open_test_browser) + params = sig.parameters + accepts_browser_options = ( + "browser_options" in params + or "extra_options" in params + or "browser_args" in params + or "options" in params + or any(p.kind == inspect.Parameter.VAR_KEYWORD for p in params.values()) + ) + assert accepts_browser_options, ( + "SalesforcePlaywright.open_test_browser should accept a " + "browser_options/extra_options keyword (or **kwargs) so callers can " + "forward Chrome/Firefox/Playwright capabilities (#3602). " + f"Actual signature: {sig}" + ) diff --git a/cumulusci/tests/triage/test_issue_3603.py b/cumulusci/tests/triage/test_issue_3603.py new file mode 100644 index 0000000000..77b737ac2b --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3603.py @@ -0,0 +1,69 @@ +"""Regression repro for #3603. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (partial; R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci/core/source/github.py L126: + self.commit = self.repo.ref(ref).object.sha +lets a raw github3 `NotFoundError("404 [No message]")` bubble out when +the user-specified ref / tag / branch on an _existing_ repo does not +resolve. The peer cases (repo-not-found at L1/2 and the `release:` +spec at L103) ARE wrapped in DependencyResolutionError with the URL +and ref context; only the ref-not-found case-3 path leaks. + +A real fix wraps `self.repo.ref(ref)` in try/except NotFoundError and +re-raises DependencyResolutionError mentioning the repo URL and the +missing ref/tag/branch. + +This test simulates a NotFoundError from `repo.ref()` and asserts the +resulting exception is a DependencyResolutionError (not a raw +NotFoundError) and includes the missing tag in its message. On dev +the raw NotFoundError leaks, so both assertions fail -> XFAIL. +""" + +from unittest import mock + +import pytest +from github3.exceptions import NotFoundError + +from cumulusci.core.exceptions import DependencyResolutionError +from cumulusci.core.source.github import GitHubSource +from cumulusci.utils.yaml.cumulusci_yml import GitHubSourceModel + + +class _DummyResponse: + status_code = 404 + content = "" + + +@pytest.mark.xfail( + reason="repro for #3603 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3603(): + project_config = mock.Mock() + spec = GitHubSourceModel( + github="https://github.com/Test/Repo", + tag="release/9.99.99-no-such-tag", + ) + + fake_repo = mock.Mock() + fake_repo.ref.side_effect = lambda *_a, **_kw: (_ for _ in ()).throw( + NotFoundError(_DummyResponse) + ) + + fake_gh = mock.Mock() + with ( + mock.patch( + "cumulusci.core.source.github.get_github_api_for_repo", + return_value=fake_gh, + ), + mock.patch.object(GitHubSource, "_get_repository", return_value=fake_repo), + ): + with pytest.raises(DependencyResolutionError) as exc: + GitHubSource(project_config, spec) + msg = str(exc.value) + assert "no-such-tag" in msg or "Test/Repo" in msg, ( + "Expected case-3 ref-not-found error to surface a " + f"DependencyResolutionError mentioning the repo or ref; got: {msg!r}" + ) diff --git a/cumulusci/tests/triage/test_issue_3604.py b/cumulusci/tests/triage/test_issue_3604.py new file mode 100644 index 0000000000..97f674a947 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3604.py @@ -0,0 +1,51 @@ +"""Regression repro for #3604. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: there is no CCI task that writes computed dependencies +into a project's `sfdx-project.json`. Per the narrative: + +> `uv run cci task list` returns 0 tasks that write `sfdx-project.json`. +> A project-wide grep for `unpackagedMetadata` returns no matches. + +The maintainer filed an internal tracking ticket in 2023 but no implementation has +shipped through v4.10.0. + +The fix is to add a new task (e.g. `update_sfdx_project_dependencies`) +that resolves the current cumulusci dependencies and writes them +into `sfdx-project.json` - likely populating the `unpackagedMetadata` +key (or `dependencies`). + +This test scans cumulusci/tasks/ source for references to the +`unpackagedMetadata` key, which is the canonical sfdx-project.json +field for dependency tracking. On dev there are zero references, so +the assertion fails -> XFAIL. +""" + +import pathlib + +import pytest + +import cumulusci as _cci_pkg + + +@pytest.mark.xfail( + reason="repro for #3604 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3604(): + pkg_root = pathlib.Path(_cci_pkg.__file__).parent + tasks_dir = pkg_root / "tasks" + hits = [] + for py in tasks_dir.rglob("*.py"): + text = py.read_text(encoding="utf-8") + if "unpackagedMetadata" in text: + hits.append(str(py.relative_to(pkg_root))) + + assert hits, ( + "No task under cumulusci/tasks/ references the sfdx-project.json " + "'unpackagedMetadata' key; no task writes computed cumulusci " + "dependencies back into sfdx-project.json. " + f"Scanned {tasks_dir}; hits: {hits}" + ) diff --git a/cumulusci/tests/triage/test_issue_3613.py b/cumulusci/tests/triage/test_issue_3613.py new file mode 100644 index 0000000000..7df76d566c --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3613.py @@ -0,0 +1,65 @@ +"""Regression repro for #3613. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: `MetadataSingleEntityTransformTask._transform` in +cumulusci/tasks/metadata_etl/base.py:330-332 raises +``CumulusCIException(f"Cannot find metadata file {path}")`` +whenever the user-supplied `api_name` does not exactly match +the Metadata API on-disk filename. For Page Layouts the +filename convention is `-.layout`; a user +passing just `Account` (the api_name they see in Setup → Object +Manager) crashes with that bare error and no hint about the +expected format or what was actually retrieved. + +The proposed UX fix is to drive `_transform` against a fake +retrieve directory and assert the raised exception message +includes either the retrieved filename list or a hint at the +expected `-` format. +""" + +import pytest + +from cumulusci.core.exceptions import CumulusCIException +from cumulusci.tasks.metadata_etl.layouts import AddFieldsToPageLayout + + +@pytest.mark.xfail( + reason="repro for #3613 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_issue_3613(tmp_path): + retrieve_dir = tmp_path / "retrieve" + layouts_dir = retrieve_dir / "layouts" + layouts_dir.mkdir(parents=True) + # Simulate a successful retrieve with the *correct* MDAPI filename. + (layouts_dir / "Account-Account Layout.layout").write_text( + "" + ) + + task = AddFieldsToPageLayout.__new__(AddFieldsToPageLayout) + task.retrieve_dir = retrieve_dir + task.deploy_dir = tmp_path / "deploy" + task.deploy_dir.mkdir() + # User typed just the object api_name, like in the bug report. + task.api_names = {"Account"} + task.api_version = "59.0" + + with pytest.raises(CumulusCIException) as excinfo: + task._transform() + + msg = str(excinfo.value) + helpful = ( + "Account-Account Layout" in msg + or "" in msg + or "available" in msg.lower() + or "expected format" in msg.lower() + ) + assert helpful, ( + "MetadataSingleEntityTransformTask still raises a bare " + f"'Cannot find metadata file ...' message: {msg!r}. " + "It should reference the retrieved files or the expected " + "api_name format (see #3613)." + ) diff --git a/cumulusci/tests/triage/test_issue_3618.py b/cumulusci/tests/triage/test_issue_3618.py new file mode 100644 index 0000000000..9d1cb121d2 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3618.py @@ -0,0 +1,49 @@ +"""Regression repro for #3618. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: `cci org remove` and `cci org scratch_delete` both +take a single `org_name` argument via +`orgname_option_or_argument(required=True)` (Click +`@click.argument` with no `nargs=-1`, no comma-split helper). +The user ask is to accept a list of org names in one invocation +so a CI step can clean up several scratch orgs at once. + +This test introspects the Click commands and asserts that the +`orgname` argument accepts >1 value (either `nargs=-1` or a +list-coerced custom callback). On dev neither command opts in +-> XFAIL. +""" + +import pytest + +from cumulusci.cli.org import org_remove, org_scratch_delete + + +def _orgname_param(cmd): + for param in cmd.params: + if param.name == "orgname": + return param + return None + + +@pytest.mark.xfail( + reason="repro for #3618 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_issue_3618(): + for cmd in (org_remove, org_scratch_delete): + param = _orgname_param(cmd) + assert param is not None, ( + f"{cmd.name}: expected an 'orgname' click.Argument, none found" + ) + accepts_many = getattr(param, "nargs", 1) in (-1,) or getattr( + param, "multiple", False + ) + assert accepts_many, ( + f"`cci org {cmd.name}` orgname argument still accepts only a " + f"single value (nargs={getattr(param, 'nargs', 1)}). #3618 " + "asks for list/batch support." + ) diff --git a/cumulusci/tests/triage/test_issue_3619.py b/cumulusci/tests/triage/test_issue_3619.py new file mode 100644 index 0000000000..25e1903542 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3619.py @@ -0,0 +1,60 @@ +"""Regression repro for #3619. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (no_reverify_needed). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: ``GitHubDependencyPin`` +(cumulusci/core/dependencies/dependencies.py:79-101) only declares +``github: str`` and ``tag: str``. Adding ``password_env_name:`` to a +``dependency_pins:`` entry triggers +``DependencyParseError: Unable to parse dependency pin: {...}`` from +``parse_dependency_pin()``/``parse_pins()`` because Pydantic rejects +the extra field. + +(Part B of #3619 - the silent password drop in ``pin.pin()`` - is +captured at the same location: even if Part A is fixed by adding the +field, ``pin.pin()`` calls ``GitHubTagResolver().resolve(...)`` +directly and bypasses the password-propagation block in +``resolvers.py`` ~L644-654. Both parts need a fix.) + +The minimal fix for Part A is to add +``password_env_name: Optional[str] = None`` to +``GitHubDependencyPin``; the test here asserts ``parse_pins`` no +longer raises ``DependencyParseError`` on an entry carrying +``password_env_name``. +""" + +import pytest + +from cumulusci.core.dependencies.dependencies import parse_pins +from cumulusci.core.exceptions import DependencyParseError + + +@pytest.mark.xfail( + reason="repro for #3619 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3619(): + pin_dict = { + "github": "https://github.com/example/repo", + "tag": "release/1.0", + "password_env_name": "MY_INSTALL_KEY", + } + + raised = None + parsed = None + try: + parsed = parse_pins([pin_dict]) + except BaseException as e: + raised = e + + assert not isinstance(raised, DependencyParseError), ( + "parse_pins still raises DependencyParseError for an entry that includes " + "password_env_name; GitHubDependencyPin only declares github+tag, so the " + f"password_env_name field is rejected. Got: {raised!r}" + ) + assert parsed is not None and len(parsed) == 1 + assert getattr(parsed[0], "password_env_name", None) == "MY_INSTALL_KEY", ( + "Even if the pin parses, password_env_name is not carried onto the pin " + "object (#3619 Part B). Fix must both accept and propagate it." + ) diff --git a/cumulusci/tests/triage/test_issue_3649.py b/cumulusci/tests/triage/test_issue_3649.py new file mode 100644 index 0000000000..f1c03277e2 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3649.py @@ -0,0 +1,38 @@ +"""Regression repro for #3649. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci/tasks/bulkdata/update_data.py L184 and L211 +both call `get_query_operation` / `get_dml_operation` with +`api_options={}` hardcoded empty. `BulkApiDmlOperation` in +`step.py` honors `api_options["bulk_mode"]` for Serial/Parallel +selection, but `UpdateData` never exposes a `bulk_mode` (or +`api_options`) task option, so the Snowfakery-driven update path +cannot run in serial mode. `LoadData` and the snowfakery channel +runner DO let users pick `bulk_mode`; `update_data` is the gap. + +A real fix is small (~10 lines): add `bulk_mode` (or `api_options`) +to `UpdateData.task_options` and pipe it into both call sites. + +This test asserts that `UpdateData.task_options` exposes a +`bulk_mode` (or `api_options`) option. On dev neither exists, so the +assertion fails -> XFAIL. +""" + +import pytest + +from cumulusci.tasks.bulkdata.update_data import UpdateData + + +@pytest.mark.xfail( + reason="repro for #3649 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3649(): + options = set(UpdateData.task_options.keys()) + assert "bulk_mode" in options or "api_options" in options, ( + "Expected UpdateData.task_options to expose `bulk_mode` (or " + "`api_options`) so Snowfakery-driven updates can be run in Serial " + f"mode; current options: {sorted(options)!r}." + ) diff --git a/cumulusci/tests/triage/test_issue_3663.py b/cumulusci/tests/triage/test_issue_3663.py new file mode 100644 index 0000000000..c46c85e255 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3663.py @@ -0,0 +1,54 @@ +"""Regression repro for #3663. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: ``FlowCoordinator._run_step`` (cumulusci/core/flowrunner.py +around lines 510-516) builds the Jinja2 context for ``when:`` from +only ``project_config`` and ``org_config``. ``self.results`` (prior-task +return values) is never exposed, so a user cannot write +``when: tasks.previous_task.return_values.foo`` in a flow ``when:`` +clause - there is no codepath for that lookup at all. + +The fix is to extend the Jinja2 context (e.g. include a ``tasks`` or +``steps`` mapping built from ``self.results`` keyed by task name) so +``when:`` expressions can reference prior step results, matching the +``^^task.return_value`` resolver that the option-resolution path already +supports. + +This test asserts the source of ``_run_step`` references prior-step +results (``self.results``, ``tasks``, or ``steps``) inside the jinja2 +context build-up. On dev it fails because the context only contains +project_config + org_config. +""" + +import inspect + +import pytest + +from cumulusci.core.flowrunner import FlowCoordinator + + +@pytest.mark.xfail( + reason="repro for #3663 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3663(): + src = inspect.getsource(FlowCoordinator._run_step) + ctx_idx = src.find("jinja2_context") + assert ctx_idx != -1, ( + "Source of _run_step no longer references jinja2_context; test needs updating." + ) + end_idx = src.find("compile_expression", ctx_idx) + if end_idx == -1: + end_idx = len(src) + ctx_block = src[ctx_idx:end_idx] + has_prior_results = any( + token in ctx_block + for token in ("self.results", '"tasks"', "'tasks'", '"steps"', "'steps'") + ) + assert has_prior_results, ( + "FlowCoordinator._run_step still builds the when: Jinja2 context " + "from only project_config + org_config; prior task results " + "(self.results) are not exposed to when: expressions" + ) diff --git a/cumulusci/tests/triage/test_issue_3692.py b/cumulusci/tests/triage/test_issue_3692.py new file mode 100644 index 0000000000..945df87237 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3692.py @@ -0,0 +1,41 @@ +"""Regression repro for #3692. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci/tasks/metadata/metadata_map.yml does not include +a `digitalExperiences` (or `digitalExperienceConfigs`) entry. The +`PackageXmlGenerator` therefore raises +`MetadataParserMissingError("No parser configuration found for +subdirectory %s")` on any Enhanced LWR site, which corresponds +directly to the user's reported error. + +The fix is to add `digitalExperiences` (and likely +`digitalExperienceConfigs`) entries to `metadata_map.yml` with a +suitable bundle parser class. + +This test loads metadata_map.yml and asserts that the +`digitalExperiences` key is present. On dev it is absent, so the +assertion fails -> XFAIL. +""" + +import pathlib + +import pytest +import yaml + +import cumulusci.tasks.metadata as _md_pkg + + +@pytest.mark.xfail( + reason="repro for #3692 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3692(): + map_path = pathlib.Path(_md_pkg.__file__).parent / "metadata_map.yml" + cfg = yaml.safe_load(map_path.read_text(encoding="utf-8")) + assert "digitalExperiences" in cfg, ( + "metadata_map.yml is missing a digitalExperiences entry; " + "Enhanced LWR sites fail with MetadataParserMissingError. " + f"Present keys (first 30): {sorted(cfg.keys())[:30]}" + ) diff --git a/cumulusci/tests/triage/test_issue_3699.py b/cumulusci/tests/triage/test_issue_3699.py new file mode 100644 index 0000000000..6a95b3ba8f --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3699.py @@ -0,0 +1,36 @@ +"""Regression repro for #3699. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: `ExtractData._soql_for_mapping` in +cumulusci/tasks/bulkdata/extract.py:132-146 builds the SOQL with +`WHERE` only - there is no `ORDER BY` clause. The +`MappingStep` model in +cumulusci/tasks/bulkdata/mapping_parser.py has no `order_by` / +`sort` field. The user-facing workaround (`soql_filter: "... +ORDER BY ..."`) does work via `append_filter_clause`, but the +2023 ask is for a first-class `order_by` knob to give +deterministic, diff-friendly extracts. + +This test asserts that `MappingStep` declares an `order_by` +(or `sort`) field. On dev it doesn't -> XFAIL. +""" + +import pytest + +from cumulusci.tasks.bulkdata.mapping_parser import MappingStep + + +@pytest.mark.xfail( + reason="repro for #3699 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_issue_3699(): + field_names = set(MappingStep.model_fields.keys()) + has_order_by = any(name in field_names for name in ("order_by", "sort", "sort_by")) + assert has_order_by, ( + "MappingStep still has no first-class order_by/sort field; " + f"declared fields: {sorted(field_names)} (see #3699)." + ) diff --git a/cumulusci/tests/triage/test_issue_3700.py b/cumulusci/tests/triage/test_issue_3700.py new file mode 100644 index 0000000000..a6109474a5 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3700.py @@ -0,0 +1,63 @@ +"""Regression repro for #3700. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (no_reverify_needed). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: ``MappingStep._get_required_permission_types`` +(cumulusci/tasks/bulkdata/mapping_parser.py:361-381) unconditionally +returns ``("updateable", "createable")`` for any operation in +``(UPSERT, ETL_UPSERT)``. Master-detail lookup fields in Salesforce are +``createable: True`` but ``updateable: False`` (you cannot reparent a +master-detail child after creation), so +``_check_field_permission`` returns ``False`` for the MD lookup on an +upsert mapping. ``_validate_field_dict`` then errors with +``Field xxx__c does not have the correct permissions ('updateable', +'createable') for this operation`` - exactly the symptom #3700 reports. + +The fix is a field-shape-aware permission check: for upsert lookup +fields that look like master-detail (``cascadeDelete: True``, +``updateable: False``, ``createable: True``), accept ``createable`` +alone - the MD lookup never gets updated post-insert anyway. + +This test simulates ``_check_field_permission`` against an MD-shaped +describe and asserts the call returns ``True`` for an UPSERT operation. +On dev it fails because the permission check still demands +``updateable``. +""" + +import pytest + +from cumulusci.tasks.bulkdata.mapping_parser import MappingStep +from cumulusci.tasks.bulkdata.step import DataOperationType + + +@pytest.mark.xfail( + reason="repro for #3700 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3700(): + step = MappingStep( + sf_object="Order__c", + table="Order__c", + action="upsert", + update_key="ExternalId__c", + fields={"Name": "Name", "Account__c": "Account__c"}, + ) + + md_describe = { + "Account__c": { + "createable": True, + "updateable": False, + "name": "Account__c", + } + } + + allowed = step._check_field_permission( + md_describe, "Account__c", DataOperationType.UPSERT + ) + assert allowed, ( + "MappingStep._check_field_permission still rejects an UPSERT against a " + "master-detail-shaped (createable=True, updateable=False) lookup field; " + "upsert should treat the MD lookup as create-only and accept " + "createable alone (see #3700)" + ) diff --git a/cumulusci/tests/triage/test_issue_3701.py b/cumulusci/tests/triage/test_issue_3701.py new file mode 100644 index 0000000000..0229abf574 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3701.py @@ -0,0 +1,41 @@ +"""Regression repro for #3701. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci/tasks/bulkdata/mapping_parser.py special-cases +the literal field name "Id" throughout (L171, L190, L228, L241, +L422); the field "Id" is always interpreted as the Salesforce 18-char +Id and is bound to the SQLite `sf_id` column. The MappingStep model +has no `primary_key` / `id_field` option to designate a different +field (e.g. an external-id like `BCM_Unique_Id__c`) as the row's +primary key. The user's example yaml `Id : BCM_Unique_Id__c` is +currently interpreted as "extract the SF Id into a column named +BCM_Unique_Id__c", not "use BCM_Unique_Id__c as the primary key". + +A real fix introduces a per-step opt-in to override the primary-key +identity, touching extract / load / lookup-resolution. + +This test asserts that `MappingStep` exposes some PK-override +affordance. On dev no such field exists, so the assertion fails -> +XFAIL. +""" + +import pytest + +from cumulusci.tasks.bulkdata.mapping_parser import MappingStep + + +@pytest.mark.xfail( + reason="repro for #3701 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3701(): + candidate_attrs = ("primary_key", "id_field", "external_id", "pk_field") + fields = set(MappingStep.model_fields.keys()) + found = [a for a in candidate_attrs if a in fields] + assert found, ( + "Expected MappingStep to expose a PK-override field (one of " + f"{candidate_attrs!r}) so users can designate an external id as the " + f"row's primary key; existing fields: {sorted(fields)!r}." + ) diff --git a/cumulusci/tests/triage/test_issue_3721.py b/cumulusci/tests/triage/test_issue_3721.py new file mode 100644 index 0000000000..fb5e8071eb --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3721.py @@ -0,0 +1,39 @@ +"""Regression repro for #3721. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: +- cumulusci/tasks/create_package_version.py:184 defaults + `version_name` to the literal string `"Release"`. +- cumulusci/cumulusci.yml `upload_production` task hard-codes + `name: Release`. + +The issue asks for the default to be the predicted version number +(or a jinja2 template that resolves to it), so consumers do not all +end up with a homogeneous `"Release"` name. The fix has shipped on +the muselab-d2x fork (commit 7aaf348f3) but is not in upstream cci. + +This test inspects the source of `create_package_version.py` and +asserts that the literal `or "Release"` fallback has been removed. +On dev it is still present, so the assertion fails -> XFAIL. +""" + +import pathlib + +import pytest + +import cumulusci.tasks.create_package_version as _cpv_mod + + +@pytest.mark.xfail( + reason="repro for #3721 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3721(): + src = pathlib.Path(_cpv_mod.__file__).read_text(encoding="utf-8") + assert 'or "Release"' not in src, ( + "create_package_version.py still defaults version_name to literal " + '"Release"; expected version-number-based default (jinja2 template ' + "or computed string)." + ) diff --git a/cumulusci/tests/triage/test_issue_3734.py b/cumulusci/tests/triage/test_issue_3734.py new file mode 100644 index 0000000000..dcd304a220 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3734.py @@ -0,0 +1,57 @@ +"""Regression repro for #3734. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: `PackageUpload._validate_versions` in +cumulusci/tasks/salesforce/package_upload.py runs a SOQL +``ORDER BY MajorVersion DESC, MinorVersion DESC, +PatchVersion DESC, ReleaseState DESC LIMIT 1`` then, when the +returned `ReleaseState` is `Beta`, sets `minor_version` to that +row's `MinorVersion`. When a customer has the typical pattern +(Released 6.13 followed by Beta patch 6.13.1), the query +returns the Beta patch and the next upload is built with +`minor_version=13` - identical to the already-Released minor. +The PackageUploadRequest is rejected with +``FIELD_INTEGRITY_EXCEPTION: The version number must be greater +than the last Managed - Released version number: 6.13``. + +This test drives `_validate_versions` against a mocked +`_get_one_record` returning the Beta-patch row and asserts that +the resulting `minor_version` is bumped past the latest Released +minor. On dev it stays at 13 -> XFAIL. +""" + +from unittest import mock + +import pytest + +from cumulusci.tasks.salesforce.package_upload import PackageUpload + + +@pytest.mark.xfail( + reason="repro for #3734 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_issue_3734(): + task = PackageUpload.__new__(PackageUpload) + # _validate_versions only consults self.options, self._get_one_record + task.options = {} + task._get_one_record = mock.Mock( + return_value={ + "MajorVersion": 6, + "MinorVersion": 13, + "PatchVersion": 1, + "ReleaseState": "Beta", + } + ) + + task._validate_versions() + + minor = int(task.options["minor_version"]) + assert minor > 13, ( + "PackageUpload._validate_versions still picks the Beta patch as " + "'latest' and reuses its MinorVersion; resulting minor_version=" + f"{minor!r} collides with the already-Released 6.13 (see #3734)." + ) diff --git a/cumulusci/tests/triage/test_issue_3746.py b/cumulusci/tests/triage/test_issue_3746.py new file mode 100644 index 0000000000..1de8c90e2c --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3746.py @@ -0,0 +1,58 @@ +"""Regression repro for #3746. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery; reverified on +origin/dev@1925a3083 - only ruff refactor since v4.10.0). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci/tasks/create_package_version.py +`_get_base_version_number` (L529-545) issues a Tooling API SOQL +against Package2Version with no `IsDeprecated = false` filter: + + SELECT MajorVersion, MinorVersion, PatchVersion, BuildNumber, + IsReleased + FROM Package2Version + WHERE Package2Id='{package_id}' + ORDER BY MajorVersion DESC, MinorVersion DESC, ... + LIMIT 1 + +If the highest version was deleted (sf package version delete), the +deprecated row is still returned, so the next version-bump bases off +of a soft-deleted version. The same file at L297 DOES include +`IsDeprecated = FALSE` for `Package2` lookups, so the project knows +about the column - the omission at L535 is asymmetric and matches +the report exactly. + +A real fix is a single-line SOQL change: add +`AND IsDeprecated = false` to the WHERE clause. + +This test reads create_package_version.py and asserts the +`Package2Version` SOQL filters on `IsDeprecated`. On dev it does +not, so the assertion fails -> XFAIL. +""" + +from pathlib import Path + +import pytest + +import cumulusci + + +@pytest.mark.xfail( + reason="repro for #3746 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3746(): + src_path = Path(cumulusci.__file__).parent / "tasks" / "create_package_version.py" + text = src_path.read_text() + # Locate the _get_base_version_number SOQL block. It is the only + # Package2Version SOQL that ORDERs BY MajorVersion DESC ... LIMIT 1. + needle = "ORDER BY MajorVersion DESC" + assert needle in text, "expected the _get_base_version_number SOQL block" + block_start = text.rindex("FROM Package2Version", 0, text.index(needle)) + block_end = text.index("LIMIT 1", block_start) + len("LIMIT 1") + block = text[block_start:block_end].lower() + assert "isdeprecated" in block, ( + "Expected `_get_base_version_number` Package2Version SOQL to filter " + "on IsDeprecated = false (matching the Package2 lookup at L297); the " + f"current WHERE clause has no IsDeprecated filter: {text[block_start:block_end]!r}" + ) diff --git a/cumulusci/tests/triage/test_issue_3754.py b/cumulusci/tests/triage/test_issue_3754.py new file mode 100644 index 0000000000..2a2c19df96 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3754.py @@ -0,0 +1,52 @@ +"""Regression repro for #3754. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (no_reverify_needed). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: ``cumulusci/cli/utils.py``: + +- ``get_latest_final_version`` (lines 65-79) hardcodes + ``https://pypi.org/pypi/cumulusci/json``. There is no env-var or + kwarg to point it at a private index or to disable the call. +- ``check_latest_version`` (lines 82-101) has no opt-out flag; the + only workaround documented in the thread is to touch + ``~/.cumulusci/cumulus_timestamp`` to a far-future epoch so the + hourly check is skipped indefinitely. + +This is painful for offline/air-gapped environments and for users on +corporate networks that block pypi. + +The fix is to add an env var (e.g. ``CUMULUSCI_DISABLE_VERSION_CHECK`` +and/or ``CUMULUSCI_PYPI_URL``) consumed inside ``check_latest_version`` +/ ``get_latest_final_version``. + +This test asserts ``cli.utils`` references one of those env vars or an +explicit disable/skip path; on dev it fails because none of them exist. +""" + +import inspect + +import pytest + +import cumulusci.cli.utils as cli_utils + + +@pytest.mark.xfail( + reason="repro for #3754 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3754(): + src = inspect.getsource(cli_utils) + tokens = ( + "CUMULUSCI_DISABLE_VERSION_CHECK", + "CUMULUSCI_PYPI_URL", + "DISABLE_VERSION_CHECK", + "PYPI_URL", + "PYPI_INDEX", + ) + found = [t for t in tokens if t in src] + assert found, ( + "cumulusci.cli.utils still hardcodes https://pypi.org/pypi/cumulusci/json " + "and offers no env-var to disable or redirect the version check (looked " + f"for {tokens}); offline/air-gapped users have no clean opt-out (see #3754)" + ) diff --git a/cumulusci/tests/triage/test_issue_3758.py b/cumulusci/tests/triage/test_issue_3758.py new file mode 100644 index 0000000000..43a18a5e16 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3758.py @@ -0,0 +1,44 @@ +"""Regression repro for #3758. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci/cumulusci.yml `push_upgrade_org` flow +(L1161-1177) terminates with `flow: config_qa` as step 5. +Semantically a push-upgrade targets a managed-package UAT sandbox, +so the final step should be `flow: config_managed`, not +`config_qa`. The two flows currently expand to the same task list +(`deploy_post`, `update_admin_profile`, `load_sample_data`) so +behavior is equivalent today - but semantics drift over time and +the docs link customers to the wrong flow page. + +A real fix is a one-line YAML change: `flow: config_qa` -> +`flow: config_managed`. + +This test loads cumulusci.yml and asserts the final step of +`push_upgrade_org` is `config_managed`. On dev it is `config_qa`, +so the assertion fails -> XFAIL. +""" + +from pathlib import Path + +import pytest +import yaml + +import cumulusci + + +@pytest.mark.xfail( + reason="repro for #3758 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3758(): + cumulusci_yml = Path(cumulusci.__file__).parent / "cumulusci.yml" + data = yaml.safe_load(cumulusci_yml.read_text()) + flow = data["flows"]["push_upgrade_org"] + last_step_key = max(flow["steps"].keys(), key=lambda k: int(k)) + last_step = flow["steps"][last_step_key] + assert last_step.get("flow") == "config_managed", ( + "Expected push_upgrade_org's final step to be `flow: config_managed` " + f"(targets a managed-package UAT sandbox), not {last_step!r}." + ) diff --git a/cumulusci/tests/triage/test_issue_3768.py b/cumulusci/tests/triage/test_issue_3768.py new file mode 100644 index 0000000000..9e3d41f706 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3768.py @@ -0,0 +1,71 @@ +"""Regression repro for #3768. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: the Snowfakery channel runner creates a separate working +directory per batch via `shutil.copytree(template_path, data_dir)` +(`cumulusci/tasks/bulkdata/snowfakery_utils/queue_manager.py:322`). +Before that copy, `Snowfakery._cleanup_object_tables` (snowfakery.py: +720-729) drops every non-`sf_ids` table from the template. So when +batch 2+ starts, the SQLite database carried in the template only +contains `*_sf_ids` mapping tables, with none of the actual rows +created during the initial `just_once: true` batch. + +Snowfakery's `random_reference: Account` resolves at generation time +against rows in the recipe-local database. With just_once Accounts +unavailable after batch 1, subsequent batches generate Contacts with +no Accounts to reference - exactly the user's symptom. + +The fix needs to preserve rows of just_once-referenced objects in +the template DB carried to subsequent batches (not just `_sf_ids` +rows). + +This test invokes `_cleanup_object_tables` against an engine that +contains a `just_once`-style data table and a corresponding +`account_sf_ids` mapping table, then uses the engine inspector to +check whether the data table physically still exists. On dev the +data table is physically dropped, so the assertion fails -> XFAIL. +""" + +from sqlalchemy import Column, MetaData, String, Table, create_engine, inspect + +import pytest + +from cumulusci.tasks.bulkdata.snowfakery import Snowfakery + + +@pytest.mark.xfail( + reason="repro for #3768 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3768(): + engine = create_engine("sqlite:///:memory:") + metadata = MetaData(bind=engine) + Table( + "account", + metadata, + Column("id", String(255), primary_key=True), + Column("Name", String(255)), + ) + Table( + "account_sf_ids", + metadata, + Column("id", String(255), primary_key=True), + Column("sf_id", String(255)), + ) + metadata.create_all(engine) + + assert "account" in inspect(engine).get_table_names(), ( + "test setup is wrong - account table not created" + ) + + task = Snowfakery.__new__(Snowfakery) + task._cleanup_object_tables(engine, metadata) + + remaining = set(inspect(engine).get_table_names()) + assert "account" in remaining, ( + "Snowfakery._cleanup_object_tables physically drops non-sf_ids tables; " + "just_once-referenced data is lost when subsequent batches start. " + f"Remaining physical tables after cleanup: {sorted(remaining)}" + ) diff --git a/cumulusci/tests/triage/test_issue_3771.py b/cumulusci/tests/triage/test_issue_3771.py new file mode 100644 index 0000000000..2037979c69 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3771.py @@ -0,0 +1,51 @@ +"""Regression repro for #3771. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: `transform_xpath()` defined inline in +`FindReplaceTransform.process()` (cumulusci/core/source_transforms/ +transforms.py:417-432) splits the user XPath on `/`, wraps each +plain tag in `*[local-name()=""]`, and re-appends the +predicate verbatim. Tags referenced INSIDE the predicate (e.g. +`price` inside `[price>40]`) keep their default namespace +binding, so on documents with a default xmlns the XPath +predicate matches nothing. PR #3772 (leboff's namespace fix) is +not merged. + +We exercise transform_xpath by importing it via inline grab +(it's a closure inside `process`) - instead we just inspect the +source and assert that predicate-internal tags are also wrapped +with `local-name()` (or some equivalent namespace-stripping +machinery). On dev they are not -> XFAIL. +""" + +import inspect + +import pytest + +from cumulusci.core.source_transforms.transforms import FindReplaceTransform + + +@pytest.mark.xfail( + reason="repro for #3771 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_issue_3771(): + src = inspect.getsource(FindReplaceTransform.process) + has_predicate_ns_handling = any( + token in src + for token in ( + "transform_predicate", + "predicate_local_name", + "strip_namespace", + "register_namespace", + "xmlns", + ) + ) + assert has_predicate_ns_handling, ( + "transform_xpath() in FindReplaceTransform still wraps only the " + "tag name with local-name(); predicate-internal references stay " + "namespace-bound and never match xmlns'd documents (see #3771)." + ) diff --git a/cumulusci/tests/triage/test_issue_3773.py b/cumulusci/tests/triage/test_issue_3773.py new file mode 100644 index 0000000000..f3b34ad872 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3773.py @@ -0,0 +1,44 @@ +"""Regression repro for #3773. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (no_reverify_needed). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: ``RetrieveProfileApi._queries_retrieve_permissions`` +(cumulusci/salesforce_api/retrieve_profile_api.py:164-195) only builds +queries for ``SetupEntityAccess``, ``ObjectPermissions``, +``PermissionSetTabSetting``, and a flow-specific ``SetupEntityAccess``. +No ``FieldPermissions`` query is built. As a consequence, +``retrieve_profile`` cannot discover that a profile has only +field-level (not object-level) permissions on something like +``AccountContactRelation``, and the parent SObject is never added to +the package.xml - so its field permissions are silently dropped from +the retrieved profile XML. + +The fix is to add a ``FieldPermissions`` query against +``Parent.Profile.Name`` and include those parent SObjectTypes in the +``CustomObject`` retrieve set. + +This test asserts ``_queries_retrieve_permissions`` source mentions +``FieldPermissions``; on dev it fails because the query is not built. +""" + +import inspect + +import pytest + +from cumulusci.salesforce_api.retrieve_profile_api import RetrieveProfileApi + + +@pytest.mark.xfail( + reason="repro for #3773 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3773(): + src = inspect.getsource(RetrieveProfileApi._queries_retrieve_permissions) + has_field_perms = "FieldPermissions" in src or "fieldpermissions" in src.lower() + assert has_field_perms, ( + "RetrieveProfileApi._queries_retrieve_permissions still does not query " + "FieldPermissions; profiles with only field-level perms on an object " + "(e.g. AccountContactRelation) will be retrieved with those perms missing " + "(see #3773)" + ) diff --git a/cumulusci/tests/triage/test_issue_3849.py b/cumulusci/tests/triage/test_issue_3849.py new file mode 100644 index 0000000000..56a9cb0cef --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3849.py @@ -0,0 +1,78 @@ +"""Repro for SFDO-Tooling/CumulusCI#3849. + +Pip-installing CumulusCI today picks ``urllib3>=2`` because ``requests`` +declares ``urllib3<3,>=1.26`` and CumulusCI itself imposes no upper bound. +Selenium 3.141.0 (locked in by ``selenium<4``) and +``robotframework-seleniumlibrary<6`` rely on the urllib3<2 Timeout API, so +Robot tests fail at runtime with:: + + ValueError: Timeout value connect was , + but it must be an int, float or None. + +The modernization (either dropping the ``selenium<4`` / +``robotframework-seleniumlibrary<6`` pins or adding ``urllib3<2`` to the +project dependencies) has not landed on ``origin/dev``. + +This test asserts the EXPECTED-modern state. Once any of those +constraints is fixed in ``pyproject.toml`` the assertion will pass and +``strict=False`` lets the existing suite keep going. + +See ``docs/triage/v5/repro-results.md``. +""" + +from __future__ import annotations + +import re +import tomllib +from pathlib import Path + +import cumulusci +import pytest + + +def _project_dependencies() -> list[str]: + # Locate the worktree's pyproject.toml via the installed cumulusci + # package source so this test is independent of pytest cwd / rootdir. + pkg_dir = Path(cumulusci.__file__).resolve().parent + pyproject = pkg_dir.parent / "pyproject.toml" + assert pyproject.is_file(), f"pyproject.toml not found at {pyproject}" + data = tomllib.loads(pyproject.read_text(encoding="utf-8")) + return list(data["project"]["dependencies"]) + + +def _has_urllib3_upper_bound(deps: list[str]) -> bool: + for spec in deps: + if re.match(r"^\s*urllib3\b", spec) and "<" in spec: + return True + return False + + +def _has_selenium_pin(deps: list[str], pkg: str) -> bool: + for spec in deps: + if re.match(rf"^\s*{re.escape(pkg)}\b.*<", spec): + return True + return False + + +@pytest.mark.xfail( + reason=("repro for #3849 - see docs/triage/v5/repro-results.md"), + strict=False, +) +def test_urllib3_or_selenium_modernized(): + """Either pin urllib3<2 explicitly or drop the selenium<4 / " + robotframework-seleniumlibrary<6 pins.""" + + deps = _project_dependencies() + + fixed = _has_urllib3_upper_bound(deps) or not ( + _has_selenium_pin(deps, "selenium") + and _has_selenium_pin(deps, "robotframework-seleniumlibrary") + ) + + assert fixed, ( + "Issue #3849 still reproduces: pyproject.toml lacks an explicit " + "urllib3 upper bound while still pinning selenium<4 and " + "robotframework-seleniumlibrary<6, so pip installs urllib3>=2 " + "and Robot tests crash on import.\n" + f"Current dependencies: {deps!r}" + ) diff --git a/cumulusci/tests/triage/test_issue_3889.py b/cumulusci/tests/triage/test_issue_3889.py new file mode 100644 index 0000000000..5b3f880f68 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3889.py @@ -0,0 +1,54 @@ +"""Regression repro for #3889. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: CumulusCI exposes uninstall tasks only for 1GP managed +packages (via namespace + InstalledPackage destructiveChanges): + + - cumulusci/cumulusci.yml:615-642 - uninstall_managed, + uninstall_packaged, uninstall_packaged_incremental, uninstall_src, + uninstall_pre, uninstall_post. None accept a 04t id. + - cumulusci/tasks/salesforce/UninstallPackage.py - `UninstallPackage` + only accepts `namespace` and `purge_on_delete`. + - cumulusci/salesforce_api/package_zip.py - `UninstallPackageZipBuilder` + writes destructiveChanges referencing InstalledPackage by namespace; + no 04t code path. + +The user wants a task that accepts an `04t...` SubscriberPackageVersion +id (or `Package2Version.Id`) and uninstalls via Tooling API (analogous +to `sf package uninstall -p 04t...`) so it doesn't depend on sf CLI +stability. + +A real fix introduces a new task (e.g. `UninstallPackageVersion`) +that calls the Tooling API directly. + +This test asserts `UninstallPackage` (or a sibling task) accepts a +SubscriberPackageVersion / 04t id. On dev no such option exists, so +the assertion fails -> XFAIL. +""" + +import pytest + +from cumulusci.tasks.salesforce.UninstallPackage import UninstallPackage + + +@pytest.mark.xfail( + reason="repro for #3889 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3889(): + options = set(UninstallPackage.task_options.keys()) + candidate_options = ( + "version_id", + "subscriber_package_version_id", + "package_version_id", + "package_version", + ) + found = [opt for opt in candidate_options if opt in options] + assert found, ( + "Expected UninstallPackage (or a sibling task) to expose a " + f"04t / SubscriberPackageVersion id option (one of " + f"{candidate_options!r}) for 2GP uninstalls; current options: " + f"{sorted(options)!r}." + ) diff --git a/cumulusci/tests/triage/test_issue_3910.py b/cumulusci/tests/triage/test_issue_3910.py new file mode 100644 index 0000000000..83673eea78 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3910.py @@ -0,0 +1,73 @@ +"""Repro for SFDO-Tooling/CumulusCI#3910. + +The JSON schema (and underlying Pydantic model) for a ``ScratchOrg`` +incorrectly declares ``namespaced`` as ``str`` when both the +documentation and the actual runtime code path (e.g. +``BaseProjectKeychain.create_scratch_org`` and +``ScratchOrgConfig._build_org_create_args``) treat it as a boolean. + +This test demonstrates the bug from two angles: + +1. The exported JSON schema must declare ``namespaced`` as ``boolean``. +2. The Pydantic model must preserve a YAML-style boolean rather than + silently coercing it to the strings ``"True"``/``"False"``. + +PR #3911 (OPEN as of 2026-05-14) fixes ``cumulusci_yml.py`` (changing +``namespaced: str = None`` to ``bool = None``) and the regenerated +``cumulusci.jsonschema.json``. Once that lands the assertions below +will all pass. +""" + +import json +from pathlib import Path + +import pytest + +from cumulusci.utils.yaml.cumulusci_yml import ScratchOrg + + +REPO_ROOT = Path(__file__).resolve().parents[3] +SCHEMA_PATH = REPO_ROOT / "cumulusci/schema/cumulusci.jsonschema.json" + + +def _scratch_org_namespaced_schema() -> dict: + with SCHEMA_PATH.open() as fh: + schema = json.load(fh) + return schema["definitions"]["ScratchOrg"]["properties"]["namespaced"] + + +@pytest.mark.xfail( + reason="repro for #3910 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_jsonschema_namespaced_is_boolean(): + assert _scratch_org_namespaced_schema()["type"] == "boolean" + + +@pytest.mark.xfail( + reason="repro for #3910 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_pydantic_model_namespaced_is_boolean(): + schema = ScratchOrg.schema() + assert schema["properties"]["namespaced"]["type"] == "boolean" + + +@pytest.mark.xfail( + reason="repro for #3910 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_pydantic_does_not_stringify_boolean_namespaced(): + parsed = ScratchOrg.parse_obj({"namespaced": True}) + assert parsed.namespaced is True + assert not isinstance(parsed.namespaced, str) + + +@pytest.mark.xfail( + reason="repro for #3910 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_pydantic_does_not_stringify_boolean_namespaced_false(): + parsed = ScratchOrg.parse_obj({"namespaced": False}) + assert parsed.namespaced is False + assert not isinstance(parsed.namespaced, str) diff --git a/cumulusci/tests/triage/test_issue_3931.py b/cumulusci/tests/triage/test_issue_3931.py new file mode 100644 index 0000000000..8b76641f65 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3931.py @@ -0,0 +1,82 @@ +"""Regression repro for #3931. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: cumulusci/tasks/salesforce/update_profile.py:290-292 +contains: + + for elem in tree.findall("layoutAssignments"): + if elem.find("recordType").text == rt["record_type"]: + elem.layout.text = layout_option + +`elem.find("recordType")` returns `None` whenever a +`` element has no `` child (a valid +metadata shape - layoutAssignments without recordType apply to +records lacking a record-type binding). The subsequent `.text` +access then raises `AttributeError: 'NoneType' object has no +attribute 'text'`, which is exactly the user's reported error. + +The fix is to bind `rt_elem = elem.find("recordType")` and check +`if rt_elem is not None and rt_elem.text == rt["record_type"]:` +before mutating. + +This test parses a profile containing a layoutAssignments element +with no recordType child via the CCI metadata_tree wrapper (the same +type ProfileGrantAllAccess._set_record_types operates on), then +calls `_set_record_types`. On dev the path raises AttributeError so +the assertion fails -> XFAIL. +""" + +from unittest import mock + +import pytest + +from cumulusci.tasks.salesforce.update_profile import ProfileGrantAllAccess +from cumulusci.utils.xml import metadata_tree + + +@pytest.mark.xfail( + reason="repro for #3931 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3931(): + profile_xml = b""" + + + Account-Account Layout + + + Account-Business Layout + Account.Business_Account + + + false + Account.Business_Account + true + +""" + tree = metadata_tree.fromstring(profile_xml) + + task = ProfileGrantAllAccess.__new__(ProfileGrantAllAccess) + task.options = { + "record_types": [ + { + "record_type": "Account.Business_Account", + "page_layout": "Account-Replacement Layout", + } + ] + } + task.namespace_prefixes = {"namespaced_org": "", "managed": ""} + task.logger = mock.MagicMock() + + raised = None + try: + task._set_record_types(tree, "Admin") + except AttributeError as e: + raised = e + + assert raised is None, ( + "update_profile._set_record_types raised AttributeError on a " + f"layoutAssignments without recordType child: {raised}" + ) diff --git a/cumulusci/tests/triage/test_issue_3951.py b/cumulusci/tests/triage/test_issue_3951.py new file mode 100644 index 0000000000..f839ecd4a9 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3951.py @@ -0,0 +1,50 @@ +"""Regression repro for #3951. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: `SetDuplicateRuleStatus` extends +`MetadataSingleEntityTransformTask` whose `_transform` looks for +metadata files by the literal `api_name`. DuplicateRule API +names always follow `.`; users typing only +the rule name (e.g. +``Standard_Rule_for_Leads_with_Duplicate_Contacts``) crash with +the unhelpful ``Cannot find metadata file ... .duplicateRule``. +The task itself does not document or detect this format +requirement. + +Proposed UX fix: improve the `api_names` option help string to +call out the ``.`` format. This test asserts +that today's task surface includes the format hint. On dev it +doesn't -> XFAIL. + +(Same root-cause family as #3613.) +""" + +import pytest + +from cumulusci.tasks.metadata_etl.duplicate_rules import ( + SetDuplicateRuleStatus, +) + + +@pytest.mark.xfail( + reason="repro for #3951 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_issue_3951(): + api_names_opt = SetDuplicateRuleStatus.task_options.get("api_names", {}) + description = (api_names_opt.get("description") or "").lower() + hints_at_object_prefix = ( + "" in description + or "object_name" in description + or "object.rulename" in description + or "object." in description + ) + assert hints_at_object_prefix, ( + "SetDuplicateRuleStatus.task_options['api_names'] description " + f"({description!r}) still does not warn the user that " + "DuplicateRule API names require the . form " + "(see #3951)." + ) diff --git a/cumulusci/tests/triage/test_issue_3953.py b/cumulusci/tests/triage/test_issue_3953.py new file mode 100644 index 0000000000..1590d3f11b --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3953.py @@ -0,0 +1,58 @@ +"""Regression repro for #3953. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-v4.10.0 (no_reverify_needed). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: ``AddPicklistEntries._init_options`` +(cumulusci/tasks/metadata_etl/picklists.py around line 68) checks +``all("fullName" in entry for entry in self.options["entries"])`` +without first parsing ``entries`` from a JSON string. When the task +is invoked from the CLI, ``-o entries '[{...}]'`` arrives as the +literal string; iterating that string walks characters one at a time, +none contain the substring ``"fullName"``, and the task always errors +with ``Error: The 'fullName' key is required on all picklist values``. + +Net effect: ``cci task run add_picklist_entries`` is unusable from the +CLI on v4.10.0. + +The minimal fix is to JSON-parse ``self.options["entries"]`` when it +is a string before validating it (and apply the same coercion to +``record_types`` for symmetry). A more general fix is schema-driven +list coercion via the new Pydantic ``Options`` model. + +This test asserts the source of ``AddPicklistEntries._init_options`` +parses string-form ``entries`` (e.g. references ``json.loads`` or +``process_list_arg`` on ``entries``); on dev it fails because no such +parsing exists. +""" + +import inspect + +import pytest + +from cumulusci.tasks.metadata_etl.picklists import AddPicklistEntries + + +@pytest.mark.xfail( + reason="repro for #3953 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_3953(): + src = inspect.getsource(AddPicklistEntries._init_options) + parses_entries = any( + token in src + for token in ( + 'json.loads(self.options["entries"]', + "json.loads(self.options['entries']", + 'json.loads(self.options.get("entries"', + "json.loads(self.options.get('entries'", + 'process_list_arg(self.options["entries"]', + "process_list_arg(self.options['entries']", + ) + ) + assert parses_entries, ( + "AddPicklistEntries._init_options still does not parse the 'entries' " + "option from a JSON string; CLI invocation 'cci task run " + 'add_picklist_entries -o entries "[{...}]"\' iterates the string char ' + "by char and always raises \"'fullName' key is required\" (see #3953)" + ) diff --git a/cumulusci/tests/triage/test_issue_3955.py b/cumulusci/tests/triage/test_issue_3955.py new file mode 100644 index 0000000000..bda5971011 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_3955.py @@ -0,0 +1,92 @@ +"""Regression marker for SFDO-Tooling/CumulusCI#3955. + +`SalesforcePlaywright.open_test_browser` splits the ``size`` argument with +``str.split("x", 1)`` and forwards the resulting string fragments straight to +``browser.new_context(viewport={"width": , "height": })``. + +Playwright requires ``viewport.width`` / ``viewport.height`` to be ``int``; +passing strings raises:: + + browser.newContext: viewport.width: expected integer, got string + +Expected behaviour: ``width`` and ``height`` should be cast to ``int`` before +being forwarded. +""" + +import sys +import types +from unittest import mock + +import pytest + + +@pytest.fixture(autouse=True) +def _stub_browser_library(): + """``robotframework-browser`` is not a unit-test dependency, so stub the + minimum surface that ``SalesforcePlaywright`` imports at module load.""" + browser_mod = types.ModuleType("Browser") + browser_mod.SupportedBrowsers = mock.MagicMock(name="SupportedBrowsers") + utils_mod = types.ModuleType("Browser.utils") + data_types_mod = types.ModuleType("Browser.utils.data_types") + data_types_mod.KeyAction = mock.MagicMock(name="KeyAction") + data_types_mod.PageLoadStates = mock.MagicMock(name="PageLoadStates") + + added = [] + for name, mod in ( + ("Browser", browser_mod), + ("Browser.utils", utils_mod), + ("Browser.utils.data_types", data_types_mod), + ): + if name not in sys.modules: + sys.modules[name] = mod + added.append(name) + try: + yield + finally: + for name in added: + sys.modules.pop(name, None) + + +@pytest.mark.xfail( + reason=("repro for #3955 - see docs/triage/v5/repro-results.md"), + strict=False, +) +def test_open_test_browser_passes_int_viewport_to_playwright(): + from cumulusci.robotframework.SalesforcePlaywright import SalesforcePlaywright + + lib = SalesforcePlaywright() + + lib._browser = mock.MagicMock(name="Browser") + lib._browser.new_browser.return_value = "browser-id" + lib._browser.new_context.return_value = "context-id" + lib._browser.new_page.return_value = {"page_id": "page-id"} + + lib._cumulusci = mock.MagicMock(name="CumulusCI") + lib._cumulusci.login_url.return_value = "https://test.example.com" + + def _get_variable_value(name, default=None): + if name == "${DEFAULT BROWSER SIZE}": + return "1280x1024" + if name == "${BROWSER}": + return "chrome" + return default + + builtin = mock.MagicMock(name="BuiltIn") + builtin.get_variable_value.side_effect = _get_variable_value + builtin.convert_to_boolean.side_effect = lambda v: bool(v) + lib._builtin = builtin + + with mock.patch.object(lib, "wait_until_salesforce_is_ready"): + lib.open_test_browser() + + assert lib._browser.new_context.called, "new_context should be invoked" + _args, kwargs = lib._browser.new_context.call_args + viewport = kwargs["viewport"] + assert isinstance(viewport["width"], int), ( + "viewport.width should be int (Playwright contract); got " + f"{type(viewport['width']).__name__}: {viewport['width']!r}" + ) + assert isinstance(viewport["height"], int), ( + "viewport.height should be int (Playwright contract); got " + f"{type(viewport['height']).__name__}: {viewport['height']!r}" + ) diff --git a/cumulusci/tests/triage/test_issue_710.py b/cumulusci/tests/triage/test_issue_710.py new file mode 100644 index 0000000000..25d657a1f8 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_710.py @@ -0,0 +1,85 @@ +"""Repro for SFDO-Tooling/CumulusCI#710. + +Projects today inherit four default scratch-org configurations from +``cumulusci/cumulusci.yml`` (``dev``, ``qa``, ``feature``, ``beta``, +``release``). The issue requests that a project be able to disable any +of these defaults via its own ``cumulusci.yml``, e.g.:: + + orgs: + scratch: + dev: + config_file: None + +On ``origin/dev`` this is not honoured: + +* ``cumulusci.core.utils.merge_config`` silently drops a ``None`` + override (``dictmerge`` skips ``None`` values), so the universal + ``config_file: orgs/dev.json`` survives the merge. +* ``BaseProjectKeychain._load_scratch_orgs`` iterates every key under + ``orgs.scratch`` and unconditionally calls ``create_scratch_org``. + +Both behaviours are exercised below so the test fails (XFAIL) today +and passes once #710 is implemented in whichever direction the team +chooses (sentinel ``config_file: None``, an explicit ``disabled: true`` +flag, omission of inherited keys, etc.) - the assertions only check +the externally-visible end state: ``dev`` is not in the keychain after +the project disables it. +""" + +import pytest + +from cumulusci.core.config import BaseProjectConfig, UniversalConfig +from cumulusci.core.keychain import BaseProjectKeychain +from cumulusci.core.utils import merge_config + + +@pytest.fixture() +def disabling_project_config(): + universal_config = UniversalConfig() + merged = merge_config( + { + "universal_config": universal_config.config, + "project_config": { + "project": {"name": "TestProject"}, + "orgs": {"scratch": {"dev": {"config_file": None}}}, + }, + } + ) + project_config = BaseProjectConfig(universal_config, config={"no_yaml": True}) + project_config.config = merged + project_config.project__name = "TestProject" + return project_config + + +@pytest.mark.xfail( + reason="repro for #710 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_project_can_disable_default_dev_scratch_org_via_none_config_file( + disabling_project_config, +): + keychain = BaseProjectKeychain(disabling_project_config, key="0123456789123456") + assert "dev" not in keychain.orgs, ( + "Setting `config_file: None` on the inherited `dev` scratch org config " + "should disable it, but the keychain still loaded it." + ) + + +@pytest.mark.xfail( + reason="repro for #710 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_merge_config_preserves_explicit_none_override(): + universal_config = UniversalConfig() + merged = merge_config( + { + "universal_config": universal_config.config, + "project_config": { + "orgs": {"scratch": {"dev": {"config_file": None}}}, + }, + } + ) + assert merged["orgs"]["scratch"]["dev"]["config_file"] is None, ( + "merge_config should preserve an explicit None override so that " + "downstream code can detect a disabled scratch org config." + ) diff --git a/cumulusci/tests/triage/test_issue_733.py b/cumulusci/tests/triage/test_issue_733.py new file mode 100644 index 0000000000..e1d4e5506a --- /dev/null +++ b/cumulusci/tests/triage/test_issue_733.py @@ -0,0 +1,34 @@ +"""Regression repro for #733. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: CliRuntime.check_org_overwrite() at cumulusci/cli/runtime.py +hardcodes a `click.ClickException` for an already-created scratch org. +The original 2018 ask is to interactively prompt the user (Y/N) to delete +the existing scratch org rather than hard-erroring. + +The fix would introduce `click.confirm` / `click.prompt` (or equivalent) so +the function offers an interactive choice. This test asserts presence of a +prompt mechanism inside `check_org_overwrite`; on dev it fails because the +function still only raises `ClickException`. +""" + +import inspect + +import pytest + +from cumulusci.cli.runtime import CliRuntime + + +@pytest.mark.xfail( + reason="repro for #733 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_733(): + src = inspect.getsource(CliRuntime.check_org_overwrite) + has_prompt = any(token in src for token in ("click.confirm", "click.prompt")) + assert has_prompt, ( + "check_org_overwrite still hard-errors with ClickException; " + "no interactive prompt path found in source" + ) diff --git a/cumulusci/tests/triage/test_issue_773.py b/cumulusci/tests/triage/test_issue_773.py new file mode 100644 index 0000000000..af9d566c41 --- /dev/null +++ b/cumulusci/tests/triage/test_issue_773.py @@ -0,0 +1,75 @@ +"""Repro for SFDO-Tooling/CumulusCI#773 - Document task return values and results. + +cdcarter requested that tasks be able to *declare* their ``return_values`` +(and ``result``) and that ``cci task info`` / the web docs surface that +declaration. + +On ``origin/dev`` (1925a3083): + +* ``BaseTask`` exposes a ``task_options`` class attribute that + ``doc_task()`` walks (``cumulusci/utils/__init__.py:doc_task``) to emit + an "Options" section. +* ``BaseTask`` exposes a free-form ``task_docs`` string that ``doc_task`` + splices into the output. +* There is **no** declarative analogue for return values: ``self.return_values`` + is an empty dict mutated at runtime, and ``doc_task`` never emits a + "Return Values" / "Returns" section. + +This test pins down the gap by running ``doc_task`` against a task that +sets ``return_values`` at runtime and asserting the rendered RST advertises +those return values. That assertion fails today because the task-info +plumbing has no awareness of return values. Mark xfail until a declarative +schema (e.g. ``return_values_schema``) and matching ``doc_task`` section +are added. +""" + +from __future__ import annotations + +import pytest + +from cumulusci.core.config import BaseProjectConfig, TaskConfig, UniversalConfig +from cumulusci.core.keychain import BaseProjectKeychain +from cumulusci.tasks.salesforce.package_upload import PackageUpload +from cumulusci.utils import doc_task + + +def _make_task_config(): + """Build a minimal TaskConfig pointing at PackageUpload - a real shipping + task that documents (in its docstring / source) that it populates + ``return_values`` with ``version_number``, ``version_id``, and ``package_id``. + """ + universal_config = UniversalConfig() + project_config = BaseProjectConfig(universal_config, config={"noyaml": True}) + project_config.set_keychain(BaseProjectKeychain(project_config, key=None)) + task_config = TaskConfig( + { + "class_path": f"{PackageUpload.__module__}.{PackageUpload.__name__}", + "description": "Uploads a beta release of the metadata currently in the packaging org", + "options": {}, + } + ) + return task_config + + +@pytest.mark.xfail( + reason="repro for #773 - see docs/triage/v5/repro-results.md", + strict=False, +) +def test_doc_task_renders_return_values_section(): + """``cci task info`` (via ``doc_task``) should document a task's + declared return values. Today no declarative mechanism exists, so the + rendered RST has no "Return Values" section even for tasks whose + implementation clearly populates ``self.return_values``. + """ + rendered = doc_task("upload_beta", _make_task_config()) + + assert "Return Values" in rendered or "Returns" in rendered, ( + "doc_task output should include a 'Return Values' (or 'Returns') " + "section sourced from a declarative attribute on the task class " + "(parallel to task_options / task_docs)." + ) + for key in ("version_number", "version_id", "package_id"): + assert key in rendered, ( + f"doc_task output should mention the {key!r} return value populated " + f"by PackageUpload._set_return_values()." + ) diff --git a/cumulusci/tests/triage/test_issue_808.py b/cumulusci/tests/triage/test_issue_808.py new file mode 100644 index 0000000000..12dbef6ebe --- /dev/null +++ b/cumulusci/tests/triage/test_issue_808.py @@ -0,0 +1,37 @@ +"""Regression repro for #808. + +See docs/triage/v5/repro-results.md for full narrative. +Verdict: REPRODUCED-on-dev (R1+R2 + Task 4 recovery). +This xfail marker will be removed by the corresponding fix-PR. + +Root cause: UninstallPackaged._init_options() at +cumulusci/tasks/salesforce/UninstallPackaged.py:21-24 defaults the +'package' option to `project__package__name` only. The asymmetric fix +expected (matching InstallPackageVersion._init_options, which falls +back through `name_managed` -> `name` -> `namespace`) is to consult +`project__package__name_managed` first. Today the function never +consults `project__package__name_managed`, so deploy_packaging of a +managed package whose unmanaged display name differs ends up +uninstalling the wrong package. + +This test asserts the override source mentions `name_managed`; on dev +it does not, so the assertion fails -> XFAIL. +""" + +import inspect + +import pytest + +from cumulusci.tasks.salesforce.UninstallPackaged import UninstallPackaged + + +@pytest.mark.xfail( + reason="repro for #808 - see docs/triage/v5/repro-results.md", strict=False +) +def test_issue_808(): + src = inspect.getsource(UninstallPackaged._init_options) + assert "name_managed" in src, ( + "UninstallPackaged._init_options still falls back to " + "project__package__name only; expected name_managed fallback. " + f"Current source:\n{src}" + ) diff --git a/docs/triage/v5/README.md b/docs/triage/v5/README.md new file mode 100644 index 0000000000..28464ea49e --- /dev/null +++ b/docs/triage/v5/README.md @@ -0,0 +1,125 @@ +# CCI v5 Issue Triage Evidence (May 2026) + +This directory contains the results of an AI-assisted triage of the 142 +open issues in `SFDO-Tooling/CumulusCI` as part of preparation for +CumulusCI v5. + +## Contents + +| File | Purpose | +| ---------------------------- | ------------------------------------------------------------------------------------------- | +| `README.md` | This file. | +| `proposals.md` | Per-issue classification + proposed pass-1 action (kept-open, close-with-reason, etc.). | +| `repro-results.md` | Per-issue narrative evidence for the 115 issues that went through the reproducibility pass. | +| `repro-results.csv` | Machine-readable matrix backing the narrative (one row per issue). | +| `themes.md` | Theme clusters, duplicate detection, and Tranche-1 candidate list. | +| `fix-sketches/issue_NNNN.md` | One fix sketch per `REPRODUCED` issue not slated for an immediate fix-PR. | + +## Methodology + +1. **Classification**: All 142 open issues were evaluated against a + hygiene policy covering staleness (>24mo no activity), missing + reproducer information, and known-resolved status. Output: + `proposals.md`. + +2. **Theme clustering**: Issues were grouped into 12 themes + (`packaging`, `metadata-etl`, `cli`, `bulkdata`, `dependencies`, + `ci-integration`, `robotframework`, `scratch-org-config`, `auth`, + `keychain`, `docs`, `python-modernization`) with cross-theme + duplicate detection. Output: `themes.md`. + +3. **Reproducibility pass**: 115 of 142 issues were verified in + isolated git worktrees against CumulusCI v4.10.0 or `origin/dev`. + The 27 `closed:pre-v4.0.0` issues were excluded from the repro pass + (they're tagged separately for close). Verdicts: + + | Verdict | Count | + | ---------------------------------- | ------: | + | `REPRODUCED-on-v4.10.0` | 56 | + | `REPRODUCED-on-dev` | 24 | + | `NOT-REPRODUCED-on-v4.10.0` | 18 | + | `NOT-REPRODUCED-on-dev` | 5 | + | `INCONCLUSIVE-needs-*` (kept-open) | 11 | + | `closed:duplicate-of-#3544` | 1 | + | **Total** | **115** | + + Output: `repro-results.md`, `repro-results.csv`. + +4. **Failing-test capture**: For each `REPRODUCED-on-*` issue with a + code-level assertion target, a `@pytest.mark.xfail(strict=False)` + regression test was committed to `cumulusci/tests/triage/`. The + xfail marker is intentionally non-strict so a bug that resolves + independently surfaces as `XPASS` rather than as a CI failure. + Fix-PRs remove the marker. + +5. **Fix sketches**: For every `REPRODUCED` issue not slated for an + immediate fix-PR, `fix-sketches/issue_NNNN.md` captures the + proposed approach, target `file:line`, size estimate, and risk. + +6. **Tranche 1 fix PRs**: A small slate of recent regressions + (`#3852`, `#3854`, `#3886`, `#3938`, `#3939`) receives immediate + fix-PRs against `dev` with the xfail marker removed and a + corresponding passing unit test. + +## AI assistance disclosure + +This triage was conducted with AI assistance. Specifically: + +- Initial classification, theme clustering, and reproducibility + verification were performed by AI coding agents working in + isolated `git` worktrees with hard-coded constraints: no source + mutation outside scope, no live-org access, no scratch-org + creation outside a designated DevHub, no GitHub state mutation, + no `git push`. +- The xfail tests in `cumulusci/tests/triage/` are intentionally + `strict=False`; an `XPASS` surfaces a verdict that no longer + holds (e.g. the bug was fixed independently) rather than crashing + CI. +- All proposed pass-1 mutations against GitHub issues (close / + label) are gated on explicit maintainer approval before + execution. They are NOT executed by this PR. + +Intermediate run logs (dispatch records, anomaly notes, consolidation +scripts) are kept locally and intentionally not included here. + +## Spec basis + +Pass-1 vocabulary used in `proposals.md`: + +- `closed:stale-24mo` - no activity >24 months, no maintainer label. +- `closed:pre-v4.0.0` - body declares CumulusCI 3.x; no reporter + reconfirmation against v4+. +- `closed:missing-fields` - issue lacks repro / cci-version / + expected behaviour. +- `closed:pr-resolved-#NNNN` - fix already on dev via specified PR. +- `closed:not-reproducible-on-v4.10.0` - bug not reproducible on + v4.10.0; close with explicit verdict. +- `closed:not-reproducible-on-dev` - bug not reproducible on `dev`; + close with explicit verdict. +- `closed:feature-implemented` - feature ask already shipped. +- `closed:duplicate-of-#NNNN` - pointer to canonical issue. +- `closed:pr-pending-#NNNN` - fix exists in an open PR ready to + land; close once that PR merges. +- `kept-open` - confirmed REPRO; rescue from close. + +Pass-2 vocabulary (selected): `target:v4-patch`, `v5-candidate:yes|no`, +`severity:{critical,major,minor,trivial}`, `area:{packaging,bulkdata, +cli,robotframework,...}`. + +## Tranche 1 candidate list + +Recent regressions slated for immediate fix-PR against `dev`: + +| Issue | Title | Estimated diff | +| ----- | ------------------------------------------------------------ | -------------- | +| #3852 | sarge cosmetic warning under Python 3.13 | ~5-15 LOC | +| #3854 | extract-validation regression from PR #3741 | ~5-10 LOC | +| #3886 | `cumulusci[select]` warning fires at every `extract_dataset` | ~3-5 LOC | +| #3938 | (see fix-sketches/issue_3938.md) | TBD | +| #3939 | (see fix-sketches/issue_3939.md) | TBD | + +Two adjacent fix-already-on-dev issues that close without a fix-PR +needed here: + +- `#3610` → `closed:pr-resolved-#3681` (already on dev). +- `#3910` → `closed:pr-pending-#3911` (PR #3911 ready to land). diff --git a/docs/triage/v5/fix-sketches/issue_1348.md b/docs/triage/v5/fix-sketches/issue_1348.md new file mode 100644 index 0000000000..b43d363912 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_1348.md @@ -0,0 +1,12 @@ +# Fix sketch - #1348: Multiple Git Provider Support **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `cli` +**Issue**: ## Bug `rg -li "gitlab" cumulusci/` and `rg -li "bitbucket" cumulusci/` both return zero matches. The `ci_feature` flow still hardcodes GitHub-specific tasks: ## Target `cumulusci/cumulusci.yml` lines 767-789 ## Recommended approach (from triage narrative) - pass1: `closed:stale-24mo` - large architectural change (multi-VCS abstraction); 6yr no traction; user `zenibako` confirmed using cci on GitLab via custom flows is feasible. + +- | pass2 labels: `enhancement,stale,wontfix-candidate` --- ## Size & risk | Field | Value | + | ---------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_1348.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #1348:`). | diff --git a/docs/triage/v5/fix-sketches/issue_1432.md b/docs/triage/v5/fix-sketches/issue_1432.md new file mode 100644 index 0000000000..20a2d9ab62 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_1432.md @@ -0,0 +1,12 @@ +# Fix sketch - #1432: CCI Inconsistencies in validating arguments **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `cli` +**Issue**: ## Bug Old-style `task_options` dict still does not validate unknown keys: Repro test passes (= unknown `colour` typo is silently accepted via YAML/Python path): Test path: `_(repro evidence; see narrative)_`. ## Target `cumulusci/core/tasks.py` lines 186-196 ## Recommended approach (from triage narrative) - pass1: `closed:stale-24mo` - 5yr; partial mitigation in place; full fix would require reworking every legacy `task_options` dict task. + +- | pass2 labels: `bug,stale,partially-fixed` --- ## Size & risk | Field | Value | + | ------------------------------------------------------------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_1432.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #1432:`). | diff --git a/docs/triage/v5/fix-sketches/issue_1769.md b/docs/triage/v5/fix-sketches/issue_1769.md new file mode 100644 index 0000000000..541664614b --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_1769.md @@ -0,0 +1,12 @@ +# Fix sketch - #1769: Unusual case in test_load **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `bulkdata` +**Issue**: ## Bug The original line 352 in `158a2d4356f` (May 2020) was: In v4.10.0 the same pattern survives, just wrapped in `MappingLookup`: The pattern repeats at lines 754, 773, 801, 1119, 1187, 1255 - declaring `Id` as a "lookup" key inside the `lookups` dict so `_expand_mapping` can express the after-step's UPDATE-on-Id dependency. davidmreed acknowledged in 2020 it was "a horrible hack" he intended to clean... ## Target `cumulusci/tasks/bulkdata/tests/test_load.py` lines 736-739 ## Recommended approach (from triage narrative) - pass1: `closed:stale-24mo` - pure test-fixture nit; never escalated to a real bug; original commenters have moved on. + +- | pass2 labels: `test-cleanup, low-priority` --- ## Size & risk | Field | Value | + | ------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_1769.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #1769:`). | diff --git a/docs/triage/v5/fix-sketches/issue_2013.md b/docs/triage/v5/fix-sketches/issue_2013.md new file mode 100644 index 0000000000..48882f1822 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_2013.md @@ -0,0 +1,12 @@ +# Fix sketch - #2013: Mapping files with steps that are not 1-1 with SObjects are unreliable for extraction **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `bulkdata` +**Issue**: ## Bug `create_table_if_needed` (`utils.py:133-139`) tries to detect duplicate tables but the SQLAlchemy `Table()` constructor raises first: Reproduction (`_(repro evidence; see narrative)_`) yields the exact 2020 traceback: ## Target `cumulusci/tasks/bulkdata/utils.py` lines 133-139 ## Recommended approach (from triage narrative) - pass1: `keep-open` - bug is real, easy to reproduce, easy to fix (catch the SQLAlchemy error and re-raise as `BulkDataException`, or validate at mapping-parse time). + +- | pass2 labels: `bug, bulkdata, extract_dataset, error-handling` --- ## Size & risk | Field | Value | + | --------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_2013.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #2013:`). | diff --git a/docs/triage/v5/fix-sketches/issue_2140.md b/docs/triage/v5/fix-sketches/issue_2140.md new file mode 100644 index 0000000000..614b302840 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_2140.md @@ -0,0 +1,12 @@ +# Fix sketch - #2140: Prompt Org Configs when Org Does Not Exist and Command Runs Against It **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `cli` +**Issue**: ## Bug `keychain.get_org` raises `OrgNotFound` -> `cli/org.py:530-531` shows `"Org {name} does not exist in the keychain"`. No interactive prompt offering available scratch configs. ## Target `cumulusci/cli/runtime.py` lines 95-104 ## Recommended approach (from triage narrative) - pass1: `closed:stale-24mo` - 5yr `cli-usability` enhancement with no traction. + +- | pass2 labels: `enhancement,cli-usability,stale` --- ## Size & risk | Field | Value | + | ------------------------------------------------------------------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_2140.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #2140:`). | diff --git a/docs/triage/v5/fix-sketches/issue_2153.md b/docs/triage/v5/fix-sketches/issue_2153.md new file mode 100644 index 0000000000..1f9318d8f3 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_2153.md @@ -0,0 +1,12 @@ +# Fix sketch - #2153: Add comment to original PR which tags all branch subscribers when a merge **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `ci-integration` +**Issue**: ## Bug `cumulusci/tasks/github/merge.py` `_create_conflict_pull_request` (the only place an auto-merge PR is created): The method only creates the conflict PR; it never opens a comment on any PR (the original child PR or otherwise). Repo-wide grep for `create_comment|issue_comment|pr.create_comment|comment.*pull_request` under `cumulusci/tasks/github` returns no hits in production code (only test fixture... ## Target `cumulusci/tasks/github/merge.py` lines 264-288 ## Recommended approach (from triage narrative) - pass1: `keep-open` - small, well-scoped enhancement; reasonable "good-second-issue". + +- | pass2 labels: `enhancement, github, merge-conflict` --- ## Size & risk | Field | Value | + | ---------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_2153.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #2153:`). | diff --git a/docs/triage/v5/fix-sketches/issue_2325.md b/docs/triage/v5/fix-sketches/issue_2325.md new file mode 100644 index 0000000000..41730f06fe --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_2325.md @@ -0,0 +1,12 @@ +# Fix sketch - #2325: Task to turn off validation rules to allow data insert **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `bulkdata` +**Issue**: ## Bug - Trigger analog: `disable_tdtm_trigger_handlers` / `restore_tdtm_trigger_handlers` (`cumulusci.yml:738-747`). - DuplicateRule analog: `set_duplicate_rule_status` → `cumulusci.tasks.metadata_etl.duplicate_rules.SetDuplicateRuleStatus` (a 25-line `MetadataSingleEntityTransformTask` subclass with `entity = "DuplicateRule"`). - ValidationRule equivalent: **none.** `cci task list | grep -i -E "v... ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open` - feature still missing, clear implementation pattern, modest scope. + +- | pass2 labels: `enhancement, bulkdata, metadata_etl, good-first-issue` --- ## Size & risk | Field | Value | + | ---------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_2325.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #2325:`). | diff --git a/docs/triage/v5/fix-sketches/issue_2402.md b/docs/triage/v5/fix-sketches/issue_2402.md new file mode 100644 index 0000000000..04d7b9bd30 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_2402.md @@ -0,0 +1,12 @@ +# Fix sketch - #2402: Create a --rebuild-org parameter for cci flow run **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `cli` +**Issue**: ## Bug Only `--delete-org` exists. `rg -i "rebuild.org"` returns zero hits. ## Target `cumulusci/cli/flow.py` lines 119-145 ## Recommended approach (from triage narrative) - pass1: `closed:stale-24mo` - 5yr; (no movement); user can accomplish via `cci org scratch_delete X && cci flow run`. + +- | pass2 labels: `enhancement,stale` --- ## Size & risk | Field | Value | + | ---------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_2402.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #2402:`). | diff --git a/docs/triage/v5/fix-sketches/issue_2500.md b/docs/triage/v5/fix-sketches/issue_2500.md new file mode 100644 index 0000000000..d041f04cba --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_2500.md @@ -0,0 +1,18 @@ +# Fix sketch - #2500: `ignore_failure` is not documented **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `docs` +**Issue**: ## Bug `ignore_failure` is not documented ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) Add `### Ignore a Failed Step` (or `### Continue After a Failed Step`) +to `docs/config.md` immediately after the existing `### Skip a Flow Step` +section. Body should cover: - What it does: when `ignore_failure: True` is set on a step, an exception raised by that step does not abort the flow; subsequent steps still run. + +- A minimal YAML example (the existing `release_unlocked_production` snippet around line 783 already demonstrates it - link to it instead of duplicating). +- Interaction with `^^step.return_value` references: downstream steps that reference a failed step's return values must defend against missing keys. +- When NOT to use it: in CI, silently ignoring a failure hides regressions; prefer a `when:` clause for intentional conditional branching. +- One sentence on the difference between `ignore_failure` (post hoc: swallow the exception) and `when:` (a priori: skip the step entirely). This is a tiny, high-leverage docs PR - a strong candidate for a + `good-first-issue` label. ## Size & risk | Field | Value | + | ------------------------------------ | ---------------------- | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_2500.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #2500:`). diff --git a/docs/triage/v5/fix-sketches/issue_2506.md b/docs/triage/v5/fix-sketches/issue_2506.md new file mode 100644 index 0000000000..a2ec208409 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_2506.md @@ -0,0 +1,12 @@ +# Fix sketch - #2506: Bulk Operations should have a --debug mode which maintains logs and tempfiles **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `bulkdata` +**Issue**: ## Bug - Snowfakery task (`snowfakery.py:241,355,385,565`) calls `get_debug_mode()` and at `:386` logs `f"Working Directory: {tempdir}"` per loop iteration. - `extract.py`, `step.py` - **zero** references to `debug_mode` or `get_debug_mode`. - `load.py:283` uses `tempfile.TemporaryFile` with no debug-mode override; the file is auto-deleted on context exit regardless of debug. ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open` - partial implementation; remaining work is straightforward (wire `get_debug_mode()` into load/extract, conditionally use `TemporaryDirectory(delete=False)` and emit path). + +- | pass2 labels: `enhancement, bulkdata, extract_dataset, load_dataset, debugging` --- ## Size & risk | Field | Value | + | -------------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_2506.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #2506:`). | diff --git a/docs/triage/v5/fix-sketches/issue_2507.md b/docs/triage/v5/fix-sketches/issue_2507.md new file mode 100644 index 0000000000..a9ef9e1975 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_2507.md @@ -0,0 +1,12 @@ +# Fix sketch - #2507: Undo mode for CumulusCI Insert **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `cli` +**Issue**: ## Bug No `undo_insert` task exists (`rg -l undo_insert` returns nothing). Closest functionality is `enable_rollback` on `load_dataset` and `snowfakery` tasks: That only triggers rollback on error during the load; it does not provide the post-hoc "delete everything we ever inserted" capability the requester described. ## Target `cumulusci/tasks/bulkdata/load.py` lines 97-99 ## Recommended approach (from triage narrative) - pass1: `closed:stale-24mo` - 4yr feature with partial mitigation already shipped. + +- | pass2 labels: `enhancement,stale,partially-fixed` --- ## Size & risk | Field | Value | + | -------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_2507.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #2507:`). | diff --git a/docs/triage/v5/fix-sketches/issue_2508.md b/docs/triage/v5/fix-sketches/issue_2508.md new file mode 100644 index 0000000000..846bab89d6 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_2508.md @@ -0,0 +1,12 @@ +# Fix sketch - #2508: Manual load retries **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `bulkdata` +**Issue**: ## Bug - `cci task list` returns no retry-named task. - `load.py` has an `enable_rollback` option (`:97-98`, `RollbackType` enum at `:1051`) but rollback **undoes successful inserts when failures occur** - the opposite of "retry the failures." - `RowErrorChecker` (`utils.py:158`) only logs and optionally raises; it does not persist a failed-rows artifact that could be replayed. ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open` - distinct from rollback; would need (a) failed-row CSV/SQL persistence + (b) a new `retry_failed_load` task that consumes it. + +- | pass2 labels: `enhancement, bulkdata, load_dataset, reliability` --- ## Size & risk | Field | Value | + | ----------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_2508.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #2508:`). | diff --git a/docs/triage/v5/fix-sketches/issue_2826.md b/docs/triage/v5/fix-sketches/issue_2826.md new file mode 100644 index 0000000000..9788f0b7ba --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_2826.md @@ -0,0 +1,12 @@ +# Fix sketch - #2826: deploy_unmanaged flow is supposed to silently do nothing if there's not actually a package directory **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `metadata-etl` +**Issue**: ## Bug deploy*unmanaged flow is supposed to silently do nothing if there's not actually a package directory ## Target \_See narrative for target file:line.* ## Recommended approach (from triage narrative) - pass1: `keep-open` - small, contained fix. + +- | pass2 labels: `bug`, `good-first-issue` **Notes**: Could be fixed at the task layer (skip with logger.info when path missing) or at the flow layer (`when:` guard on the deploy_unmanaged steps). Task-layer is more defensible because the same task may be referenced from custom flows. --- ## Size & risk | Field | Value | + | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_2826.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #2826:`). | diff --git a/docs/triage/v5/fix-sketches/issue_2951.md b/docs/triage/v5/fix-sketches/issue_2951.md new file mode 100644 index 0000000000..e3409369ac --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_2951.md @@ -0,0 +1,12 @@ +# Fix sketch - #2951: Error in task load_dataset - Standard_price_not_defined **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `bulkdata` +**Issue**: ## Bug No special handling exists in the loader to sequence Standard-Price-Book PricebookEntries before custom-pricebook PricebookEntries within a single mapping step. Within one Bulk job, records are processed in parallel within batches and Salesforce throws `STANDARD_PRICE_NOT_DEFINED` when a custom price is created before the matching standard price. Mitigation already in place for the `extract_datase... ## Target `cumulusci/tasks/bulkdata/extract_dataset_utils/hardcoded_default_declarations.py`lines 14-18 ## Recommended approach (from triage narrative) - pass1:`keep-open` - bug is latent. Fix options: (a) loader auto-splits PricebookEntry into two implicit batches (standard pricebook first); (b) document the requirement and validate at parse time that PricebookEntry steps are not "mixed." + +- | pass2 labels: `bug, bulkdata, load_dataset, pricebook, documentation` --- ## Size & risk | Field | Value | + | ---------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_2951.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #2951:`). | diff --git a/docs/triage/v5/fix-sketches/issue_2979.md b/docs/triage/v5/fix-sketches/issue_2979.md new file mode 100644 index 0000000000..ccf8f56107 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_2979.md @@ -0,0 +1,12 @@ +# Fix sketch - #2979: deploy task should deploy from default entry in packageDirectories **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `packaging` +**Issue**: ## Bug - `cumulusci/cumulusci.yml:227-229` - the `deploy` task still hard-codes `path: src` for `cumulusci.tasks.salesforce.Deploy`. - `cumulusci/core/config/project_config.py:517-525` - `default_package_path` correctly reads `packageDirectories[*].default` from `sfdx-project.json` when `project__source_format == "sfdx"`. - The only consumer of `default_package_path` is `cumulusci/tasks/create_pack... ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open`- feature still missing; davisagli's 2021 design comment (3-tier fallback`path`-> sfdx default ->`src`) remains the natural plan. + +- | pass2 labels: `severity:low,area:packaging,area:sfdx,state:needs-design` --- ## Size & risk | Field | Value | + | ------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_2979.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #2979:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3015.md b/docs/triage/v5/fix-sketches/issue_3015.md new file mode 100644 index 0000000000..0327a2449f --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3015.md @@ -0,0 +1,12 @@ +# Fix sketch - #3015: Remove imported dx org from cci list without deleting actual scratch **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `cli` +**Issue**: ## Bug No `--keep-org` flag. davisagli's manual workaround (delete `~/.cumulusci//.org` directly) still applies. ## Target `cumulusci/cli/org.py` lines 519-543 ## Recommended approach (from triage narrative) - pass1: `closed:stale-24mo` - 4yr; . + +- | pass2 labels: `enhancement,stale` --- ## Size & risk | Field | Value | + | ---------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3015.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3015:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3024.md b/docs/triage/v5/fix-sketches/issue_3024.md new file mode 100644 index 0000000000..4faaf041d5 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3024.md @@ -0,0 +1,12 @@ +# Fix sketch - #3024: Order of flow groups in `cumulusci/cumulusci.yml` **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `cli` +**Issue**: ## Bug Sequence of unique `group:` values in `cumulusci/cumulusci.yml` (first appearance): `Continuous Integration` is buried near the bottom; `Org Setup` (the user's preferred name) does not exist (uses `Setup`); ordering does not match the user's request. ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `closed:stale-24mo` - 4yr cosmetic VS Code extension request; the true fix is sorting at the consumer (the extension) rather than rearranging the canonical YAML. + +- | pass2 labels: `enhancement,stale` --- ## Size & risk | Field | Value | + | ---------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3024.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3024:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3137.md b/docs/triage/v5/fix-sketches/issue_3137.md new file mode 100644 index 0000000000..88aeb95421 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3137.md @@ -0,0 +1,12 @@ +# Fix sketch - #3137: cci task run update_package_xml and Salesforce Case Custom Object **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `metadata-etl` +**Issue**: ## Bug - `cumulusci/tasks/metadata/package.py:451-458` skips standard objects. - `cumulusci/tasks/metadata/package.py:562-584` `UpdatePackageXml.task_options` has only `path`, `output`, `package_name`, `managed`, `delete`, ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: keep-open - a real product gap remains; mark for design discussion. + +- | pass2 labels: `severity:low,area:metadata-etl,type:enhancement,state:needs-design` --- ## Size & risk | Field | Value | + | ----------------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3137.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3137:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3161.md b/docs/triage/v5/fix-sketches/issue_3161.md new file mode 100644 index 0000000000..daa356f777 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3161.md @@ -0,0 +1,12 @@ +# Fix sketch - #3161: Ability to Hide Option Values When Using Task Options **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `cli` +**Issue**: ## Bug A masking infrastructure was added (task-option metadata can opt in via `sensitive: True`), but: 1. The Robot `vars` option is not marked sensitive (`cumulusci/tasks/robotframework/robotframework.py:54-56`). 2. There's no CLI/Robot-side flag for the user to mark an ad-hoc `-o` value as sensitive. ## Target `cumulusci/core/flowrunner.py` lines 300-320 ## Recommended approach (from triage narrative) - pass1: `closed:stale-24mo` - 4yr; partial fix in place. + +- | pass2 labels: `enhancement,stale,partially-implemented` --- ## Size & risk | Field | Value | + | -------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3161.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3161:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3165.md b/docs/triage/v5/fix-sketches/issue_3165.md new file mode 100644 index 0000000000..881d08fbc0 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3165.md @@ -0,0 +1,12 @@ +# Fix sketch - #3165: Update Admin Profile task fails when specifying record types without custom package.xml **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `metadata-etl` +**Issue**: ## Bug Update Admin Profile task fails when specifying record types without custom package.xml ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open` - single-line refactor. + +- | pass2 labels: `bug` **Notes**: Smallest fix at update_profile.py:137 - always call `self._expand_package_xml_objects(package_xml)` regardless of `include_packaged_objects`, and only call the broader `self._expand_package_xml` (which does the Tooling API query) when `include_packaged_objects=True`. `_expand_package_xml_objects` itself only walks the user-supplied options, no API call. --- ## Size & risk | Field | Value | + | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3165.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3165:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3307.md b/docs/triage/v5/fix-sketches/issue_3307.md new file mode 100644 index 0000000000..98269f5ec9 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3307.md @@ -0,0 +1,12 @@ +# Fix sketch - #3307: Project Template Support for `cci project init` **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `cli` +**Issue**: ## Bug `rg "template" cumulusci/cli/project.py` only finds references to internal Jinja templates rendered from `cumulusci/files/templates/project` (lines 220-329). No `--template` CLI option exists. `project_init` (line 41) takes no template-source argument. ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `closed:stale-24mo` - 4yr; explicitly described by the requester as "low priority / nice to have". + +- | pass2 labels: `enhancement,stale` --- ## Size & risk | Field | Value | + | ---------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3307.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3307:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3331.md b/docs/triage/v5/fix-sketches/issue_3331.md new file mode 100644 index 0000000000..2517b0d57e --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3331.md @@ -0,0 +1,12 @@ +# Fix sketch - #3331: Task update_package_xml does not write correct package.xml for AssignmentRules **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `metadata-etl` +**Issue**: ## Bug - `cumulusci/tasks/metadata/metadata_map.yml:45-48` maps the `assignmentRules` folder to `type: AssignmentRule` (singular). MDAPI expects the plural type name (cf. `autoResponseRules:` at lines 60-63 ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: keep-open - one-line YAML fix; reporter offered a PR. + +- | pass2 labels: `severity:medium,area:metadata-etl,type:bug,good-first-issue` --- ## Size & risk | Field | Value | + | ---------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3331.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3331:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3349.md b/docs/triage/v5/fix-sketches/issue_3349.md new file mode 100644 index 0000000000..324069e48e --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3349.md @@ -0,0 +1,10 @@ +# Fix sketch - #3349: Make generated dataset recordType tables unique based on table instead of sf_object **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `bulkdata` +**Issue**: ## Bug Make generated dataset recordType tables unique based on table instead of sf*object ## Target \_See narrative for target file:line.* ## Recommended approach (from triage narrative) _See narrative for recommended action._ ## Size & risk | Field | Value | +| ------------------------------------ | ---------------------- | +| Size estimate | _TBD by fix-PR author_ | +| Risk | _TBD by fix-PR author_ | +| Touches `cumulusci/robotframework/*` | _TBD_ | +| Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | +| Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3349.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3349:`). diff --git a/docs/triage/v5/fix-sketches/issue_3353.md b/docs/triage/v5/fix-sketches/issue_3353.md new file mode 100644 index 0000000000..efdedee9d2 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3353.md @@ -0,0 +1,10 @@ +# Fix sketch - #3353: Enable Snowfakery task to use recipes from other repositories **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `bulkdata` +**Issue**: ## Bug Enable Snowfakery task to use recipes from other repositories ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) _See narrative for recommended action._ ## Size & risk | Field | Value | +| ------------------------------------ | ---------------------- | +| Size estimate | _TBD by fix-PR author_ | +| Risk | _TBD by fix-PR author_ | +| Touches `cumulusci/robotframework/*` | _TBD_ | +| Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | +| Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3353.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3353:`). diff --git a/docs/triage/v5/fix-sketches/issue_3407.md b/docs/triage/v5/fix-sketches/issue_3407.md new file mode 100644 index 0000000000..327839ee22 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3407.md @@ -0,0 +1,16 @@ +# Fix sketch - #3407: `set_service(service_config)` annotation lies **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `keychain` +**Issue**: ## Bug - `cumulusci/core/keychain/base_project_keychain.py` lines 202-209 declare `service_config: ServiceConfig` on `set_service`. - `cumulusci/core/keychain/encrypted_file_project_keychain.py` line 717 ## Target `cumulusci/core/keychain/base_project_keychain.py:202-219`. Also add the same widened annotation on the abstract `_set_service` (line 360) and propagate to `EncryptedFileProjectKeychain._set_service` (line 583). ## Recommended approach (from triage narrative) - **Approach**: Widen the annotation. `set_service`'s `service_config` parameter should be `Union[ServiceConfig, str]` (or `ServiceConfig | str | +bytes`) and the docstring updated to say "raw encrypted payload when `config_encrypted=True`". The deeper refactor - splitting the API into `set_service` (validated `ServiceConfig`) and `set_encrypted_service` (raw blob) - is cleaner but breaks the public API and is out of scope for this triage pass. + +- **Target**: `cumulusci/core/keychain/base_project_keychain.py:202-219`. Also add the same widened annotation on the abstract `_set_service` (line 360) and propagate to `EncryptedFileProjectKeychain._set_service` (line 583). +- **Size**: ~10 LOC plus a typing-import bump. Single-file change feasible. +- **Risk**: very low. Pure type-hint widening; no runtime behaviour change. Downstream callers that already pass `ServiceConfig` keep working; pyright/mypy users gain an accurate hint. +- | **API break**: no. Widening a parameter type is non-breaking for callers. ## Size & risk | Field | Value | + | ---------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3407.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3407:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3429.md b/docs/triage/v5/fix-sketches/issue_3429.md new file mode 100644 index 0000000000..9f4723dad3 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3429.md @@ -0,0 +1,12 @@ +# Fix sketch - #3429: Support overriding `cumulusci.yml` to be used for configuration **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `packaging` +**Issue**: ## Bug - `cumulusci/core/config/project_config.py:82` - `config_filename = "cumulusci.yml"` is hardcoded. - `cumulusci/core/config/project_config.py:118-184` - only an `additional_yaml` kwarg (programmatic, used by MetaCI) is supported; no env var or CLI plumbing. - `git merge-base --is-ancestor 9d650ace2 HEAD` returns non-zero - PR #3969 (commits prefixed `feat(cli): add resolve_extra_yaml helper ... ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open`- feature is genuinely actionable on v4.10.0; auto-close once #3969 merges via`closed:fixed-by-pr-#3969`. + +- | pass2 labels: `severity:medium,area:packaging,area:cli,state:in-progress` --- ## Size & risk | Field | Value | + | -------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3429.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3429:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3440.md b/docs/triage/v5/fix-sketches/issue_3440.md new file mode 100644 index 0000000000..60e7faacab --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3440.md @@ -0,0 +1,12 @@ +# Fix sketch - #3440: Enhance `default_package_path` to serve multi-package projects better **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `packaging` +**Issue**: ## Bug - `cumulusci/core/config/project_config.py:517-525` - implementation is the simple "first packageDirectory with `default: true`" pattern; falls back to `force-app`, then `src`. No name-based lookup, no multi-package warning, no hard fail when both are missing. ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open` - same multi-package umbrella as #2979 / #3429; would best be solved together. + +- | pass2 labels: `severity:low,area:packaging,area:sfdx,area:multi-package` --- ## Size & risk | Field | Value | + | ------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3440.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3440:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3441.md b/docs/triage/v5/fix-sketches/issue_3441.md new file mode 100644 index 0000000000..719b5f5cdc --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3441.md @@ -0,0 +1,12 @@ +# Fix sketch - #3441: `cci task run create_package_version` should allow `version_base: default` **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `packaging` +**Issue**: ## Bug - `cumulusci/tasks/create_package_version.py:63-112` - `version_base: Optional[str]`; documented values are `None`, a literal version number, or `latest_github_release`. - `cumulusci/tasks/create_package_version.py:529-563` - `_get_base_version_number` only branches on `None` (default) and `"latest_github_release"`; any other string is parsed as a literal version. There is no `"default"`/`"hig... ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open`- could be solved minimally with a`default`/`highest`sentinel in`\_get_base_version_number`, or generalized as a CCI null-override feature. + +- | pass2 labels: `severity:low,area:packaging,area:flow-overrides,area:cli` --- ## Size & risk | Field | Value | + | ------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3441.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3441:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3446.md b/docs/triage/v5/fix-sketches/issue_3446.md new file mode 100644 index 0000000000..103a367557 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3446.md @@ -0,0 +1,12 @@ +# Fix sketch - #3446: CCI task push_qa crashes for Unlocked package with no namespace **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `packaging` +**Issue**: ## Bug - `cumulusci/tasks/push/tasks.py:33` - `version_parts = version.split(".")` (no None-guard above). - `cumulusci/tasks/push/tasks.py:283-297` - `_run_task` does not validate `version` before calling `_get_version`. - Test: `_(repro evidence; see narrative)_` passes on v4.10.0. ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open` - real bug, simple fix. + +- | pass2 labels: `bug`, `good-first-issue` --- ## Size & risk | Field | Value | + | ---------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3446.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3446:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3464.md b/docs/triage/v5/fix-sketches/issue_3464.md new file mode 100644 index 0000000000..c7f16794d7 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3464.md @@ -0,0 +1,12 @@ +# Fix sketch - #3464: Concise project-config documentation **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `docs` +**Issue**: ## Bug Concise project-config documentation ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) Two-step path: 1. **Tactical**: expand the `### Project Configurations` heading at `docs/config.md:673` into a real reference subsection that lists every `project:` top-level key with a one-sentence description and one example value. Cross-link to `docs/dev.md` for in-depth treatment of `dependencies` / `dependency_resolutions` / `dependency_pins` so we are not duplicating the longer narratives, just providing a central index. This alone closes the issue per the user's literal ask. 2. **Strategic** (separate, follow-up PR): backfill `Field(description=...)` on every Pydantic attribute in `cumulusci_yml.py` and add a Sphinx directive (or a `conf.py` autodoc hook) that emits the reference table directly from the model at docs-build time. This keeps the reference and the schema in lockstep - the same mechanism powers the JSON schema (`cumulusci/schema/cumulusci.jsonschema.json`), so the plumbing is straightforward. Step 1 is a single-day effort; step 2 is multi-day but pays back every +time a new field is added to `cumulusci.yml`. Recommend keep-open until +at least step 1 ships. Worktree: the triage worktree @ the triage worktree based on `origin/dev` (`1925a3083`). ## Size & risk | Field | Value | +| ------------------------------------ | ---------------------- | +| Size estimate | _TBD by fix-PR author_ | +| Risk | _TBD by fix-PR author_ | +| Touches `cumulusci/robotframework/*` | _TBD_ | +| Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | +| Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3464.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3464:`). diff --git a/docs/triage/v5/fix-sketches/issue_3470.md b/docs/triage/v5/fix-sketches/issue_3470.md new file mode 100644 index 0000000000..20009be579 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3470.md @@ -0,0 +1,12 @@ +# Fix sketch - #3470: Rename `ci_master` to `ci_main` (or alias) **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `cli` +**Issue**: ## Bug `rg "ci_main"` returns no matches. davidmreed's 2022 reply indicated this requires flow-aliasing infrastructure first. ## Target `cumulusci/cumulusci.yml` lines 823-835 ## Recommended approach (from triage narrative) - pass1: `closed:stale-24mo` - 4yr stale; preserve as `closed:stale-24mo` rather than dismiss; the inclusive-language motivation is real and could be revisited if flow aliasing lands. + +- | pass2 labels: `enhancement,stale,inclusive-language` ## Size & risk | Field | Value | + | ------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3470.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3470:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3471.md b/docs/triage/v5/fix-sketches/issue_3471.md new file mode 100644 index 0000000000..900e39436e --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3471.md @@ -0,0 +1,12 @@ +# Fix sketch - #3471: `Merged 0 commits into branch:` message displays when a non-Source Code change is **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `ci-integration` +**Issue**: ## Bug The reported log message originates from `_merge`: The log line at 251 reports `compare.behind_by` from github3's CompareCommits. `behind_by` is computed from the GitHub compare-commits endpoint and reflects how many commits the destination branch is behind the merged commit _as of the comparison's chosen merge-base_; for "effectively no-op" content merges (e.g. README/test.txt scenarios where dow... ## Target `cumulusci/tasks/github/merge.py` lines 241-262 ## Recommended approach (from triage narrative) - pass1: `keep-open` - small, well-localized fix (replace `compare.behind_by` with `len(list(compare.commits))` or report the SHA returned from `self.repo.merge(...)`); add a test covering the `behind_by=0` case. + +- | pass2 labels: `bug, github, merge, low-priority` --- ## Size & risk | Field | Value | + | ------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3471.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3471:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3485.md b/docs/triage/v5/fix-sketches/issue_3485.md new file mode 100644 index 0000000000..74f426e63f --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3485.md @@ -0,0 +1,12 @@ +# Fix sketch - #3485: "cci task run run_tests" generates incorrect test_results.xml format **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `cli` +**Issue**: ## Bug - `cumulusci/tasks/apex/testrunner.py:803-834` - `_write_output` opens `junit_output` and writes `'\n'` with no `` declaration and no enclosing `` element. - The closing tag at line 834 is ``. This exactly matches the malformed XML the reporter showed. - `junit_output` defaults to `test_results.xml` (line 201-203), unchanged. ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open` - small mechanical fix, still affects users producing JUnit reports for CI. + +- | pass2 labels: `bug, area:apex, good-first-issue` ## Size & risk | Field | Value | + | --------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3485.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3485:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3492.md b/docs/triage/v5/fix-sketches/issue_3492.md new file mode 100644 index 0000000000..19afc36583 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3492.md @@ -0,0 +1,12 @@ +# Fix sketch - #3492: Enhance the "-o" option of "cci flow run" to accept "project\_\_custom" attribute values **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `cli` +**Issue**: ## Bug - `cumulusci/cli/flow.py:152-162` - parses `-o` pairs by splitting key on `"__"` and unpacking into exactly two parts (`task_name, option_name = key.split("__")`). - A user passing `-o project__custom__myattr value` would actually error with "too many values to unpack" because the split yields three elements; even worded as `-o project__custom value` there is no codepath that writes into `proj... ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open` - legitimate usability gap for matrix-style CI. + +- | pass2 labels: `enhancement, area:cli` ## Size & risk | Field | Value | + | ---------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3492.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3492:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3506.md b/docs/triage/v5/fix-sketches/issue_3506.md new file mode 100644 index 0000000000..71706507bb --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3506.md @@ -0,0 +1,12 @@ +# Fix sketch - #3506: when clause support for flow steps which call other flows **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `cli` +**Issue**: ## Bug - `cumulusci/core/flowrunner.py:660-672` - when the step has a `task:` key, the StepSpec is built with `when=step_config.get("when")`. - `cumulusci/core/flowrunner.py:674-697` - the `flow:` branch recurses via `_visit_step(...)` passing only `parent_options`, `parent_ui_options`, and `from_flow`; it never reads or propagates `step_config.get("when")`. Any `when:` clause attached to a flow-call... ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open` - confirmed silent-failure foot-gun the user reported. + +- | pass2 labels: `enhancement, area:flows` ## Size & risk | Field | Value | + | ------------------------------------------------------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3506.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3506:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3518.md b/docs/triage/v5/fix-sketches/issue_3518.md new file mode 100644 index 0000000000..9c9171fac2 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3518.md @@ -0,0 +1,12 @@ +# Fix sketch - #3518: Task add_picklist_entries always sets a default value for record types **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `metadata-etl` +**Issue**: ## Bug - `cumulusci/tasks/metadata_etl/picklists.py:177` missing `()` after `.lower`. - `cumulusci/tasks/metadata_etl/picklists.py:214-221` unconditionally sets defaults whenever `default` is truthy. ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: keep-open - small targeted fix. + +- | pass2 labels: `severity:high,area:metadata-etl,type:bug` --- ## Size & risk | Field | Value | + | --------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3518.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3518:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3541.md b/docs/triage/v5/fix-sketches/issue_3541.md new file mode 100644 index 0000000000..6a5463f00b --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3541.md @@ -0,0 +1,13 @@ +# Fix sketch - #3541: `None__dev` SFDX alias **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `keychain` +**Issue**: ## Bug - `cumulusci/core/keychain/base_project_keychain.py` lines 77-79: - When `project_config.project__name` is None (cumulusci.yml without a `project.name`, or partially-loaded project*config), this f-string ## Target `cumulusci/core/keychain/base_project_keychain.py:77-79`. Migration helper recommended to scan the keychain for `None\_\_*`aliases and rewrite them once a real`project.name`is available. ## Recommended approach (from triage narrative) - **Approach**: Guard the alias construction. Two reasonable options: (1) raise`CumulusCIException("Cannot build sfdx\*alias: project.name is not set in cumulusci.yml")`- surfaces the misconfiguration at the earliest possible moment. (2) Fall back to`f"{org*name}"`(no prefix) when`project**name`is None, with a`logger.warning`. Less disruptive for existing setups. Option 2 is the safer change; option 1 is the more correct one. Pick based on willingness to break first-run UX. - **Target**: `cumulusci/core/keychain/base_project_keychain.py:77-79`. Migration helper recommended to scan the keychain for `None\***`aliases and rewrite them once a real`project.name`is available. - **Size**: ~10 LOC for the guard, ~30 LOC including a one-shot migration pass on keychain load. - **Risk**: medium. Existing keychains in the wild may already contain`None\_\_dev`rows; option 2 silently writes a new alias on next run, option 1 forces the user to fix cumulusci.yml. Document either way in CHANGELOG. - **API break**: no public API change. The`OrgConfig.sfdx_alias`field shape is preserved; only its derivation logic shifts. - **Bonus**: remove the`cannot-reproduce`label - the repro here is deterministic. Worktree: the triage worktree @`1925a3083`(off`origin/dev`). +Issues processed: 3/3 (#773, #2500, #3464). +Verdict tally: 3 REPRODUCED-on-dev (all pure doc-gaps with code-level +testable assertions). ## Size & risk | Field | Value | +| ------------------------------------ | ---------------------- | +| Size estimate | \_TBD by fix-PR author* | +| Risk | \_TBD by fix-PR author* | +| Touches `cumulusci/robotframework/\*` | \_TBD* | +| Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | +| Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3541.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3541:`). diff --git a/docs/triage/v5/fix-sketches/issue_3543.md b/docs/triage/v5/fix-sketches/issue_3543.md new file mode 100644 index 0000000000..f3283dd3e8 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3543.md @@ -0,0 +1,12 @@ +# Fix sketch - #3543: New Option `load_sfdx_project_paths` for dx_convert_from Task **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `metadata-etl` +**Issue**: ## Bug - `cumulusci/tasks/dx_convert_from.py:7-14` exposes only `extra` and `src_dir`; no `load_sfdx_project_paths` / `resolve_sfdx_package_dirs`. - The grep hits in `project_config.py` and `cli/project.py` are unrelated ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: keep-open - feature still unimplemented; reporter offered a draft PR. + +- | pass2 labels: `severity:low,area:metadata-etl,type:enhancement` --- ## Size & risk | Field | Value | + | ---------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3543.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3543:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3549.md b/docs/triage/v5/fix-sketches/issue_3549.md new file mode 100644 index 0000000000..d85161c780 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3549.md @@ -0,0 +1,12 @@ +# Fix sketch - #3549: Deploy to Salesforce does not create a test output **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `cli` +**Issue**: ## Bug - `cumulusci/tasks/salesforce/Deploy.py:49-94` - exposes `test_level` and `specified_tests` options and validates them. - `cumulusci/tasks/salesforce/Deploy.py:150-154` - passes them through to the metadata API call but never captures `runTestResult`/`runTestsResult` from the response. - `rg "junit_output|test_results"` against `cumulusci/tasks/salesforce/Deploy.py` and `cumulusci/salesforce... ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open` - natural feature; tracks #3564. + +- | pass2 labels: `enhancement, area:metadata-deploy` ## Size & risk | Field | Value | + | ---------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3549.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3549:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3570.md b/docs/triage/v5/fix-sketches/issue_3570.md new file mode 100644 index 0000000000..c7f631acfa --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3570.md @@ -0,0 +1,12 @@ +# Fix sketch - #3570: Feature Request: Flow "finally" or "error" path **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `cli` +**Issue**: ## Bug - `cumulusci/core/flowrunner.py` - only `ignore_failure` (mapped to `StepSpec.allow_failure`, line 122/144) and the `finally:` Python clause inside `flow.run()` (line 500) handle failures. There is no flow-step type for `finally:` / `on_error:` / `cleanup:` / `always_run`. `rg "finally|on_error|on_failure|always_run"` confirms. - `_run_step` (line 503-536) re-raises on `result.exception` if no... ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open` - design-level feature, but problem is real (rollback, notify on partial failure). + +- | pass2 labels: `enhancement, area:flows` ## Size & risk | Field | Value | + | ------------------------------------------------------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3570.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3570:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3585.md b/docs/triage/v5/fix-sketches/issue_3585.md new file mode 100644 index 0000000000..e15251d684 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3585.md @@ -0,0 +1,12 @@ +# Fix sketch - #3585: Error Occurs when Using `update_package_xml` on object with `xsi:nil="true"` **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `metadata-etl` +**Issue**: ## Bug - `cumulusci/tasks/metadata/package.py:115-130` - when a folder has objects it instantiates the registered parser; for `objects/` the parser uses the metadata tree which is strict about namespaces. ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: keep-open - needs either a namespace-shim before parsing or pre-stripping of `xsi:nil` attributes. + +- | pass2 labels: `severity:medium,area:metadata-etl,type:bug,sfdx-compat` --- ## Size & risk | Field | Value | + | ----------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3585.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3585:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3593.md b/docs/triage/v5/fix-sketches/issue_3593.md new file mode 100644 index 0000000000..d292daa43a --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3593.md @@ -0,0 +1,12 @@ +# Fix sketch - #3593: `dx` task doesn't work for some commands like `project convert source` **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `packaging` +**Issue**: ## Bug - `cumulusci/tasks/sfdx.py:46-51` - `SFDXOrgTask._get_command` unconditionally appends `" -o {username}"` for any `ScratchOrgConfig`, regardless of whether the underlying sf subcommand accepts a target-org flag. - Repro test FAILS with the resulting command: `sf project convert source -r src -d force-app -o test@example.com` - the same shape that the issue reporter said sf cli rejects. - Not... ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open` - needs an opt-out option (e.g. `pass_org: False` or a `no_org_command` whitelist). Verifying actual sf cli rejection of `-o` for `project convert source` would need an org/sf cli; the CCI side of the bug is unchanged. + +- | pass2 labels: `severity:medium,area:packaging,area:sfdx,area:dx-task,state:needs-design` --- ## Size & risk | Field | Value | + | ----------------------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3593.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3593:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3602.md b/docs/triage/v5/fix-sketches/issue_3602.md new file mode 100644 index 0000000000..14afbf8826 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3602.md @@ -0,0 +1,18 @@ +# Fix sketch - #3602: Need Chrome/Firefox options(browser options/capabilities) in 'Open Test Browser' Keyword **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `robotframework` +**Issue**: ## Bug - `cumulusci/robotframework/SalesforcePlaywright.py:60-62` - `def open_test_browser(self, size=None, useralias=None, wait=True, record_video=None):` has no `browser_options`/`extra_options`/`**kwargs` hook. - `cumulusci/robotframework/Salesforce.robot:103` - Selenium keyword signature `[Arguments] ${size}=... ${alias}=${NONE} ${wait}=True ${useralias}=${NONE}` - same gap. - `cumulusci/ro... ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - Approach (Playwright): add a `browser_options: dict | None = None`kwarg; pass through to`new_browser`(browser-launch options) and a separate`context_options`dict merged into the`new_context` call. + +- Approach (Selenium): expose a `${EXTRA_CHROME_OPTIONS}` variable / list argument honoured by `Get Chrome Options`; alternatively add an `extra_options` argument to `Open Test Browser` and pipe through to `Create Webdriver With Retry`. +- Target: `cumulusci/robotframework/SalesforcePlaywright.py:60-117`; `cumulusci/robotframework/Salesforce.robot:77-168`. +- Size: medium (~30-60 lines across both implementations + tests + docs). +- Risk: low - additive parameter with safe default. +- API break: no (default `None` preserves current behaviour). **Recommended action**: - pass1: `keep-open` - reasonable, scoped feature ask; age <36mo; no PR yet; tractable medium-sized contribution. +- pass2 labels: `enhancement, robotframework, playwright, good-second-issue` +- | triage test: `cumulusci/tests/triage/test_issue_3602.py` Working tree: the triage worktree on the triage worktree off `origin/dev` at `1925a3083`. Triaged 3 issues (#3910, #3306, #710). All three remain actionable on dev: #3910 has an open fix PR; #3306 and #710 are never-implemented enhancements. ## Size & risk | Field | Value | + | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3602.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3602:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3603.md b/docs/triage/v5/fix-sketches/issue_3603.md new file mode 100644 index 0000000000..6b80bd854b --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3603.md @@ -0,0 +1,10 @@ +# Fix sketch - #3603: Any issue with git results in the unhelpful "404 not found" error **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `dependencies` +**Issue**: ## Bug Any issue with git results in the unhelpful "404 not found" error ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) _See narrative for recommended action._ ## Size & risk | Field | Value | +| ------------------------------------ | ---------------------- | +| Size estimate | _TBD by fix-PR author_ | +| Risk | _TBD by fix-PR author_ | +| Touches `cumulusci/robotframework/*` | _TBD_ | +| Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | +| Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3603.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3603:`). diff --git a/docs/triage/v5/fix-sketches/issue_3604.md b/docs/triage/v5/fix-sketches/issue_3604.md new file mode 100644 index 0000000000..19a3ae1a92 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3604.md @@ -0,0 +1,10 @@ +# Fix sketch - #3604: Task request: Update sfdx-project.json dependencies based off of computed cumulusci dependencies **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `dependencies` +**Issue**: ## Bug Task request: Update sfdx-project.json dependencies based off of computed cumulusci dependencies ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) _See narrative for recommended action._ ## Size & risk | Field | Value | +| ------------------------------------ | ---------------------- | +| Size estimate | _TBD by fix-PR author_ | +| Risk | _TBD by fix-PR author_ | +| Touches `cumulusci/robotframework/*` | _TBD_ | +| Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | +| Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3604.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3604:`). diff --git a/docs/triage/v5/fix-sketches/issue_3613.md b/docs/triage/v5/fix-sketches/issue_3613.md new file mode 100644 index 0000000000..e243cad986 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3613.md @@ -0,0 +1,14 @@ +# Fix sketch - #3613: AddFieldsToPageLayout - "Cannot find metadata file" **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `metadata-etl` +**Issue**: ## Bug AddFieldsToPageLayout - "Cannot find metadata file" ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `improve-error-message` - keep open as a UX bug. + +- pass2 labels: `bug`, `good-first-issue` **Notes**: Two complementary improvements would help: 1. In `_transform` (base.py:332), include the actual list of files retrieved into `source_metadata_dir` in the error message so the user can spot the naming mismatch. + +2. | In `AddFieldsToPageLayout._init_options`, warn when an api_name does not contain `-` (Layout API names always do). The user was on Windows in 2023 - note that the user might also have been hitting a backslash path issue, but the underlying class of bug is the same: api_name format mismatch. --- ## Size & risk | Field | Value | + | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3613.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3613:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3618.md b/docs/triage/v5/fix-sketches/issue_3618.md new file mode 100644 index 0000000000..1d0288f33b --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3618.md @@ -0,0 +1,12 @@ +# Fix sketch - #3618: Allow for list when deleting/removing CumulusCI orgs **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `cli` +**Issue**: ## Bug - `cumulusci/cli/org.py:519-545` - `org_remove` decorated with `@orgname_option_or_argument(required=True)`, takes a single `org_name`. - `cumulusci/cli/org.py:605-625` - `org_scratch_delete` same pattern, single `org_name`. - No `nargs=-1`, no comma-split helper; passing `org1,org2` would be treated as a single literal alias and fail keychain lookup. ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open` - legitimately useful for cleanup workflows; small implementation surface. + +- | pass2 labels: `enhancement, area:cli, good-first-issue` ## Size & risk | Field | Value | + | ---------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3618.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3618:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3619.md b/docs/triage/v5/fix-sketches/issue_3619.md new file mode 100644 index 0000000000..807f9e59f0 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3619.md @@ -0,0 +1,10 @@ +# Fix sketch - #3619: Dependency_pins does not honor passwords **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `dependencies` +**Issue**: ## Bug Dependency*pins does not honor passwords ## Target \_See narrative for target file:line.* ## Recommended approach (from triage narrative) _See narrative for recommended action._ ## Size & risk | Field | Value | +| ------------------------------------ | ---------------------- | +| Size estimate | _TBD by fix-PR author_ | +| Risk | _TBD by fix-PR author_ | +| Touches `cumulusci/robotframework/*` | _TBD_ | +| Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | +| Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3619.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3619:`). diff --git a/docs/triage/v5/fix-sketches/issue_3649.md b/docs/triage/v5/fix-sketches/issue_3649.md new file mode 100644 index 0000000000..4fd7340806 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3649.md @@ -0,0 +1,10 @@ +# Fix sketch - #3649: Support serial loads with update_data task **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `bulkdata` +**Issue**: ## Bug Support serial loads with update*data task ## Target \_See narrative for target file:line.* ## Recommended approach (from triage narrative) _See narrative for recommended action._ ## Size & risk | Field | Value | +| ------------------------------------ | ---------------------- | +| Size estimate | _TBD by fix-PR author_ | +| Risk | _TBD by fix-PR author_ | +| Touches `cumulusci/robotframework/*` | _TBD_ | +| Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | +| Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3649.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3649:`). diff --git a/docs/triage/v5/fix-sketches/issue_3663.md b/docs/triage/v5/fix-sketches/issue_3663.md new file mode 100644 index 0000000000..cfc3f56abe --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3663.md @@ -0,0 +1,12 @@ +# Fix sketch - #3663: When clause | Ability to pass in prior task response values **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `cli` +**Issue**: ## Bug - `cumulusci/core/flowrunner.py:510-516` - the `when` Jinja context is hardcoded to `{"project_config": ..., "org_config": ...}`. Prior step results (`self.results`) are not exposed. - The `^^task.return_value` resolver lives elsewhere (option resolution path) and is not threaded into the `when` evaluator. ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open` - natural extension of `when`; complements #3506. + +- | pass2 labels: `enhancement, area:flows` ## Size & risk | Field | Value | + | ------------------------------------------------------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3663.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3663:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3692.md b/docs/triage/v5/fix-sketches/issue_3692.md new file mode 100644 index 0000000000..f757cad90e --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3692.md @@ -0,0 +1,12 @@ +# Fix sketch - #3692: No parser configuration found for subdirectory digitalExperiences **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `metadata-etl` +**Issue**: ## Bug - `cumulusci/tasks/metadata/metadata_map.yml` has no `digitalExperiences` key. - `cumulusci/tasks/metadata/package.py:115-118` raises ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: keep-open - add `digitalExperiences` (and likely `digitalExperienceConfigs`) entries to `metadata_map.yml` with appropriate parser classes (probably a bundle parser). + +- | pass2 labels: `severity:medium,area:metadata-etl,type:bug` --- ## Size & risk | Field | Value | + | ----------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3692.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3692:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3699.md b/docs/triage/v5/fix-sketches/issue_3699.md new file mode 100644 index 0000000000..a3a482103e --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3699.md @@ -0,0 +1,10 @@ +# Fix sketch - #3699: Sort of the data during extraction **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `bulkdata` +**Issue**: ## Bug Sort of the data during extraction ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) _See narrative for recommended action._ ## Size & risk | Field | Value | +| ------------------------------------ | ---------------------- | +| Size estimate | _TBD by fix-PR author_ | +| Risk | _TBD by fix-PR author_ | +| Touches `cumulusci/robotframework/*` | _TBD_ | +| Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | +| Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3699.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3699:`). diff --git a/docs/triage/v5/fix-sketches/issue_3700.md b/docs/triage/v5/fix-sketches/issue_3700.md new file mode 100644 index 0000000000..5d9f3639fc --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3700.md @@ -0,0 +1,10 @@ +# Fix sketch - #3700: Trying to do an upsert on a master-detail child object gets an error around permission **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `bulkdata` +**Issue**: ## Bug Trying to do an upsert on a master-detail child object gets an error around permission ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) _See narrative for recommended action._ ## Size & risk | Field | Value | +| ------------------------------------ | ---------------------- | +| Size estimate | _TBD by fix-PR author_ | +| Risk | _TBD by fix-PR author_ | +| Touches `cumulusci/robotframework/*` | _TBD_ | +| Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | +| Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3700.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3700:`). diff --git a/docs/triage/v5/fix-sketches/issue_3701.md b/docs/triage/v5/fix-sketches/issue_3701.md new file mode 100644 index 0000000000..acece4c3ff --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3701.md @@ -0,0 +1,10 @@ +# Fix sketch - #3701: set a mapping to the id instead of it being either a number or the salesforce id **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `bulkdata` +**Issue**: ## Bug set a mapping to the id instead of it being either a number or the salesforce id ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) _See narrative for recommended action._ ## Size & risk | Field | Value | +| ------------------------------------ | ---------------------- | +| Size estimate | _TBD by fix-PR author_ | +| Risk | _TBD by fix-PR author_ | +| Touches `cumulusci/robotframework/*` | _TBD_ | +| Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | +| Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3701.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3701:`). diff --git a/docs/triage/v5/fix-sketches/issue_3721.md b/docs/triage/v5/fix-sketches/issue_3721.md new file mode 100644 index 0000000000..4beda9eab4 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3721.md @@ -0,0 +1,12 @@ +# Fix sketch - #3721: `create_package_version` `version_name` default should be version number, not "Release" **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `packaging` +**Issue**: ## Bug - `cumulusci/tasks/create_package_version.py:184` - `version_name=self.options.get("version_name") or "Release"`. Default is still the literal string `"Release"`. - `cumulusci/cumulusci.yml:684-686` - `upload_production` hard-codes `name: Release`. - `cumulusci/tasks/salesforce/package_upload.py:147-154` - passes `VersionName` straight through; no jinja2/template support. ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open` - needs upstream port + design (templating? plain version number? both 1GP and 2GP?). + +- | pass2 labels: `severity:low,area:packaging,area:1gp,area:2gp` --- ## Size & risk | Field | Value | + | -------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3721.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3721:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3734.md b/docs/triage/v5/fix-sketches/issue_3734.md new file mode 100644 index 0000000000..d1b70e3c92 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3734.md @@ -0,0 +1,12 @@ +# Fix sketch - #3734: upload_production fails with FIELD_INTEGRITY_EXCEPTION when latest is Beta patch **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `packaging` +**Issue**: ## Bug - `cumulusci/tasks/salesforce/package_upload.py:80-98` - SOQL query, `ORDER BY MajorVersion DESC, MinorVersion DESC, PatchVersion DESC, ReleaseState DESC LIMIT 1`. - `cumulusci/tasks/salesforce/package_upload.py:134-137` - Beta branch sets `minor_version` to the same minor. - Test passes on v4.10.0 with mocked `_get_one_record`. ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open` - confirmed real bug; remove the stale `cannot-reproduce`/`awaiting-more-details` labels. + +- | pass2 labels: `bug` --- ## Size & risk | Field | Value | + | -------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3734.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3734:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3746.md b/docs/triage/v5/fix-sketches/issue_3746.md new file mode 100644 index 0000000000..489b006436 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3746.md @@ -0,0 +1,10 @@ +# Fix sketch - #3746: Deleted Versions used for determining next version **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `packaging` +**Issue**: ## Bug Deleted Versions used for determining next version ## Target `cumulusci/tasks/create_package_version.py` lines 529-545 ## Recommended approach (from triage narrative) _See narrative for recommended action._ ## Size & risk | Field | Value | +| ------------------------------------ | ---------------------- | +| Size estimate | _TBD by fix-PR author_ | +| Risk | _TBD by fix-PR author_ | +| Touches `cumulusci/robotframework/*` | _TBD_ | +| Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | +| Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3746.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3746:`). diff --git a/docs/triage/v5/fix-sketches/issue_3754.md b/docs/triage/v5/fix-sketches/issue_3754.md new file mode 100644 index 0000000000..b8760e3f66 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3754.md @@ -0,0 +1,12 @@ +# Fix sketch - #3754: Enable configuration for cci version update sources **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `cli` +**Issue**: ## Bug - `cumulusci/cli/utils.py:65-79` - `get_latest_final_version` hits `https://pypi.org/pypi/cumulusci/json` literally, no env-var, no kwarg. - `cumulusci/cli/utils.py:82-101` - `check_latest_version` cannot be disabled via flag/env. Workaround in the comments (touch `~/.cumulusci/cumulus_timestamp` to a far-future epoch) confirmed by inspecting the timestamp logic at lines 38-50, 86-89. ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open` - easy add (e.g. `CUMULUSCI_DISABLE_VERSION_CHECK` env), helps offline/restricted environments. + +- | pass2 labels: `enhancement, area:cli` ## Size & risk | Field | Value | + | ---------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3754.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3754:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3758.md b/docs/triage/v5/fix-sketches/issue_3758.md new file mode 100644 index 0000000000..660a3cc35b --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3758.md @@ -0,0 +1,12 @@ +# Fix sketch - #3758: Flow `push_upgrade_org` is incorrectly defined **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `packaging` +**Issue**: ## Bug - `cumulusci/cumulusci.yml:1161-1177` - final step is `flow: config_qa`. The bug report (correctly, in my view) argues this should be `config_managed` because push upgrades target managed-package orgs (UAT sandboxes), not QA scratch orgs. - Repro test FAILS with `config_qa` != `config_managed`. - Both flows currently expand to the same steps (`deploy_post`, `update_admin_profile`, `load_samp... ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open`- single-line YAML fix; great`good-first-issue` candidate. Out of scope for this triage pass per task constraints (do not fix bugs). + +- | pass2 labels: `severity:medium,area:packaging,area:flows,good-first-issue` --- ## Size & risk | Field | Value | + | --------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3758.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3758:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3768.md b/docs/triage/v5/fix-sketches/issue_3768.md new file mode 100644 index 0000000000..8db82fd23a --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3768.md @@ -0,0 +1,10 @@ +# Fix sketch - #3768: Snowfakery Batch Size and Just Once **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `bulkdata` +**Issue**: ## Bug Snowfakery Batch Size and Just Once ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) _See narrative for recommended action._ ## Size & risk | Field | Value | +| ------------------------------------ | ---------------------- | +| Size estimate | _TBD by fix-PR author_ | +| Risk | _TBD by fix-PR author_ | +| Touches `cumulusci/robotframework/*` | _TBD_ | +| Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | +| Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3768.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3768:`). diff --git a/docs/triage/v5/fix-sketches/issue_3771.md b/docs/triage/v5/fix-sketches/issue_3771.md new file mode 100644 index 0000000000..59c679a4d9 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3771.md @@ -0,0 +1,12 @@ +# Fix sketch - #3771: find_replace transforms on XPath with predicates does not work **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `metadata-etl` +**Issue**: ## Bug - `cumulusci/core/source_transforms/transforms.py:420-435` - naive predicate handling. - `git log --all --oneline --grep="3772\|3771\|XPath.*predicate"` shows ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: keep-open - the leboff PR is a viable starting point; or implement the reporter's "strip xmlns then re-add" approach for simplicity. + +- | pass2 labels: `severity:medium,area:source-transforms,type:bug,has-pr` --- ## Size & risk | Field | Value | + | ----------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3771.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3771:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3773.md b/docs/triage/v5/fix-sketches/issue_3773.md new file mode 100644 index 0000000000..09cdac28ad --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3773.md @@ -0,0 +1,12 @@ +# Fix sketch - #3773: retrieve_profile task seems to be missing some Metadata **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `metadata-etl` +**Issue**: ## Bug - `cumulusci/salesforce_api/retrieve_profile_api.py:164-195` - no `FieldPermissions` query. - Greped `FieldPermission|field_permission|fieldPermission` - only the ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: keep-open - needs additional `FieldPermissions` query plus inclusion of those parent SObjectTypes in the `CustomObject` retrieve set. + +- | pass2 labels: `severity:medium,area:retrieve-profile,type:bug` --- ## Size & risk | Field | Value | + | --------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3773.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3773:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3849.md b/docs/triage/v5/fix-sketches/issue_3849.md new file mode 100644 index 0000000000..e30424ce47 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3849.md @@ -0,0 +1,10 @@ +# Fix sketch - #3849: urllib3 v2 breaks Robot tests on a fresh pip install **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `python-modernization` +**Issue**: ## Bug urllib3 v2 breaks Robot tests on a fresh pip install ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) _See narrative for recommended action._ ## Size & risk | Field | Value | +| ------------------------------------ | ---------------------- | +| Size estimate | _TBD by fix-PR author_ | +| Risk | _TBD by fix-PR author_ | +| Touches `cumulusci/robotframework/*` | _TBD_ | +| Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | +| Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3849.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3849:`). diff --git a/docs/triage/v5/fix-sketches/issue_3873.md b/docs/triage/v5/fix-sketches/issue_3873.md new file mode 100644 index 0000000000..030f235b22 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3873.md @@ -0,0 +1,17 @@ +# Fix sketch - #3873: Standalone Robot Framework Library for Selenium-Based Salesforce Automation (Inspired by Copado QForce) **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `robotframework` +**Issue**: ## Bug - `cumulusci/robotframework/base_library.py:1-39` - `BaseLibrary` lazily resolves `cumulusci.robotframework.CumulusCI`, `cumulusci.robotframework.Salesforce`, `cumulusci.robotframework.SalesforceAPI` libraries through Robot's `BuiltIn`, which require a CumulusCI project context. - `cumulusci/robotframework/Salesforce.py:20-28` - imports `cumulusci.robotframework.locator_manager`, `faker_mixin`... ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - Approach: factor a UI-only subset out of `cumulusci.robotframework.*` into a sibling distribution (e.g. `salesforce-robot-library`) that depends only on Selenium/Playwright + the locator dictionaries; have CumulusCI's Robot task consume that subset. + +- Target: package layout change spanning `cumulusci/robotframework/` and `pyproject.toml`. +- Size: large (>100 lines, design change + new distribution). +- Risk: medium - must not break existing tasks/keywords. +- API break: no (additive new distribution). **Recommended action**: - pass1: `keep-open` - reasonable architectural request, age <24mo, no community PR yet; worth retaining as roadmap signal. +- pass2 labels: `enhancement, robotframework, scope-large` +- | triage test: n/a - no concrete API to xfail against. --- ## Size & risk | Field | Value | + | ----------------------------------------------------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test _None yet - add a regression test as part of the fix-PR._. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3873:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3889.md b/docs/triage/v5/fix-sketches/issue_3889.md new file mode 100644 index 0000000000..39291c7a0f --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3889.md @@ -0,0 +1,12 @@ +# Fix sketch - #3889: Uninstall 2GP task request **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `packaging` +**Issue**: ## Bug - `cumulusci/cumulusci.yml:615-642` - Uninstall tasks: `uninstall_managed`, `uninstall_packaged`, `uninstall_packaged_incremental`, `uninstall_src`, `uninstall_pre`, `uninstall_post`. None take a 04t id. - `cumulusci/tasks/salesforce/UninstallPackage.py:6-32` - `UninstallPackage` accepts only `namespace` (and `purge_on_delete`). Builds an `UninstallPackageZipBuilder` from the namespace. - `c... ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - pass1: `keep-open`- needs a new`UninstallPackageVersion`task (or extend`UninstallPackage`) that calls Tooling API directly so it doesn't depend on sf cli stability (per the user's note about sf cli breaking changes). + +- | pass2 labels: `severity:medium,area:packaging,area:2gp,area:unlocked-package` ## Size & risk | Field | Value | + | -------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3889.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3889:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3910.md b/docs/triage/v5/fix-sketches/issue_3910.md new file mode 100644 index 0000000000..258fd089b2 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3910.md @@ -0,0 +1,15 @@ +# Fix sketch - #3910: JSON Schema incorrectly defines namespaced field as string instead of boolean for scratch org configuration **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `scratch-org-config` +**Issue**: ## Bug JSON Schema incorrectly defines namespaced field as string instead of boolean for scratch org configuration ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - Approach: land PR #3911 essentially as-is (one-line Pydantic field change in `cumulusci_yml.py` + `make schema` regenerated `cumulusci.jsonschema.json`). Both edits must ship together because `test_schema_is_current` would otherwise fail. + +- Target file:line: `cumulusci/utils/yaml/cumulusci_yml.py:150` (`namespaced: str = None` → `namespaced: bool = None`); regenerate `cumulusci/schema/cumulusci.jsonschema.json`. +- Size: small. +- Risk: low. The only Pydantic consumers of `ScratchOrg.namespaced` are YAML-validation pathways; the runtime keychain path already uses booleans. +- | API break: no (silent coercion was incidental and arguably already broken for users). ## Size & risk | Field | Value | + | ---------------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3910.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3910:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3931.md b/docs/triage/v5/fix-sketches/issue_3931.md new file mode 100644 index 0000000000..68caab4326 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3931.md @@ -0,0 +1,12 @@ +# Fix sketch - #3931: Specifying a profile in cumulusci.tasks.salesforce.ProfileGrantAllAccess results in an error **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `metadata-etl` +**Issue**: ## Bug Specifying a profile in cumulusci.tasks.salesforce.ProfileGrantAllAccess results in an error ## Target `cumulusci/tasks/salesforce/update_profile.py` lines 290-292 ## Recommended approach (from triage narrative) - pass1: `keep-open` - small, contained fix. + +- | pass2 labels: `bug` **Notes**: Minimal fix at update_profile.py:290-293 - bind `rt_elem = elem.find("recordType")` and check `if rt_elem is not None and rt_elem.text == rt["record_type"]`. Worth a quick scan of sibling code in `_set_record_types` for similar None-deref patterns on optional XML children. --- ## Size & risk | Field | Value | + | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3931.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3931:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3951.md b/docs/triage/v5/fix-sketches/issue_3951.md new file mode 100644 index 0000000000..4a1ae24a6f --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3951.md @@ -0,0 +1,14 @@ +# Fix sketch - #3951: set_duplicate_rule_status broken **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `metadata-etl` +**Issue**: ## Bug set*duplicate_rule_status broken ## Target \_See narrative for target file:line.* ## Recommended approach (from triage narrative) - pass1: `improve-error-message` - keep open. + +- pass2 labels: `bug`, `good-first-issue`, `documentation` **Notes**: Two improvements: 1. Update the `set_duplicate_rule_status` task option help to call out the `.` format requirement. + +2. | Same as #3613 - improve the base.py:332 error to list the files actually retrieved. The `Cannot find metadata file` error is shared across most `MetadataSingleEntityTransformTask` subclasses, so a single base-class fix would benefit several issues at once. --- ## Size & risk | Field | Value | + | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3951.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3951:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3953.md b/docs/triage/v5/fix-sketches/issue_3953.md new file mode 100644 index 0000000000..49c11d1c7b --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3953.md @@ -0,0 +1,12 @@ +# Fix sketch - #3953: add_picklist_entries never works through CLI **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `metadata-etl` +**Issue**: ## Bug add*picklist_entries never works through CLI ## Target \_See narrative for target file:line.* ## Recommended approach (from triage narrative) - pass1: `keep-open` - single-line fix. + +- | pass2 labels: `bug`, `good-first-issue` **Notes**: Minimal fix in `AddPicklistEntries._init_options`: `if isinstance(self.options.get("entries"), str): self.options["entries"] = json.loads(self.options["entries"])`. Apply same pattern to `record_types` for symmetry. The same class of bug exists in `AddFieldsToPageLayout` (encountered while investigating #3613): `cci task run add_page_layout_fields ... -o fields '[...]'` -> `pydantic.ValidationError: value is not a valid list`. A more general fix would be a helper in the CLI/task-base that auto-parses JSON strings for list-typed options, or schema-driven coercion via the new task_options Pydantic models. --- ## Size & risk | Field | Value | + | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3953.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3953:`). | diff --git a/docs/triage/v5/fix-sketches/issue_3955.md b/docs/triage/v5/fix-sketches/issue_3955.md new file mode 100644 index 0000000000..420448c67f --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_3955.md @@ -0,0 +1,17 @@ +# Fix sketch - #3955: Open Test Browser - SalesforcePlaywright.robot **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `robotframework` +**Issue**: ## Bug - `cumulusci/robotframework/SalesforcePlaywright.py:106` - `width, height = size.split("x", 1)` returns two `str` values. - `cumulusci/robotframework/SalesforcePlaywright.py:109-111` - the strings are forwarded directly: - Playwright contract requires `viewport.width` / `viewport.height` to be `int`, hence the runtime error `viewport.width: expected integer, got string` reported by the user ... ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - Approach: cast both fragments to `int` immediately after splitting. + +- Target: `cumulusci/robotframework/SalesforcePlaywright.py:106` +- Size: small (~1 line) - e.g. `width, height = (int(v) for v in size.split("x", 1))` +- Risk: low - preserves all existing call sites; users were already passing the documented `WxH` string format. +- API break: no. **Recommended action**: - pass1: `keep-open` - clear, low-risk, single-line bug fix; great good-first-issue candidate. +- pass2 labels: `bug, robotframework, playwright, good-first-issue` +- | triage test: `cumulusci/tests/triage/test_issue_3955.py` --- ## Size & risk | Field | Value | + | --------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_3955.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #3955:`). | diff --git a/docs/triage/v5/fix-sketches/issue_675.md b/docs/triage/v5/fix-sketches/issue_675.md new file mode 100644 index 0000000000..ff062d3544 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_675.md @@ -0,0 +1,17 @@ +# Fix sketch - #675: Show full traceback for Python exceptions in robot keywords **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `robotframework` +**Issue**: ## Bug - `cumulusci/tasks/robotframework/robotframework.py` configures listeners (`KeywordLogger`, `DebugListener`) but never sets `loglevel`/`logtitle`/`pythonpath` to surface Python tracebacks. `rg 'traceback|format_exc|format_tb' cumulusci/robotframework/` returns 0 matches; same for `cumulusci/tasks/robotframework/`. - Robot Framework's default behaviour: when a Python keyword raises, only `str(e... ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - Approach: in `Robot.\_init_options`, default `options.loglevel`to include`TRACE`, OR install a small listener that captures `sys.exc_info()`in keyword-end events and emits a formatted traceback through`robot.api.logger.error`. + +- Target: `cumulusci/tasks/robotframework/robotframework.py:104` (`_init_options`). +- Size: small (~10 lines). +- Risk: low - additive listener; opt-out via options if needed. +- API break: no. **Recommended action**: - pass1: `closed:stale-24mo` - issue opened 2018-07, last activity 2018-09, ~8 years inactivity. Two simple workarounds exist (`-o loglevel:TRACE`, `traceback.format_exc()` in keywords). Surface as a tip in docs rather than keep the bug open. +- pass2 labels: `cli-usability, robotframework, stale` +- | triage test: n/a - observable only in a robot run. --- ## Size & risk | Field | Value | + | --------------------------------------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test _None yet - add a regression test as part of the fix-PR._. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #675:`). | diff --git a/docs/triage/v5/fix-sketches/issue_710.md b/docs/triage/v5/fix-sketches/issue_710.md new file mode 100644 index 0000000000..047f32b61f --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_710.md @@ -0,0 +1,15 @@ +# Fix sketch - #710: Allow disabling default scratch org configs **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `scratch-org-config` +**Issue**: ## Bug Allow disabling default scratch org configs ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) - Approach: introduce a sentinel for "disabled" (recommend an explicit `disabled: true` flag on each scratch org config rather than relying on `config_file: None`, which has overloaded meaning) and skip disabled entries in `_load_scratch_orgs`. Optional: a stricter `merge_config` mode that preserves `None` overrides under `orgs.scratch.*`. + +- Target file:line: `cumulusci/utils/yaml/cumulusci_yml.py:147` (add `disabled: bool = None` to `ScratchOrg`); `cumulusci/core/keychain/base_project_keychain.py:155` (skip when `config.get("disabled")`); regenerate `cumulusci/schema/cumulusci.jsonschema.json`. +- Size: small/medium. +- Risk: low. Existing keys remain compatible; only adds new opt-in behaviour. Needs a docs note (`docs/orgs/scratch.md`) on how to disable inherited defaults. +- | API break: no (additive). The issue's literal `config_file: None` syntax would NOT be honoured under this proposal; if the team prefers that exact syntax instead, add a `dictmerge` exception so `None` overrides under `orgs.scratch.*` are preserved, then have `_load_scratch_orgs` skip entries with `config_file is None`. Either implementation satisfies the XFAIL test (`dev not in keychain.orgs`). ## Size & risk | Field | Value | + | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_710.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #710:`). | diff --git a/docs/triage/v5/fix-sketches/issue_733.md b/docs/triage/v5/fix-sketches/issue_733.md new file mode 100644 index 0000000000..8e989d4638 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_733.md @@ -0,0 +1,12 @@ +# Fix sketch - #733: Prompt to delete scratch org when creating one that already exists **Verdict**: `REPRODUCED-on-v4.10.0` (verdict_source: `v4.10.0`) + +**Theme**: `cli` +**Issue**: ## Bug Behaviour identical to the 2018 report - hard error, no interactive Y/N prompt. ## Target `cumulusci/cli/runtime.py` lines 126-140 ## Recommended approach (from triage narrative) - pass1: `closed:stale-24mo` - 7-year-old `cli-usability` enhancement, no traction. + +- | pass2 labels: `enhancement,cli-usability,stale` --- ## Size & risk | Field | Value | + | ------------------------------------------------------------------ | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_733.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #733:`). | diff --git a/docs/triage/v5/fix-sketches/issue_773.md b/docs/triage/v5/fix-sketches/issue_773.md new file mode 100644 index 0000000000..ad19427f79 --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_773.md @@ -0,0 +1,17 @@ +# Fix sketch - #773: Document task return values and results **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `docs` +**Issue**: ## Bug Document task return values and results ## Target _See narrative for target file:line._ ## Recommended approach (from triage narrative) 1. Add a class attribute on `BaseTask`: ```python +return_values_schema: ClassVar[Dict[str, str]] = {} + +```where each key is the return-values dict key and each value is a one-line +description (parallel to how `task_options` already works). 2. Extend `doc_task()` in `cumulusci/utils/__init__.py` to render a "Return Values" RST section (heading + bullet list) when `task_class.return_values_schema` is non-empty. Update the matching `get_task_*` helpers to surface the schema for web-doc generation. 3. Backfill the schema on tasks that already emit return values - search for `self.return_values\[` across `cumulusci/tasks/`: at minimum `PackageUpload`, `PromotePackageVersion`, `GithubRelease`, `CreatePackageVersion`, dependency-resolution tasks. Each gets a few lines. 4. Lift the `attention` admonition in `docs/config.md:740-744` once coverage is broad enough. Estimated effort: 1-2 day PR for the framework + first wave of tasks; an +ongoing "every new task documents its return values" contributor +expectation thereafter (enforce in `requesting-code-review` skill). ## Size & risk | Field | Value | +| ------------------------------------ | ---------------------- | +| Size estimate | _TBD by fix-PR author_ | +| Risk | _TBD by fix-PR author_ | +| Touches `cumulusci/robotframework/*` | _TBD_ | +| Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | +| Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_773.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #773:`). +``` diff --git a/docs/triage/v5/fix-sketches/issue_808.md b/docs/triage/v5/fix-sketches/issue_808.md new file mode 100644 index 0000000000..63e598bf8f --- /dev/null +++ b/docs/triage/v5/fix-sketches/issue_808.md @@ -0,0 +1,12 @@ +# Fix sketch - #808: deploy_packaging flow runs uninstall_packaged_incremental with wrong package name **Verdict**: `REPRODUCED-on-dev` (verdict_source: `dev`) + +**Theme**: `metadata-etl` +**Issue**: ## Bug - `UninstallPackaged._init_options` (UninstallPackaged.py:22-25): - Compare to `InstallPackageVersion._init_options` (install_package_version.py:75-79) which DOES use the fall-back chain `name_managed -> name -> namespace`. The asymmetry is the bug. - Bug pattern is unchanged since 2018; no fix has landed. ## Target `cumulusci/tasks/salesforce/UninstallPackaged.py` lines 22-25 ## Recommended approach (from triage narrative) - pass1: `keep-open` - small, contained fix. + +- | pass2 labels: `bug`, `good-first-issue` **Notes**: jlantz's 2018 follow-up about deprecating `project__package__name_managed` (legacy NPSP-only feature) is a separate, larger conversation; for this triage the minimal symmetric fix is enough. --- ## Size & risk | Field | Value | + | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | Size estimate | _TBD by fix-PR author_ | + | Risk | _TBD by fix-PR author_ | + | Touches `cumulusci/robotframework/*` | _TBD_ | + | Touches `cumulusci/tasks/bulkdata/*` | _TBD_ | + | Breaks public CLI surface | _TBD_ | ## Regression test `cumulusci/tests/triage/test_issue_808.py`. Remove the `@pytest.mark.xfail` marker and confirm green. ## Full narrative See `docs/triage/v5/repro-results.md` (search for `### #808:`). | diff --git a/docs/triage/v5/proposals.md b/docs/triage/v5/proposals.md new file mode 100644 index 0000000000..959124ce3f --- /dev/null +++ b/docs/triage/v5/proposals.md @@ -0,0 +1,245 @@ +# CCI Issue Triage Proposals + +Classification matrix for the 142 open issues, with proposed pass-1 actions. +Mutations are gated on explicit maintainer approval; NONE of the GitHub state changes +described here are executed by this PR. + +## Class summary + +By proposed-pass1: + +- kept-open: 70 +- closed:stale-24mo: 30 +- closed:pre-v4.0.0: 28 +- closed:not-reproducible-on-v4.10.0: 6 +- closed:feature-implemented: 4 +- closed:missing-fields: 1 +- closed:pr-resolved-#3361: 1 +- closed:pr-resolved-#3651: 1 +- closed:pr-resolved-#3681: 1 + +By risk: + +- low: 97 +- medium: 0 +- medium-high: 45 +- high: 0 + +## Repro pass updates (the reproducibility pass) + +Two rounds of subagents verified 98 of 142 open issues against v4.10.0 +source on `origin/main` (see `repro-results.md` and `repro-results.csv`): + +- **the initial reproducibility pass**: packaging + metadata-etl (45 issues, the triage). +- **the follow-up reproducibility pass**: cli + bulkdata + dependencies + ci-integration (53 issues, the triage). + +This file integrates the initial reproducibility pass + the follow-up reproducibility pass verdicts. Proposed-pass1 was changed for **62 rows**; **24 rows** were confirmed by repro and marked in `rule-rationale`. Untouched themes (44 issues): `robotframework`, `scratch-org-config`, `auth`, `keychain`, `docs`, `python-modernization`, plus all `closed:pre-v4.0.0` rows. + +### New proposed-pass1 vocabulary introduced by repro pass + +The repro pass introduces three pass1 values that are not yet in the spec section 7 vocabulary; they are proposed here for spec amendment in the closeout: + +- `closed:not-reproducible-on-v4.10.0`: bug already fixed (or feature already shipped) on v4.10.0; close with explicit verdict citation rather than the generic stale-24mo template. +- `closed:feature-implemented`: feature ask already shipped (separate from PR-resolved because no specific PR can be cited in the close comment, just a confirmation that the requested behaviour exists). +- `closed:pr-resolved-#NNNN`: same as the existing spec value but reused for repro-discovered fixes (e.g. #3283 -> PR #3361, #3605 -> PR #3651). The PR was found via repro, not via GitHub linkage. + +### Repro pass cross-cutting findings (relevant to Phase 2 execution and v5 planning) + +- **Same-file pairs / triplets** (worth bundling as one fix epic each): +- #2153 + #3471: both touch `cumulusci/tasks/github/merge.py` (`MergeBranch`). +- #3506 + #3570 + #3663: flow `when:` evaluator in `cumulusci/core/flowrunner.py:510-516` and missing propagation in the flow branch (lines 660-697). +- #3349 + #2013: shared root cause in `MappingStep` table-naming (uses `sf_object` not `table`). +- #3603 + #3604 + #3619: dependency-resolution UX gaps (404 leak, missing pin password env, missing sfdx-project.json writeback). +- **Recent regressions still active** ([Tranche 1](../../docs/triage/v5/README.md#tranche-1-candidate-list) candidates for v4.10.x patch): +- #3852 (sarge cosmetic, Py 3.13). +- #3854 (PR #3741 / commit `2c5d0056e` introduced a still-active validation error in `cumulusci/tasks/bulkdata/extract.py:371-374`). +- #3938, #3939 (already in the initial reproducibility pass; reaffirmed REPRO). +- **Wrong-repo report**: +- #3612: `cci robot ...` issue belongs in `cci-vscode`, not cci core. Recommend new spec vocab `closed:wrong-repo` and a one-line "filed-in-wrong-repo" close comment. +- **Cross-room duplicate confirmation** (the initial reproducibility pass): +- #3762 -> closed:duplicate-of-#3544 (Cluster A canonical). +- **Unchanged INCONCLUSIVE-needs-X** (kept-open with informational note in narrative; no proposals.md row change required): +- #3418 + #3717: both `INCONCLUSIVE-needs-cumulus-actions-workflow` (cci has zero GitHub Actions env auto-detect; structural gap). +- #3542: needs 2GP CI pipeline. +- #3936: flaky-network read-timeout; intermittent. +- **Adjacent-finding** surfaced but not blocking: +- `TypeError` in `cumulusci/utils/__init__.py:229` when `namespaced_org=True` is supplied against a project with `namespace=None` (the initial reproducibility pass). +- `cumulusci[select]` warning at module-import time in `cumulusci/tasks/bulkdata/select_utils.py` (#3886, fires on every `extract_dataset`). + +### Approval-gate hint + +Most `closed:stale-24mo -> kept-open` rescues warrant a `target:v4-patch` or `v5-candidate` pass2 label, but those columns were left unchanged here so the next user gate can attach labels deliberately. + +the dev-branch verification pass added 17 issues across 6 themes (`robotframework`, `scratch-org-config`, `auth`, `keychain`, `docs`, `python-modernization`) against `origin/dev` (NOT v4.10.0). Each updated row in the proposal table is annotated with `+repro: {verdict}`. + +- **12 REPRODUCED-on-dev**: #675, #710, #773, #2500, #3407, #3464, #3541, #3602, #3849, #3873, #3910, #3955. +- **5 NOT-REPRODUCED-on-dev**: #987, #2126, #2667, #3306, #3610. + +R3 rescues from prior closure verdicts: + +- **From `closed:missing-fields` (5 issues)**: +- `#2126`, `#2667` → `closed:stale-24mo` (NOT-REPRO on dev; ancient). +- `#2500` → `kept-open` (REPRO; documentation gap, not stale). +- `#3306` confirmed kept-open (NOT-REPRO, but real reporter). +- `#3873` confirmed kept-open (REPRO; feature ask, not stale). +- **From `closed:stale-24mo` (5 issues)**: +- `#710`, `#773` → `kept-open` (REPRO on dev; surface as v5 candidates). +- `#987` confirmed `closed:stale-24mo` (NOT-REPRO; ancient). +- `#675` confirmed `closed:stale-24mo` (REPRO but robot-runner-only with no python test path). +- `#3407` confirmed kept-open. +- **New PR-resolved (1 issue)**: +- `#3610` → `closed:pr-resolved-#3681` (fixed on dev by commit 84389d998; regression tests already on dev). + +Notable R3 findings (also captured in repro-results.md narrative): + +- `#3910` (REPRO on dev) has an open PR `#3911` that fixes it; suggest `closed:pr-pending-#3911` close vocab once PR merges. Until then, keep open. +- `#3849` (REPRO on dev) is a urllib3 v2 / Selenium 3 ABI conflict on a fresh `pip install`; `.venv` only survives via the `uv.lock` pin. Surface as a dependency-modernization candidate for v5. +- `#3955` and `#3602` (REPRO on dev) are RF-theme bugs with pytest-stubbable repros - first proof that the parent plan's no-RF-inspection rule is too broad for evidence/test work (addressed in plan's "RF scope clarification" subsection). +- `#3541` (REPRO on dev) is a keychain corruption when the password is missing - reproducible via test stubbing the encryption layer. + +## Proposal table + +| issue# | title | last-activity | proposed-pass1 | proposed-pass2 | proposed-v5 | rule-rationale | risk | approval | +| -----: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------- | ---------------------------------- | -------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -------- | +| #675 | Show full traceback for Python exceptions in robot keywords | 2018-09-11 | closed:stale-24mo | n/a | n/a | rule 1: updated 2018-09-11, no maintainer label; +repro: REPRO-on-dev | low | | +| #710 | Allow disabling default scratch org configs | 2018-11-02 | kept-open | n/a | n/a | rule 1: updated 2018-11-02, no maintainer label; +repro: REPRO-on-dev | low | | +| #733 | Prompt to delete scratch org when creating one that already exists | 2018-09-11 | closed:stale-24mo | n/a | n/a | rule 1: updated 2018-09-11, no maintainer label; +repro the reproducibility pass: REPRO (confirms) | low | | +| #773 | Document task return values and results | 2018-11-19 | kept-open | n/a | n/a | rule 1: updated 2018-11-19, no maintainer label; +repro: REPRO-on-dev | low | | +| #808 | deploy_packaging flow runs uninstall_packaged_incremental with wrong package name | 2018-10-09 | kept-open | n/a | n/a | rule 1: updated 2018-10-09, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #987 | Last week this test worked, now I get a javascriptexception message. | 2022-06-16 | closed:stale-24mo | n/a | n/a | rule 1: updated 2022-06-16, no maintainer label; +repro: NOT-REPRO-on-dev | low | | +| #1348 | Multiple Git Provider Support | 2022-11-07 | closed:stale-24mo | n/a | n/a | rule 1: updated 2022-11-07, no maintainer label; +repro the reproducibility pass: REPRO (confirms) | low | | +| #1350 | Unable to run tasks in remote projects | 2022-07-07 | closed:not-reproducible-on-v4.10.0 | n/a | n/a | rule 1: updated 2022-07-07, no maintainer label; +repro the reproducibility pass: NOT-REPRO | low | | +| #1432 | CCI Inconsistencies in validating arguments | 2020-01-03 | closed:stale-24mo | n/a | n/a | rule 1: updated 2020-01-03, no maintainer label; +repro the reproducibility pass: REPRO (confirms) | low | | +| #1769 | Unusual case in test_load | 2020-08-07 | closed:stale-24mo | n/a | n/a | rule 1: updated 2020-08-07, no maintainer label; +repro the reproducibility pass: REPRO (confirms) | low | | +| #2013 | Mapping files with steps that are not 1-1 with SObjects are unreliable for extraction | 2020-09-13 | kept-open | n/a | n/a | rule 1: updated 2020-09-13, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #2096 | REST dataloads throw errors that Bulk loads do not. | 2020-10-15 | closed:not-reproducible-on-v4.10.0 | n/a | n/a | rule 1: updated 2020-10-15, no maintainer label; +repro the reproducibility pass: NOT-REPRO | low | | +| #2126 | Support task to encrypt all possible fields with probabilistic encryption | 2022-01-28 | closed:stale-24mo | n/a | n/a | rule 3: missing cci-version; updated 2022-01-28; +repro: NOT-REPRO-on-dev | medium-high | | +| #2140 | Prompt Org Configs when Org Does Not Exist and Command Runs Against It | 2022-05-20 | closed:stale-24mo | n/a | n/a | rule 1: updated 2022-05-20, no maintainer label; +repro the reproducibility pass: REPRO (confirms) | low | | +| #2153 | Add comment to original PR which tags all branch subscribers when a merge conflict auto-generated PR is created ("Merge master into ") | 2020-11-11 | kept-open | n/a | n/a | rule 1: updated 2020-11-11, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #2325 | Task to turn off validation rules to allow data insert | 2021-01-19 | kept-open | n/a | n/a | rule 1: updated 2021-01-19, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #2402 | Create a --rebuild-org parameter for cci flow run | 2022-01-28 | closed:stale-24mo | n/a | n/a | rule 3: missing repro, cci-version; updated 2022-01-28; +repro the reproducibility pass: REPRO | medium-high | | +| #2440 | add_permission_set_perms throws 'string indices must be integers' error | 2022-01-28 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #2500 | ignore_failure is not documented | 2022-03-28 | kept-open | n/a | n/a | rule 3: missing repro, cci-version; updated 2022-03-28; +repro: REPRO-on-dev | medium-high | | +| #2505 | Filtering records to be extracted | 2021-06-02 | closed:feature-implemented | n/a | n/a | rule 1: updated 2021-06-02, no maintainer label; +repro the reproducibility pass: NOT-REPRO | low | | +| #2506 | Bulk Operations should have a --debug mode which maintains logs and tempfiles | 2021-03-27 | kept-open | n/a | n/a | rule 1: updated 2021-03-27, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #2507 | Undo mode for CumulusCI Insert | 2021-03-27 | closed:stale-24mo | n/a | n/a | rule 1: updated 2021-03-27, no maintainer label; +repro the reproducibility pass: REPRO (confirms) | low | | +| #2508 | Manual load retries | 2021-03-27 | kept-open | n/a | n/a | rule 1: updated 2021-03-27, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #2667 | `cci org connect` should output the name of the connected app it is using, especially if it is non-default | 2022-01-28 | closed:stale-24mo | n/a | n/a | rule 3: missing repro, cci-version; updated 2022-01-28; +repro: NOT-REPRO-on-dev | medium-high | | +| #2697 | Initialization issue with 'namespaced' field in 'cci org info' after updating from a non-namespaced scratch org to namespaced | 2021-06-25 | closed:stale-24mo | n/a | n/a | rule 1: updated 2021-06-25, no maintainer label; +repro the reproducibility pass: INCONCL:scratch-slot (confirms) | low | | +| #2826 | The deploy_unmanaged flow is supposed to silently do nothing if there's not actually a package directory | 2021-08-25 | kept-open | n/a | n/a | rule 1: updated 2021-08-25, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #2930 | Exception in task load_dataset | 2022-01-28 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #2951 | Error in task load_dataset - Standard_price_not_defined | 2021-11-11 | kept-open | n/a | n/a | rule 1: updated 2021-11-11, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #2973 | cumulusci.tasks.salesforce.CreateCommunity has no option to set authentication mode for LWR Community | 2022-01-28 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #2979 | deploy task should deploy from the default entry in packageDirectories in sfdx-project.json if the project is using sfdx format. | 2021-11-17 | kept-open | n/a | n/a | rule 1: updated 2021-11-17, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3015 | Request to have the ability to remove an imported dx org from cci org list without deleting the actual scratch org. | 2022-01-28 | closed:stale-24mo | n/a | n/a | rule 3: missing repro, cci-version; updated 2022-01-28; +repro the reproducibility pass: REPRO | medium-high | | +| #3024 | Order of flow groups in cumulusci/cumulusci.yml reflects in the Flows box in CCI VSCode extension, but order is not natural | 2022-01-05 | closed:stale-24mo | n/a | n/a | rule 1: updated 2022-01-05, no maintainer label; +repro the reproducibility pass: REPRO (confirms) | low | | +| #3045 | create_bulk_data_permission_set errors on multiple runs | 2022-01-28 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3047 | Open Test Browser TypeError: 'NoneType' object is not callable when using useralias param | 2022-01-28 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3089 | task run_tests not including namespace in query | 2022-02-21 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3100 | uninstall_post is not substituting %%%NAMESPACE%%% | 2023-10-02 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3134 | Running flow always needs a default org or org defined | 2022-04-18 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3137 | cci task run update_package_xml and Salesforce Case Custom Object | 2022-05-18 | kept-open | n/a | n/a | rule 3: missing repro; updated 2022-05-18; +repro the reproducibility pass: REPRO | medium-high | | +| #3161 | Ability to Hide Option Values When Using Task Options In a Task or Flow Run | 2022-04-21 | closed:stale-24mo | n/a | n/a | rule 1: updated 2022-04-21, no maintainer label; +repro the reproducibility pass: REPRO (confirms) | low | | +| #3165 | Update Admin Profile task fails when specifying records types without custom package.xml | 2022-04-19 | kept-open | n/a | n/a | rule 1: updated 2022-04-19, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3167 | Add ability to define page layout assignments with record types using the update_admin_profile task | 2022-04-20 | closed:stale-24mo | n/a | n/a | rule 1: updated 2022-04-20, no maintainer label; +repro the reproducibility pass: NOT-REPRO (confirms) | low | | +| #3182 | "Error: 'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte" when running any CCI command after connecting Github service | 2025-02-19 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3203 | generate_and_load_from_yaml task record type extraction fails for namespaced custom objects | 2023-01-09 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3283 | json parser error when empty string passed for date field during upsert or update | 2022-09-16 | closed:pr-resolved-#3361 | n/a | n/a | rule 1: updated 2022-09-16, no maintainer label; +repro the reproducibility pass: NOT-REPRO | low | | +| #3306 | Preview Toggle for Scratch org def file | 2023-09-13 | kept-open | n/a | n/a | rule 3: missing repro, cci-version; updated 2023-09-13; +repro: NOT-REPRO-on-dev | medium-high | | +| #3307 | Project Template Support for initialisation | 2022-07-25 | closed:stale-24mo | n/a | n/a | rule 3: missing repro, cci-version; updated 2022-07-25; +repro the reproducibility pass: REPRO | medium-high | | +| #3320 | Metadata ETL task to Deactivate a Flow | 2023-02-15 | closed:feature-implemented | n/a | n/a | rule 3: missing repro, cci-version; updated 2023-02-15; +repro the reproducibility pass: NOT-REPRO | medium-high | | +| #3331 | Task update_package_xml does not write correct package.xml for AssignmentRules | 2022-10-11 | kept-open | n/a | n/a | rule 1: updated 2022-10-11, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3347 | Cannot release an unlocked beta package with release_unlocked_beta flow | 2022-08-29 | closed:stale-24mo | n/a | n/a | rule 1: updated 2022-08-29, no maintainer label; +repro the reproducibility pass: NOT-REPRO (confirms) | low | | +| #3349 | Make generated dataset recordType tables unique based on table instead of sf_object | 2022-09-26 | kept-open | n/a | n/a | rule 3: missing repro, cci-version; updated 2022-09-26; +repro the reproducibility pass: REPRO | medium-high | | +| #3353 | Enable Snowfakery task to use recipes from other repositories | 2024-08-20 | kept-open | n/a | n/a | rule 3: missing repro, cci-version; updated 2024-08-20; +repro the reproducibility pass: REPRO | medium-high | | +| #3360 | Read Only Object Lookup for Load_Dataset | 2022-09-15 | closed:feature-implemented | n/a | n/a | rule 1: updated 2022-09-15, no maintainer label; +repro the reproducibility pass: NOT-REPRO | low | | +| #3376 | extract_dataset sql error | 2022-10-03 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3407 | `set_service(service_config)` type signature is wrong | 2022-10-19 | kept-open | n/a | n/a | rule 1: updated 2022-10-19, no maintainer label; +repro: REPRO-on-dev | low | | +| #3418 | Error creating 1gp release | 2022-11-01 | closed:stale-24mo | n/a | n/a | rule 1: updated 2022-11-01, no maintainer label; +repro the reproducibility pass: INCONCL:cumulus-actions-workflow (confirms) | low | | +| #3429 | Support overriding `cumulusci.yml` to be used for configuration | 2022-11-08 | kept-open | n/a | n/a | rule 1: updated 2022-11-08, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3440 | Enhance default_package_path to serve multi package projects better | 2022-11-17 | kept-open | n/a | n/a | rule 1: updated 2022-11-17, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3441 | "cci task run create_package_version" should allow option "version_base: default" in cumulusci.yml | 2023-12-15 | kept-open | n/a | n/a | rule 1: updated 2023-12-15, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3446 | CCI task "push_qa" crashes for Unlocked package with no namespace | 2022-12-13 | kept-open | n/a | n/a | rule 1: updated 2022-12-13, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3464 | Provide concise documentation of cumulusci.yml `project` configuration options | 2022-12-12 | kept-open | n/a | n/a | rule 1: updated 2022-12-12, no maintainer label; +repro: REPRO-on-dev | low | | +| #3466 | Ability to specify a test suite to run instead of just test_name_match in the run_tests task | 2022-12-13 | closed:feature-implemented | n/a | n/a | rule 3: missing repro, cci-version; updated 2022-12-13; +repro the reproducibility pass: NOT-REPRO | medium-high | | +| #3470 | Rename `ci_master` flow to a `ci_main` or at least create a new duplicate `ci_main` flow | 2022-12-16 | closed:stale-24mo | n/a | n/a | rule 1: updated 2022-12-16, no maintainer label; +repro the reproducibility pass: REPRO (confirms) | low | | +| #3471 | `Merged 0 commits into branch:` message displays when a non-Source Code change is made in `github_automerge_feature` task | 2022-12-16 | kept-open | n/a | n/a | rule 1: updated 2022-12-16, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3479 | Error with "cci org import" in github action | 2023-02-22 | closed:not-reproducible-on-v4.10.0 | n/a | n/a | rule 1: updated 2023-02-22, no maintainer label; +repro the reproducibility pass: NOT-REPRO | low | | +| #3485 | "cci task run run_tests" generates incorrect test_results.xml format output | 2024-01-28 | kept-open | n/a | n/a | rule 1: updated 2024-01-28, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3492 | Enhance the "-o" option of "cci flow run" to accept "project\_\_custom" attribute values | 2023-01-17 | kept-open | n/a | n/a | rule 1: updated 2023-01-17, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3506 | when clause support for flow steps which call other flows | 2023-01-25 | kept-open | n/a | n/a | rule 1: updated 2023-01-25, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3512 | `namespace` does not display when running `cci org info` on a packaging org (does work when you add `--json` flag) | 2023-02-03 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3518 | Task add_picklist_entries always sets a default value for record types | 2023-02-03 | kept-open | n/a | n/a | rule 1: updated 2023-02-03, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3541 | Getting None\_\_dev as SFDX Alias | 2023-07-07 | kept-open | n/a | n/a | rule 1: updated 2023-07-07, no maintainer label; +repro: REPRO-on-dev | low | | +| #3542 | 2GP flows fail in the local environment to test: `ci_feature_2gp` or `qa_org_2gp` with error: Could not find package version id | 2023-02-22 | closed:stale-24mo | n/a | n/a | rule 1: updated 2023-02-22, no maintainer label; +repro the reproducibility pass: INCONCL:2GP-CI-pipeline (confirms) | low | | +| #3543 | New Option `load_sfdx_project_paths` for dx_convert_from Task | 2023-03-30 | kept-open | n/a | n/a | rule 1: updated 2023-03-30, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3544 | Task `update_admin_profile` errors when the org has person accounts and a namespace | 2025-05-07 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation; +repro the reproducibility pass: INCONCL:namespaced-project (confirms) | medium-high | | +| #3546 | password_env_name bleeding over to next package | 2024-09-13 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3549 | Deploy to Salesforce does not create a test output | 2023-04-05 | kept-open | n/a | n/a | rule 1: updated 2023-04-05, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3561 | Retrieve_unpackaged unusable in MetaDeploy | 2023-03-15 | closed:stale-24mo | n/a | n/a | rule 1: updated 2023-03-15, no maintainer label; +repro the reproducibility pass: NOT-REPRO (confirms) | low | | +| #3563 | Unmanaged Metadata not deploying during 2GP Package Create | 2024-06-21 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3570 | Feature Request: Flow "finally" or "error" path | 2023-04-04 | kept-open | n/a | n/a | rule 1: updated 2023-04-04, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3585 | Error Occurs when Using `update_package_xml` on object with `xsi:nil="true"` | 2023-04-19 | kept-open | n/a | n/a | rule 1: updated 2023-04-19, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3587 | Warning message when attempting to set `--install_class` or `--uninstall_class` flag on `update_package_xml` and `--managed` is `false` or null (defaults to true) | 2023-04-23 | kept-open | n/a | n/a | rule 1: updated 2023-04-23, no maintainer label; +repro the reproducibility pass: NOT-REPRO | low | | +| #3589 | "Workflow" and "MatchingRules" are removed from package.xml after running "update_package_xml" task | 2023-04-25 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3592 | release_unlocked_beta doesn't work when an install key is specified | 2023-08-02 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3593 | `dx` task doesn't work for some commands like `project convert source` | 2023-05-03 | kept-open | n/a | n/a | rule 1: updated 2023-05-03, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3600 | Allow install_managed to use environment variables | 2023-05-25 | kept-open | n/a | n/a | rule 1: updated 2023-05-25, no maintainer label; +repro the reproducibility pass: NOT-REPRO | low | | +| #3602 | Need Chrome/Firefox options(browser options/capabilities) in 'Open Test Browser' Keyword | 2023-05-26 | kept-open | n/a | n/a | rule 1: updated 2023-05-26, no maintainer label; +repro: REPRO-on-dev | low | | +| #3603 | Any issue with git results in the unhelpful "404 not found" error | 2023-05-30 | kept-open | n/a | n/a | rule 1: updated 2023-05-30, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3604 | Task request: Update sfdx-project.json dependencies based off of computed cumulusci dependencies | 2023-05-30 | kept-open | n/a | n/a | rule 3: missing repro, cci-version; updated 2023-05-30; +repro the reproducibility pass: REPRO | medium-high | | +| #3605 | Ability to Increment Major Versions when running `upload_production` | 2023-05-31 | closed:pr-resolved-#3651 | n/a | n/a | rule 1: updated 2023-05-31, no maintainer label; +repro the reproducibility pass: NOT-REPRO | low | | +| #3607 | The `retry_failures` from the task `run_tests` is not working for me. | 2023-06-01 | closed:stale-24mo | n/a | n/a | rule 1: updated 2023-06-01, no maintainer label; +repro the reproducibility pass: INCONCL:org-with-managed-package (confirms) | low | | +| #3609 | Command 'cci task run dx --command "plugins:install sfdx-browserforce-plugin"' fails | 2023-06-05 | closed:stale-24mo | n/a | n/a | rule 1: updated 2023-06-05, no maintainer label; +repro the reproducibility pass: INCONCL:live-cli-test (confirms) | low | | +| #3610 | Error running `run_tests` task | 2023-06-05 | closed:pr-resolved-#3681 | n/a | n/a | rule 1: updated 2023-06-05, no maintainer label; +repro: NOT-REPRO-on-dev (fixed on dev by PR #3681) | low | | +| #3612 | Maintain the CumulusCI for VSCode Extension | 2023-06-23 | closed:not-reproducible-on-v4.10.0 | n/a | n/a | rule 1: updated 2023-06-23, no maintainer label; +repro the reproducibility pass: NOT-REPRO | low | | +| #3613 | AddFieldsToPageLayout | 2023-06-23 | closed:stale-24mo | n/a | n/a | rule 1: updated 2023-06-23, no maintainer label; +repro the reproducibility pass: REPRO (confirms) | low | | +| #3615 | update_dependencies does not honor resolution strategy | 2023-07-06 | closed:not-reproducible-on-v4.10.0 | n/a | n/a | rule 1: updated 2023-07-06, no maintainer label; +repro the reproducibility pass: NOT-REPRO | low | | +| #3618 | Allow for list when using deleting/removing CumulusCI orgs (`cci org remove` and `cci org scratch_delete`) | 2023-07-13 | kept-open | n/a | n/a | rule 1: updated 2023-07-13, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3619 | Dependency_pins does not honor passwords | 2023-08-02 | kept-open | n/a | n/a | rule 1: updated 2023-08-02, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3641 | Prevent Downgrade of dependencies | 2024-05-17 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3649 | Support serial loads with update_data task | 2023-09-08 | kept-open | n/a | n/a | rule 1: updated 2023-09-08, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3663 | When clause | Ability to pass in prior task response values | 2023-09-21 | kept-open | n/a | n/a | rule 1: updated 2023-09-21, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3692 | No parser configuration found for subdirectory digitalExperiences | 2023-10-30 | kept-open | n/a | n/a | rule 1: updated 2023-10-30, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3699 | Sort of the data during extraction | 2023-11-10 | closed:stale-24mo | n/a | n/a | rule 1: updated 2023-11-10, no maintainer label; +repro the reproducibility pass: REPRO (confirms) | low | | +| #3700 | Trying to do an upsert on a master-detail child object gets an error around permission | 2023-11-10 | kept-open | n/a | n/a | rule 1: updated 2023-11-10, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3701 | set a mapping to the id instead of it being either a number or the salesforce id | 2023-11-10 | closed:stale-24mo | n/a | n/a | rule 1: updated 2023-11-10, no maintainer label; +repro the reproducibility pass: REPRO (confirms) | low | | +| #3717 | Github automerge feature task not working when running through Github Flows | 2023-12-08 | closed:stale-24mo | n/a | n/a | rule 1: updated 2023-12-08, no maintainer label; +repro the reproducibility pass: INCONCL:cumulus-actions-workflow (confirms) | low | | +| #3721 | create_package_version version_name default should be version number, not "Release" | 2024-02-12 | kept-open | n/a | n/a | rule 1: updated 2024-02-12, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3733 | `cci org remove` doesn't remove org | 2025-02-01 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3734 | Exception in cci task upload_production - Malformed Request Error: The version number must be greater than the last Managed - Released version number | 2024-02-15 | kept-open | n/a | n/a | rule 1: updated 2024-02-15, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3745 | cci flow run ci_beta AND cci task install_managed_beta DO not use the latest beta | 2024-02-13 | closed:stale-24mo | n/a | n/a | rule 1: updated 2024-02-13, no maintainer label; +repro the reproducibility pass: NOT-REPRO (confirms) | low | | +| #3746 | Deleted Versions used for determining next version | 2024-02-09 | kept-open | n/a | n/a | rule 1: updated 2024-02-09, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3754 | Enable configuration for cci version update sources | 2024-05-16 | kept-open | n/a | n/a | rule 3: missing repro, cci-version; updated 2024-05-16; +repro the reproducibility pass: REPRO | medium-high | | +| #3758 | Flow "push_upgrade_org" is incorrectly defined | 2024-02-28 | kept-open | n/a | n/a | rule 1: updated 2024-02-28, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3762 | `update_admin_profile` task fails on namespaced org with Person Accounts enabled | 2024-03-06 | closed:stale-24mo | n/a | n/a | rule 1: updated 2024-03-06, no maintainer label; +repro the reproducibility pass: closed:duplicate-of-#3544 (confirms) | low | | +| #3763 | Namespace not being added into Flow references in minority of cases | 2024-06-18 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3768 | Snowfakery Batch Size and Just Once | 2024-03-27 | kept-open | n/a | n/a | rule 1: updated 2024-03-27, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3770 | Error during cci task run retrieve_changes | 2024-08-05 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3771 | find_replace transforms on XPath with predicates does not work | 2024-04-12 | kept-open | n/a | n/a | rule 1: updated 2024-04-12, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3773 | retrieve_profile task seems to be missing some Metadata | 2024-04-30 | kept-open | n/a | n/a | rule 1: updated 2024-04-30, no maintainer label; +repro the reproducibility pass: REPRO | low | | +| #3781 | Package Versions Starting with 0 (0.1,0.2,0.3, etc.) Cause "does not exist" error When Referenced in cumulusci.yml Dependencies | 2024-06-13 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3784 | Can't run test suites if project**test**name_match is defined | 2024-05-13 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3796 | cci task run get_installed_packages/uninstall_managed do not see 2GP beta | 2024-06-03 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3810 | Github tasks fail when using github enterprise | 2024-07-12 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3849 | Installing CumulusCI v 4 installs the latest version of urllib3 which seems to be incompatable with running ROBOT tests | 2025-09-04 | kept-open | severity:minor,area:robotframework,v5-candidate:no,needs-repro | no | kept-open: bug, area:robotframework; +repro: REPRO-on-dev | low | | +| #3852 | CumulusCI 4 refresh token error | 2025-06-09 | kept-open | severity:minor,area:auth,v5-candidate:no | no | kept-open: bug, area:auth; +repro the reproducibility pass: REPRO (confirms) | low | | +| #3854 | Issue while Capturing Data | 2025-02-03 | kept-open | severity:minor,area:cli,v5-candidate:no | no | kept-open: bug, area:cli; +repro the reproducibility pass: REPRO (confirms) | low | | +| #3873 | Standalone Robot Framework Library for Selenium-Based Salesforce Automation (Inspired by Copado QForce) | 2025-01-24 | kept-open | n/a | n/a | rule 3: missing cci-version; updated 2025-01-24; +repro: REPRO-on-dev | medium-high | | +| #3884 | Running a Dev_Org flow goes through re-install of the the same package version again. | 2025-02-26 | closed:missing-fields | n/a | n/a | rule 3: missing cci-version; updated 2025-02-26; +repro the reproducibility pass: INCONCL:project-with-managed-deps (confirms) | medium-high | | +| #3885 | Versioning in CumulusCI Releases Does Not Support Full Versioning with Build Numbers | 2025-03-21 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 1.x; no reporter v4+ reconfirmation | medium-high | | +| #3886 | Required Dependencies? | 2025-03-06 | kept-open | severity:minor,area:bulkdata,v5-candidate:no | no | kept-open: bug, area:bulkdata; +repro the reproducibility pass: REPRO (confirms) | low | | +| #3887 | Version comparison bug: 0.10.0.1 incorrectly compared as less than 0.9.0.1 | 2025-03-06 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3889 | Uninstall 2GP task request | 2025-03-21 | kept-open | n/a | n/a | rule 3: missing repro, cci-version; updated 2025-03-21; +repro the reproducibility pass: REPRO | medium-high | | +| #3899 | Getting following Exception in task deploy_packaging.unschedule_apex | 2025-05-15 | kept-open | severity:minor,area:packaging,v5-candidate:no | no | kept-open: bug, area:packaging; +repro the reproducibility pass: INCONCL:1gp-packaging-org (confirms) | low | | +| #3902 | Task install_managed security_type not being respected with 04t ID | 2025-06-03 | kept-open | severity:minor,area:packaging,v5-candidate:no | no | kept-open: bug, area:packaging; +repro the reproducibility pass: INCONCL:managed-package-04t (confirms) | low | | +| #3910 | JSON Schema incorrectly defines namespaced field as string instead of boolean for scratch org configuration | 2025-07-14 | kept-open | severity:minor,area:keychain,v5-candidate:no | no | kept-open: bug, area:keychain; +repro: REPRO-on-dev (open PR #3911 ready to land) | low | | +| #3919 | uninstall_packaged_incremental was purged even though it was set to "not" | 2025-08-15 | closed:pre-v4.0.0 | n/a | n/a | rule 2: body declares CumulusCI 3.x; no reporter v4+ reconfirmation | medium-high | | +| #3929 | cci task run create_community (and sf community create) Appears to Loop/Timeout During Community Creation | 2025-10-22 | closed:not-reproducible-on-v4.10.0 | severity:minor,area:cli,v5-candidate:no,needs-repro | no | kept-open: bug, area:cli; +repro the reproducibility pass: NOT-REPRO | low | | +| #3931 | Specifying a profile in cumulusci.tasks.salesforce.ProfileGrantAllAccess results in an error | 2025-10-17 | kept-open | severity:minor,area:metadata-etl,v5-candidate:no | no | kept-open: bug, area:metadata-etl; +repro the reproducibility pass: REPRO (confirms) | low | | +| #3936 | Error: HTTPSConnectionPool(host='yoursite.scratch.my.salesforce.com', port=443): Read timed out. (read timeout=None) | 2025-12-03 | kept-open | severity:minor,area:bulkdata,v5-candidate:no | no | kept-open: bug, area:bulkdata; +repro the reproducibility pass: INCONCL:flaky-network (confirms) | low | | +| #3938 | Rest_Deploy ignores errors | 2025-12-16 | kept-open | severity:minor,area:other,v5-candidate:no | no | kept-open: bug, area:other; +repro the reproducibility pass: REPRO (confirms) | low | | +| #3939 | Deploy task can't parse SOAP Response | 2025-12-16 | kept-open | severity:minor,area:other,v5-candidate:no | no | kept-open: bug, area:other; +repro the reproducibility pass: REPRO (confirms) | low | | +| #3951 | set_duplicate_rule_status broken | 2026-02-03 | kept-open | severity:minor,area:metadata-etl,v5-candidate:no | no | kept-open: bug, area:metadata-etl; +repro the reproducibility pass: REPRO (confirms) | low | | +| #3953 | add_picklist_entries never works through CLI | 2026-02-13 | kept-open | severity:minor,area:metadata-etl,v5-candidate:no | no | kept-open: bug, area:metadata-etl; +repro the reproducibility pass: REPRO (confirms) | low | | +| #3955 | Open Test Browser - SalesforcePlaywright.robot | 2026-03-05 | kept-open | severity:minor,area:robotframework,v5-candidate:no,needs-repro | no | kept-open: bug, area:robotframework; +repro: REPRO-on-dev | low | | diff --git a/docs/triage/v5/repro-results.csv b/docs/triage/v5/repro-results.csv new file mode 100644 index 0000000000..e5644bbe21 --- /dev/null +++ b/docs/triage/v5/repro-results.csv @@ -0,0 +1,116 @@ +number,theme,bucket,repro_type,verdict,evidence_summary,repro_test_path,recommended_pass1,recommended_pass2_labels,verdict_source,notes +675,robotframework,feature-request,feature,REPRODUCED-on-dev,cumulusci/tasks/robotframework/robotframework.py does not configure traceback logging; rg 'traceback|format_exc|format_tb' in cumulusci/robotframework returns 0 matches. Default robot output shows only the exception's str(); full Python tracebacks require --loglevel TRACE or a custom listener which cumulusci does not provide.,,closed:stale-24mo,"cli-usability,robotframework,stale",dev,"Issue from 2018-07, last activity 2018-09, no activity for ~8 years. Two pragmatic workarounds exist today: (1) users can pass options=loglevel:TRACE to the robot task; (2) python keywords can wrap with traceback.format_exc() and log themselves. Workarounds make this low-priority. Close as stale." +710,scratch-org-config,feature,unit-pytest,REPRODUCED-on-dev,"The four+ default scratch org configs in cumulusci/cumulusci.yml:1559 cannot be disabled by a project. `merge_config` (cumulusci/core/utils.py:158, via dictmerge) drops ``None`` overrides, and BaseProjectKeychain._load_scratch_orgs (cumulusci/core/keychain/base_project_keychain.py:149) iterates every key unconditionally. Two XFAIL assertions added showing the issue's proposed `config_file: None` syntax has no effect today.",,keep-open,"enhancement,needs-spec",dev,"API question: should disabling use `config_file: None`, an explicit `disabled: true` flag, or sentinel removal? Tests assert end-state (`dev not in keychain.orgs`) so any of those implementations would pass." +733,cli,A,feature,REPRODUCED-on-v4.10.0,runtime.py:131-133 still raises ClickException with same hard error message; no interactive prompt path,,closed:stale-24mo,"enhancement,cli-usability,stale",v4.10.0,Stale 7yr feature request; confirm dry-run proposal stands +773,docs,feature-with-doc-component,doc-gap,REPRODUCED-on-dev,"cdcarter (2018) asked for tasks to declare ``return_values``/``result`` and for ``cci task info`` plus the web docs to surface them. On dev (1925a3083), ``BaseTask`` (``cumulusci/core/tasks.py:51``) only declares ``return_values`` as a runtime dict (line 64, populated as ``self.return_values = {}`` at line 92) - no declarative schema attribute. ``doc_task()`` in ``cumulusci/utils/__init__.py:354`` walks ``task_options`` and a free-form ``task_docs`` string but emits no ""Return Values"" / ""Returns"" section. The docs themselves admit the gap: ``docs/config.md:740-744`` literally says ""Current task return values are _not_ documented, so finding return values set by a specific task (if any) requires you to read the source code"". 8 years stale but still 100% accurate.",cumulusci/tests/triage/test_issue_773.py,keep-open,"area/docs,area/tasks,type/enhancement",dev,"Pass-2: still actionable. Fix sketch: add a class attr like ``return_values_schema: Dict[str, str]`` (key -> one-line description) on BaseTask, extend ``doc_task`` to render a ""Return Values"" RST section when the attr is non-empty, and backfill the common return-value-emitting tasks (PackageUpload, GithubRelease, PromotePackageVersion, etc.). Same convention can power web docs since web docs use the same ``doc_task``." +808,metadata-etl,B,bug,REPRODUCED-on-dev,UninstallPackaged._init_options (cumulusci/tasks/salesforce/UninstallPackaged.py:25) defaults the 'package' option to project__package__name only; project__package__name_managed is never consulted (compare InstallPackageVersion which does fall back through name_managed -> name -> namespace at install_package_version.py:75-79). uninstall_packaged_incremental therefore retrieves the wrong package when name_managed is set,,keep-open,"bug,good-first-issue",dev,"Static-analysis confirmation; live repro needs a packaging org with a managed package whose name differs from project__package__name. Minimal fix: in UninstallPackaged._init_options, do `self.project_config.project__package__name_managed or self.project_config.project__package__name`. Also revisit jlantz's 2018 follow-up about deprecating name_managed entirely.; Task 4 reverify: bug still present on origin/dev@1925a3083 (only commit touching root-cause file since v4.10.0 was ruff refactor 3d620762d)." +987,robotframework,user-error-or-stale,bug,NOT-REPRODUCED-on-dev,"Issue is Spring 19 Salesforce + Chromedriver 2.46 + Selenium ~3.x shadow-DOM-specific. Reporter explicitly stated workaround works (""I figured out a work-around. Use firefox with geckodriver. Everything works as intended now."") and the underlying Chrome/Selenium/Salesforce stack has changed substantially. cumulusci/robotframework/Salesforce.py:154 click_object_button still uses _jsclick but the upstream shadow-DOM bug it depends on no longer manifests the same way.",,closed:stale-24mo,"stale,user-error",dev,"Reporter found workaround within days; last meaningful activity 2022-06. Spring 19 UI no longer exists. 7 years stale, ineligible for repro without specific reproducible scenario." +1348,cli,A,feature,REPRODUCED-on-v4.10.0,No 'gitlab' or 'bitbucket' references anywhere in cumulusci/; ci_feature flow still uses github_parent_pr_notes/github_automerge_feature tasks,,closed:stale-24mo,"enhancement,stale,wontfix-candidate",v4.10.0,Architectural change scoped to GitHub only; stale 6yr +1350,cli,A,bug,NOT-REPRODUCED-on-v4.10.0,project_config.py:52-57 sets up synthetic 'tasks' namespace package; include_source() at line 662 calls _add_tasks_directory_to_python_path() which extends tasks.__path__ for each loaded source,,closed:not-reproducible-on-v4.10.0,fixed,v4.10.0,Original ModuleNotFoundError fixed via tasks namespace package; collision concern raised in 2022 comments is separate +1432,cli,A,bug,REPRODUCED-on-v4.10.0,core/tasks.py:186-196 _validate_options() only checks required; old-style task_options dict path silently accepts unknown keys (test passed). Pydantic Options class path now rejects extras.,,closed:stale-24mo,"bug,stale,partially-fixed",v4.10.0,Partially mitigated for tasks using new Pydantic Options class; old task_options dict still vulnerable +1769,bulkdata,A,bug,REPRODUCED-on-v4.10.0,"Test code-smell: `lookups[""Id""] = MappingLookup(name=""Id"", table=""accounts"", key_field=""sf_id"")` pattern still present in test_load.py:736 (was line 352 in 2020). davidmreed acknowledged ""horrible hack"" but never refactored. Pattern repeats at lines 754, 773, 801. Used to express UPDATE-on-Id dependency in `_expand_mapping` after_steps fixtures.",,closed:stale-24mo,"test-cleanup,low-priority",v4.10.0,"Issue is a test-fixture style question, never escalated to bug. 6 yrs old." +2013,bulkdata,A,bug,REPRODUCED-on-v4.10.0,"`create_table_if_needed` (utils.py:133-139) calls `Table(tablename, metadata, *fields)` then checks `inspector.has_table()` for the BulkDataException. SQLAlchemy raises `InvalidRequestError: Table 'X' is already defined for this MetaData instance` BEFORE the inspector check fires. Reproduced exact 2020 traceback verbatim with two MappingStep(sf_object=Account, table=Account) entries.",,keep-open,"bug,bulkdata,extract_dataset,error-handling",v4.10.0,"Trivial fix: try/except around Table() to convert to BulkDataException, or use `extend_existing=True`. Or detect the duplicate-table case in mapping_parser at validation time." +2096,bulkdata,A,bug,NOT-REPRODUCED-on-v4.10.0,REST DML (`step.py:778-784 RestApiDmlOperation._record_to_json`) calls `process_bool_arg` for boolean fields. `process_bool_arg` (core/utils.py:53-83) accepts the full Salesforce Data Loader spectrum: yes/y/true/on/1 -> True; no/n/false/off/0 -> False (case-insensitive). Tested all 20 spectrum values; all pass.,,closed:not-reproducible-on-v4.10.0,"resolved,bulkdata",v4.10.0,Fix predates v4.10.0; the spec referenced in the issue is now followed. +2126,keychain,feature,feature,NOT-REPRODUCED-on-dev,"Feature request for `encrypt_all_encryptable_fields` Metadata-ETL task using probabilistic Shield encryption. Author (davidmreed, 2022-01-28) wrote ""feature has been developed but is blocked by bugs in the underlying platform functionality"". No matching task exists in cumulusci/ today (grep for `encryptionScheme`/`encrypt_all_encryptable`/`probabilistic` returns 0 hits in cumulusci/). 5+ years stale, still labelled `blocked`. Not a keychain bug \u2014 theme misclassification (encryption-via-Shield, not encryption-at-rest).",,closed:stale-24mo,"area/metadata-etl,blocked",dev,"Theme mislabel: this is metadata-etl/Shield, not keychain. Pass-2: relabel before closing; if owner still wants it, keep-open with `blocked`." +2140,cli,A,feature,REPRODUCED-on-v4.10.0,runtime.py get_org() calls keychain.get_org which raises OrgNotFound; cli/org.py:530-531 just shows 'Org X does not exist'; no interactive prompt offering scratch configs,,closed:stale-24mo,"enhancement,cli-usability,stale",v4.10.0,Stale 5yr feature request +2153,ci-integration,A,feature,REPRODUCED-on-dev,"Code scan of cumulusci/tasks/github/merge.py shows _create_conflict_pull_request (lines 264-288) only calls self.repo.create_pull(...) for the new auto-generated ""Merge into "" PR. There is no call to comment on the original (source) PR or its branch's open PR. Repo-wide grep for ""create_comment"" / ""issue_comment"" in cumulusci/tasks/github returns no matches in production code (only in test fixture util_github_api.py). Feature is still unimplemented on v4.10.0.",,keep-open,"enhancement,github,merge-conflict",dev,"Small enhancement; could be implemented inside _create_conflict_pull_request after pull = self.repo.create_pull(...). Need design decision on how to find the ""original PR"" (search open PRs whose head==branch_name?).; Task 4 reverify: bug still present on origin/dev@1925a3083 (only commit touching root-cause file since v4.10.0 was ruff refactor 3d620762d)." +2325,bulkdata,A,feature,REPRODUCED-on-v4.10.0,"No `set_validation_rule_status` or `disable_validation_rules` task in cumulusci.yml. Analogs exist for triggers (`disable_tdtm_trigger_handlers`/`restore_tdtm_trigger_handlers` lines 738-747) and DuplicateRules (`set_duplicate_rule_status` -> tasks/metadata_etl/duplicate_rules.py). Implementation pattern is established: a `MetadataSingleEntityTransformTask` subclass with `entity = ""ValidationRule""` would satisfy this.",,keep-open,"enhancement,bulkdata,metadata_etl,good-first-issue",v4.10.0,Clear pattern to follow from SetDuplicateRuleStatus. Small implementation effort. +2402,cli,A,feature,REPRODUCED-on-v4.10.0,cli/flow.py:119-145 flow_run only has --delete-org flag; no --rebuild-org option; no rg matches anywhere,,closed:stale-24mo,"enhancement,stale",v4.10.0,Stale 5yr +2500,docs,feature-with-doc-component,doc-gap,REPRODUCED-on-dev,"prescod (2021) flagged that ``ignore_failure`` is not documented. On dev, the option is real and supported: ``Step.ignore_failure: bool = False`` (``cumulusci/utils/yaml/cumulusci_yml.py:45``), used in ``flowrunner.py:667`` as ``allow_failure=step_config.get(""ignore_failure"", False)``, and present in the JSON schema (``cumulusci/schema/cumulusci.jsonschema.json:149``). In the docs directory, the only mentions are (a) a single example YAML at ``docs/config.md:800`` (in a release-flow snippet) and (b) a one-line changelog blurb at ``docs/history.md:5993``. The ""Flow Configurations"" chapter has subsections for ""Add a Flow Step"", ""Skip a Flow Step"", ""Replace a Flow Step"", ""Conditionally Run a Flow Step"" (``when:``), but no parallel subsection for ``ignore_failure``. The follow-up GUS ticket [internal-ID-redacted] (davidmreed, 2022-03-28) was created but nothing landed.",cumulusci/tests/triage/test_issue_2500.py,keep-open,"area/docs,area/flows,good-first-issue",dev,"Pass-2: trivial documentation PR. Fix sketch: add a ``### Ignore a Failed Step`` (or ``Continue on Step Failure``) subsection under ``## Flow Configurations`` in ``docs/config.md``, parallel to ""Conditionally Run a Flow Step"". Cover: (1) what the option does (don't raise the task's exception; the flow continues with the next step); (2) interaction with ``result`` / ``return_values`` for downstream steps that conditioned on success; (3) when NOT to use it (silently swallowing failures hides regressions in CI); (4) link to the ``when:`` mechanism for branching instead of swallowing." +2505,bulkdata,A,feature,NOT-REPRODUCED-on-v4.10.0,"`MappingStep.soql_filter` field added (mapping_parser.py:120). `extract.py:142-147` applies it via `append_filter_clause` (extract.py:420). Also surfaced in extract-mapping generator (extract_mapping_file_generator.py:26 reads `where:` from declarations into `soql_filter`). Tests cover plain filter, filter prefixed with WHERE, and no-record-type variants (test_extract.py:1216,1248,1280).",,closed:feature-implemented,"resolved,bulkdata,extract_dataset",v4.10.0,WHERE-clause filtering for extraction is implemented via `soql_filter` per mapping step. +2506,bulkdata,A,feature,REPRODUCED-on-v4.10.0,"Snowfakery task DOES respect `get_debug_mode()` (snowfakery.py:241,355,385,565) and prints the working tempdir on each loop iteration. But the core bulk operations (extract.py, load.py, step.py) have no `--debug` mode that retains tempfiles or logs their paths. extract.py and step.py have ZERO matches for debug_mode/get_debug_mode/delete=False.",,keep-open,"enhancement,bulkdata,extract_dataset,load_dataset,debugging",v4.10.0,Partial: Snowfakery only. Could be extended to load_dataset/extract_dataset by wiring `get_debug_mode` and using TemporaryDirectory(delete=False) when set. +2507,cli,A,feature,REPRODUCED-on-v4.10.0,"No undo_insert task in repo; bulkdata/load.py and snowfakery have enable_rollback option but only triggers on error, not the requested ad-hoc undo",,closed:stale-24mo,"enhancement,stale,partially-fixed",v4.10.0,Partial mitigation via enable_rollback (rollback on error); standalone undo task still missing +2508,bulkdata,A,feature,REPRODUCED-on-v4.10.0,"No retry-only-failed-records feature in v4.10.0. `cci task list` shows zero retry-named tasks. There is an `enable_rollback` option on load_dataset (load.py:97-98, RollbackType enum at load.py:1051) but that performs the OPPOSITE: undoes successful inserts when failures occur. RowErrorChecker (utils.py:158) only logs/raises; it does not persist failed rows for retry.",,keep-open,"enhancement,bulkdata,load_dataset,reliability",v4.10.0,Distinct from rollback. Would require persisting failed-record CSV/SQL output and a new task that consumes it. +2667,auth,enhancement,cli-output,NOT-REPRODUCED-on-dev,"cumulusci/cli/org.py:204 already emits ""Connecting org using the {connected_app_name} connected app..."" before connecting. Implemented by commit 40520bee4 (2022-01-31, post-issue-filing). Existing tests at cumulusci/cli/tests/test_org.py:135 and :191 assert the connected-app name is present in CLI output for both default (""built-in"") and non-default (""other"") connected apps. All 10 org_connect tests pass on dev (1925a3083). Issue description requested ""Using connected_app 'xyzzy'""; current implementation produces functionally equivalent message.",,close-stale,"resolved,implemented",dev,"Originally requested by prescod 2021-06-08; davidmreed noted [internal-ID-redacted] covering on 2022-01-28. Implementation landed shortly after. davisagli's follow-up suggestions (storing connected_app on OrgConfig, configurable login URLs per connected app) are partially addressed: org_config.config[""connected_app""] is now stored (line 155). Login-URL-per-connected-app remains a separate concern not blocking close." +2697,cli,B,bug,INCONCLUSIVE-needs-scratch-slot,namespaced field is sourced from cci config not auto-derived from SFDX qa.json; keychain create_scratch_org defaults namespaced=False (base_project_keychain.py:74). Requires scratch org to confirm cli/org.py 'org info' output behavior matches reporter's expectation,,closed:stale-24mo,"bug,stale,needs-info",v4.10.0,User expectation conflicts with cci design; field is cci-controlled not derived from SFDX def. Scratch creation skipped (DevHub limit prudence); behavior likely unchanged +2826,metadata-etl,B,bug,REPRODUCED-on-dev,PackageXmlGenerator.parse_types (cumulusci/tasks/metadata/package.py:107) calls os.listdir(self.directory) without a pre-check; UpdatePackageXml._run_task (line 612) does not guard. Unit repro raises FileNotFoundError when path is missing rather than silently no-opping the way the issue title says deploy_unmanaged 'is supposed to',,keep-open,"bug,good-first-issue",dev,"Behavior unchanged from 2021. Smallest change: in UpdatePackageXml._init_task (or _run_task) skip with logger.info('No package directory at {path}; skipping') when not Path(path).exists(). Could also be done at the deploy_unmanaged flow level via a `when:` clause, e.g. when: project_config.has_package_directory.; Task 4 reverify: bug still present on origin/dev@1925a3083 (only commit touching root-cause file since v4.10.0 was ruff refactor 3d620762d)." +2951,bulkdata,A,bug,REPRODUCED-on-v4.10.0,"Loader has no special PricebookEntry sequencing logic. Within a single Insert PricebookEntry mapping step, records (whether targeting Standard Price Book or custom) are sent to Bulk/REST in mixed order, hitting STANDARD_PRICE_NOT_DEFINED. Mitigation: hardcoded_default_declarations.py:14-18 filters out Standard Price Book during extract_dataset by default, so the typical extract->load round-trip avoids this. But manually-authored mappings (like reporter's) still hit it.",,keep-open,"bug,bulkdata,load_dataset,pricebook,documentation",v4.10.0,"Could be addressed by either (a) auto-splitting PricebookEntry into two implicit steps (standard first, custom second), or (b) documenting that PricebookEntries against Standard pricebook must be in a separate, earlier mapping step." +2979,packaging,A,feature,REPRODUCED-on-v4.10.0,deploy task in cumulusci.yml still hardcodes path: src; default_package_path exists but is not wired into Deploy task,,keep-open,"severity:low,area:packaging,area:sfdx,state:needs-design",v4.10.0,default_package_path is wired only into create_package_version; would need backwards-compat design (per davisagli comment) +3015,cli,A,feature,REPRODUCED-on-v4.10.0,cli/org.py:519-543 org_remove always calls delete_org() if can_delete; no --keep-org or -o flag,,closed:stale-24mo,"enhancement,stale",v4.10.0,Stale 4yr; davisagli workaround (delete .org file directly) still applies +3024,cli,A,feature,REPRODUCED-on-v4.10.0,Flow groups in cumulusci/cumulusci.yml still appear in original order: Metadata Transformations first; Continuous Integration appears at position ~23. User-requested 'Org Setup' group does not exist (uses 'Setup' instead),,closed:stale-24mo,"enhancement,stale",v4.10.0,Stale 4yr cosmetic VS Code extension request +3137,metadata-etl,A,feature,REPRODUCED-on-v4.10.0,CustomObjectParser at package.py:443-461 still skips non-__c/__mdt/__e/__b objects; no opt-in option added to UpdatePackageXml,,keep-open,"severity:low,area:metadata-etl,type:enhancement,state:needs-design",v4.10.0,Maintainer agreed in 2022 it's by-design; needs design for an include_standard_objects option +3161,cli,A,feature,REPRODUCED-on-v4.10.0,flowrunner.py:317-320 _obfuscate_if_sensitive masks if task_options info.sensitive==True; partial implementation. No CLI-time hide flag for ad-hoc values like robot__vars; robot vars option not marked sensitive,,closed:stale-24mo,"enhancement,stale,partially-implemented",v4.10.0,Infrastructure exists for sensitive task option metadata; user's ad-hoc CLI -o hiding still missing +3165,metadata-etl,B,bug,REPRODUCED-on-v4.10.0,"_expand_package_xml_objects (which adds objects referenced by record_types to the retrieve package.xml) is only invoked from inside _expand_package_xml, which is gated on include_packaged_objects=True (update_profile.py:137-138 and 182). When include_packaged_objects is False (the default unless project's minimum_cumulusci_version >= 3.9.0), record_types referencing standard objects not already in admin_profile.xml (e.g. Case) are never added to the retrieve, the retrieved Admin profile lacks those recordTypeVisibilities, and _set_record_types raises TaskOptionsError 'Record Type X not found in retrieved Admin.profile'. Existing test test_init_options__include_packaged_objects asserts _expand_package_xml.assert_not_called() in that branch (test_ProfileGrantAllAccess.py:609-615) which proves the gating",,keep-open,bug,v4.10.0,"Smallest fix: in update_profile.py:137 always call self._expand_package_xml_objects(package_xml) (regardless of include_packaged_objects), and only call self._expand_package_xml when include_packaged_objects is True. _expand_package_xml_objects only walks the user-supplied record_types option; it does not need a Tooling API query." +3167,metadata-etl,B,feature,NOT-REPRODUCED-on-v4.10.0,"page_layout key on record_types is fully implemented in ProfileGrantAllAccess._set_record_types (update_profile.py:280-298). Documented in the record_types task option description (update_profile.py:32-34). Landed in PR #3243 (commit f2ff04bd5) in June 2022, well before v4.10.0",,close-as-implemented,enhancement,v4.10.0,Feature is shipped; documentation is in cci task info `update_admin_profile`. Could add a CHANGES note or a docs example showing the page_layout usage. +3283,bulkdata,A,bug,NOT-REPRODUCED-on-v4.10.0,"PR #3361 (commit b0bfb70e0, ""Support updates and upserts with blank dates represented by strings"") is in v4.10.0 (verified via `git merge-base --is-ancestor`). Fix at step.py:795-796: for UPDATE/UPSERT, empty strings are converted to None. Repro test confirms empty Birthdate -> JSON null for both UPDATE and UPSERT, and is dropped entirely for INSERT.",,closed:fixed-by-pr-#3361,"resolved,bulkdata,load_dataset",v4.10.0,"Reporter's own last comment (""Fixed in #3361"") confirms this; repro test now verifies behavior on v4.10.0 source." +3306,scratch-org-config,feature,no-test-feature-request,NOT-REPRODUCED-on-dev,"`cci org scratch ... --release preview` exists in cumulusci/cli/org.py:567 but `cci flow run` has no `--release`/`--preview` flag (cumulusci/cli/flow.py 119-150). Internally tracked as [internal-ID-redacted]; no PR landed. Issue is a never-implemented enhancement, not a regression.",,keep-open,"enhancement,needs-spec",dev,"Workaround today is `cci org scratch --release preview` then `cci flow run dev_org --org `. No test written - API/UX (--preview vs --release flag, semantics for non-scratch orgs) not yet specced." +3307,cli,A,feature,REPRODUCED-on-v4.10.0,cli/project.py project_init only renders internal Jinja templates from cumulusci/files/templates/project; no --template option exists,,closed:stale-24mo,"enhancement,stale",v4.10.0,Stale 4yr; user marked 'low priority/nice to have' +3320,metadata-etl,A,feature,NOT-REPRODUCED-on-v4.10.0,deactivate_flow task is shipped in cumulusci/cumulusci.yml:10-15 using ActivateFlow class with status:False,,closed:feature-implemented,"area:metadata-etl,type:enhancement,resolution:already-implemented",v4.10.0,Reporter likely missed that deactivate_flow exists; ActivateFlow.status:False does the work +3331,metadata-etl,A,bug,REPRODUCED-on-v4.10.0,metadata_map.yml line 46 maps assignmentRules folder to type AssignmentRule (singular); MDAPI requires AssignmentRules plural,,keep-open,"severity:medium,area:metadata-etl,type:bug,good-first-issue",v4.10.0,One-line fix in metadata_map.yml; reporter offered a PR; cross-cutting hint: autoResponseRules already plural in same file +3347,release-unlocked-beta-typeerror,C,code-only,NOT-REPRODUCED-on-v4.10.0,create_package_version.py:158-159 now raises TaskOptionsError(PERSISTENT_ORG_ERROR) early when org_config.config_file is None; replaces the cryptic TypeError reported. Fix landed in commit 2a9cadcb1 on 2023-10-12. Existing test at cumulusci/tasks/tests/test_create_package_version.py::TestPackageConfig::test_org_config validates the new behavior (passes on HEAD).,,close-with-comment,resolved-by-clear-error-message,v4.10.0,Cryptic TypeError replaced by clear actionable TaskOptionsError. Underlying limitation (cannot use persistent org for 2GP package upload) is documented now. +3349,bulkdata,A,bug,REPRODUCED-on-v4.10.0,"mapping_parser.py:177-179 still names recordtype tables as f""{self.sf_object}_rt_mapping"" (and ""_rt_target_mapping""). Two MappingStep entries for the same sf_object (e.g. Account business vs PersonAccount) collide on the same SQLite table when extracted/loaded together. load.py:552 and extract.py:259/393 both use the sf_object-derived names with no per-step disambiguation.",,keep-open,"keep-open,bug,area:bulkdata,severity:major,v5-candidate:yes",v4.10.0,"Has maintainer label wi-created . Real bug, fix would change get_source/destination_record_type_table to use self.table when present. Active community impact (Person Accounts + Business Accounts a common case)." +3353,bulkdata,B,feature,REPRODUCED-on-v4.10.0,snowfakery.py:159-162 validates recipe via Path(recipe).exists() with no SOURCE_NAME:path resolution. No call to project_config.sources / source_url anywhere in snowfakery.py. Recipe string is passed straight to Snowfakery as a filesystem path.,,keep-open,"keep-open,enhancement,area:bulkdata,severity:minor,v5-candidate:maybe",v4.10.0,"Community resurfaced this in 2024-08 (davidjray, jnesong). Workaround: cci org import. Fix would resolve SOURCE_NAME:path via project_config.get_source(name).fetch().path before Path() validation." +3360,bulkdata,A,feature,NOT-REPRODUCED-on-v4.10.0,"action: select was added by commit b15945203 (Aug 2024) - well before v4.10.0. select_utils.py + step.py SELECT branch + mapping_select.yml fixture confirm full implementation. select_options supports strategy/filter/priority_fields. This is exactly the requested ""read-only object lookup"" feature - populates the lookup table from existing org records without DML.",,closed:feature-implemented,"closed:feature-implemented,enhancement,area:bulkdata",v4.10.0,SELECT action lets you reference existing records by similarity / external-id without inserting. Documented in mapping_select.yml. Issue should be closed citing select feature. +3407,keychain,bug,type-bug,REPRODUCED-on-dev,"`BaseProjectKeychain.set_service` (base_project_keychain.py:202-209) annotates `service_config: ServiceConfig`, but `EncryptedFileProjectKeychain._load_service_files` (encrypted_file_project_keychain.py:717) calls it with a raw `str` (encrypted file body) plus `config_encrypted=True`. `_set_service` (line 583-605) explicitly branches on `config_encrypted` and stores the raw blob without ever constructing a ServiceConfig. Annotation is therefore wrong. Two-test repro xfails on dev: (a) annotation-vs-caller introspection; (b) runtime call with a string succeeds, contradicting the type.",cumulusci/tests/triage/test_issue_3407.py,keep-open,"area/keychain,good-first-issue,type/typing",dev,"Trivial fix: change annotation to `Union[ServiceConfig, str]` or `ServiceConfig | bytes | str` and update docstring. Optionally split into `set_service` (validated ServiceConfig) and `set_encrypted_service` (raw blob) for cleaner API." +3418,packaging,B,bug,INCONCLUSIVE-needs-cumulus-actions-workflow,cci has no auth:sfdxurl:store path; error originates from SFDO-Community/standard-workflows action; comment from davidmreed in 2022 indicated planned external fix,,unchanged,"needs-info,needs-repro",v4.10.0,"Bug is in the third-party github action (SFDO-Community/standard-workflows production-1gp.yml), not in cci. davidmreed promised to address externally; verify SFDO-Community fix landed before triaging." +3429,packaging,A,feature,REPRODUCED-on-v4.10.0,No CUMULUSCI_YML env var or --config-file CLI flag in v4.10.0; config_filename hardcoded; PR #3969 (extra-yaml-cli-flag) is in flight but not merged,,keep-open,"severity:medium,area:packaging,area:cli,state:in-progress",v4.10.0,PR #3969 on branch extra-yaml-cli-flag adds --extra-yaml + CUMULUSCI_EXTRA_YAML; not yet in v4.10.0; close once #3969 merges +3440,packaging,A,feature,REPRODUCED-on-v4.10.0,default_package_path in project_config.py:517 only honors first packageDirectory with default:true; no name-based lookup or multi-package warnings,,keep-open,"severity:low,area:packaging,area:sfdx,area:multi-package",v4.10.0,Same multi-package theme as #2979 and #3429; could be solved jointly with a multi-package config story +3441,packaging,A,feature,REPRODUCED-on-v4.10.0,version_base accepts None / 'latest_github_release' / literal version; no syntax to reset back to default after a flow override; CCI lacks generic null-override mechanism,,keep-open,"severity:low,area:packaging,area:flow-overrides,area:cli",v4.10.0,yippie's comment generalizes the request to a CCI-wide null-override feature; could be solved by a 'default' sentinel string in create_package_version._get_base_version_number +3446,packaging,B,bug,REPRODUCED-on-v4.10.0,_parse_version(None) raises NoneType.split when push_qa is run with --metadata_package_id but no --version/--version_id; user comment about Push API activation is a downstream concern,,keep-open,"bug,good-first-issue",v4.10.0,Two-part fix is wanted: (1) validate that version or version_id is required in _init_options; (2) wrap MetadataPackage SOQL in a check that surfaces a friendlier error if Push API is not activated on the org. +3464,docs,feature-with-doc-component,doc-gap,REPRODUCED-on-dev,"yippie (2022) asked for ALL ``cumulusci.yml`` ``project:`` keys to be defined with at least one sentence each in https://cumulusci.readthedocs.io/en/stable/config.html. On dev, the ``Project`` Pydantic model (``cumulusci/utils/yaml/cumulusci_yml.py:135``) declares 9 fields: ``name``, ``package``, ``test``, ``git``, ``dependencies``, ``dependency_resolutions``, ``dependency_pins``, ``source_format``, ``custom``. ``docs/config.md`` shows ONE example YAML block at line 281 covering only ``name`` + ``package``. Substring scan of the file: ``dependency_resolutions`` 0 hits, ``dependency_pins`` 0 hits - those are documented only in ``docs/dev.md`` (lines 499, 509, 535, 596, 724, 727), which is precisely the ""docs are scattered to the wind"" complaint in the issue. ``source_format`` is mentioned in flow examples but not as a project-level key reference. Sub-models (``Package``, ``Git``, ``Test``, ``DependencyResolutions``) have additional second-level fields with no reference at all in ``config.md``. jstvz acknowledged the gap in 2022.",cumulusci/tests/triage/test_issue_3464.py,keep-open,"area/docs,type/enhancement",dev,"Pass-2: still actionable; significant doc work. Fix sketch: add a ``## Project Configuration Reference`` (or expand the existing ``### Project Configurations`` at line 673) to ``docs/config.md`` that enumerates every ``project:`` key with one-sentence semantics. Source the enumeration from the ``Project``/``Package``/``Git``/``Test``/``DependencyResolutions`` Pydantic models so it stays in sync. Alternative low-effort path: generate the reference from the Pydantic ``__fields__`` + field descriptions at docs-build time (Sphinx extension) - this requires backfilling ``Field(description=...)`` calls on each model attribute, but produces an always-accurate page. Cross-link to the in-depth dependency pages in ``docs/dev.md`` rather than duplicating their content." +3466,packaging,A,feature,NOT-REPRODUCED-on-v4.10.0,RunApexTests in cumulusci/tasks/apex/testrunner.py exposes test_suite_names option (line 173); fully wired through _get_test_classes_from_test_suite_names,,closed:feature-implemented,"area:packaging,area:apex,state:resolved",v4.10.0,Implemented at some point after the 2022-12 request; [internal-ID-redacted] backlog item appears completed +3470,cli,A,feature,REPRODUCED-on-v4.10.0,cumulusci.yml:823 only ci_master flow defined; no ci_main alias. davidmreed acknowledged backlog need for flow aliasing first,,closed:stale-24mo,"enhancement,stale,inclusive-language",v4.10.0,Naming/inclusivity request; needs flow aliasing infra +3471,ci-integration,A,bug,REPRODUCED-on-dev,"cumulusci/tasks/github/merge.py line 251 still emits f""Merged {compare.behind_by} commits into branch: {branch_name}"" using github3's compare.behind_by. ""behind_by"" is computed by GitHub's compare-commits API and reflects how many commits the merged commit is behind the destination tip after the comparison ref-point; for some merges (e.g. when the merge target shares an ancestor at the same ref or when GitHub's compare picks the same head), this can return 0 even though the merge POST at line 249 succeeded and shipped a real commit. git log shows the line was last touched as part of the original MergeBranch implementation; no fix has landed since the issue was filed (2022-12). Pattern reported (README/test.txt vs source-code changes) is consistent with how GitHub's compare API treats ""effectively no-op"" merges where the file content already matches downstream content via merge-base.",,keep-open,"bug,github,merge,low-priority",dev,"Fix: replace compare.behind_by with len(list(compare.commits)) or report the actual merged commit SHA returned from self.repo.merge(...). Existing test_merge.py asserts ""Merged 1 commits"" in normal cases but has no test for the behind_by=0 case.; Task 4 reverify: bug still present on origin/dev@1925a3083 (only commit touching root-cause file since v4.10.0 was ruff refactor 3d620762d)." +3479,ci-integration,A,bug,NOT-REPRODUCED-on-v4.10.0,"Reported error ""Expecting value: line 1 column 1 (char 0)"" is the bare json.JSONDecodeError. In v4.10.0 cumulusci/core/config/sfdx_org_config.py lines 38-55 wrap both nonzero return codes AND JSON parse failures from `sfdx org display --json` in SfdxOrgException with explicit ""Failed to parse json from output. Exception: ... Output: ..."" message (wrapping landed in commit 017bc49f4 on 2020-11-24, predating the 2023-01 issue, so the symptom may have come from a different code path or be a regression in the user's specific 3.71.0 environment). davidmreed's only reply (2023-02-22) correctly diagnoses the root cause as shell-expansion of the multiline GHA secret (echo ${{ secrets.DEV_AUTH_URL }} without quotes). Reporter never responded. The issue is a user CI/workflow-config problem; cci's only contribution is the wrapper message, which is already in place on v4.10.0.",,closed:not-reproducible-on-v4.10.0,"awaiting-more-details,external-config",v4.10.0,"No reporter response in 3+ years; root cause is in user's GHA workflow (unquoted multiline secret), not cci. Improved SfdxOrgException wrapping is already in v4.10.0." +3485,cli,A,bug,REPRODUCED-on-dev,cumulusci/tasks/apex/testrunner.py:803-834 still writes a single tag with no declaration and no wrapper; matches user-reported invalid JUnit format exactly.,,keep-open,"bug,area:apex,good-first-issue",dev,Fix is mechanical: prepend XML declaration and wrap in . Old issue but bug still reproducible against v4.10.0.; Task 4 reverify: bug still present on origin/dev@1925a3083 (only commit touching root-cause file since v4.10.0 was ruff refactor 3d620762d). +3492,cli,A,feature,REPRODUCED-on-dev,cumulusci/cli/flow.py:152-162 splits -o key on '__' into exactly 2 parts; passing project__custom__attr would actually error with 'too many values to unpack'. No project-level option override path exists from -o.,,keep-open,"enhancement,area:cli",dev,Feature still missing. Would need new -p / --project-option flag or smarter -o parser.; Task 4 reverify: bug still present on origin/dev@1925a3083 (only commit touching root-cause file since v4.10.0 was ruff refactor 3d620762d). +3506,cli,A,feature,REPRODUCED-on-dev,"cumulusci/core/flowrunner.py:660-672 sets when=step_config.get('when') only on the task: branch. The flow: branch at lines 674-697 never reads when from step_config, so when on a flow-call step is silently ignored.",,keep-open,"enhancement,area:flows",dev,Confirmed silent-ignore behavior the user complained about.; Task 4 reverify: bug still present on origin/dev@1925a3083 (only commit touching root-cause file since v4.10.0 was ruff refactor 3d620762d). +3518,metadata-etl,A,bug,REPRODUCED-on-v4.10.0,picklists.py:177 stores str.lower bound method (not call) so if-default check at line 214 always truthy; every entry overrides record-type default,,keep-open,"severity:high,area:metadata-etl,type:bug",v4.10.0,Two-line fix at picklists.py:177 (call .lower()) plus rewrite assignment to avoid shadowing +3541,keychain,bug,bug,REPRODUCED-on-dev,"`BaseProjectKeychain.create_scratch_org` (base_project_keychain.py:77-79) builds `sfdx_alias = f""{project_config.project__name}__{org_name}""`. When `project__name` is None (no `project.name` in cumulusci.yml, or project not yet resolved during the eager `_load_scratch_orgs` pass invoked at keychain init, line 45), the resulting alias is the literal string ""None__dev"" \u2014 exactly matching the reporter. Repro builds a BaseProjectConfig without project.name, calls create_scratch_org, asserts alias doesn't contain literal 'None'; xfails on dev. Second test exercises the eager-load path via __init__.",cumulusci/tests/triage/test_issue_3541.py,keep-open,"area/keychain,area/scratch-orgs,bug",dev,Issue currently labelled `cannot-reproduce` \u2014 evidence here contradicts that label. Recommend removing it. Fix: guard against None (raise CumulusCIException with clear message OR fall back to org_name). Mind the upgrade path \u2014 existing keychains may already contain `None__*` aliases needing migration. +3542,packaging,B,bug,INCONCLUSIVE-needs-2GP-CI-pipeline,github_package_data uses self.project_config.repo_commit (local SHA) verbatim; if the upstream workflow recorded version_id under a merge-commit SHA from pull_request trigger; locally checked-out PR head SHA will not match; cci-side code path unchanged on v4.10.0,,unchanged,"needs-repro,2gp",v4.10.0,Root cause is workflow-vs-local SHA mismatch. May need a docs/CI fix in cumulus-actions/standard-workflows rather than cci. Could add a fallback: search by SHA on parent commits. +3543,metadata-etl,A,feature,REPRODUCED-on-v4.10.0,DxConvertFrom in cumulusci/tasks/dx_convert_from.py only exposes extra and src_dir options; no load_sfdx_project_paths or resolve_sfdx_package_dirs,,keep-open,"severity:low,area:metadata-etl,type:enhancement",v4.10.0,Reporter offered draft PR; could be folded into multi-path support across deploy/uninstall_packaged_incremental too +3544,update-admin-profile-person-accounts-namespaced,C,e2e-config-required,INCONCLUSIVE-needs-namespaced-project,Provisioned scratch org [scratch-org] from orgs/person_accounts.json (PersonAccounts + Communities + ContactsToMultipleAccounts features). update_admin_profile task ran SUCCESSFULLY against this non-namespaced person_accounts org on v4.10.0. Bug condition requires BOTH PersonAccounts AND namespaced:true on the project; CumulusCI itself has no project namespace so the second condition cannot be satisfied without registering a namespace in the configured DevHub. No code fix found referencing #3544 or [internal-ID-redacted] in update_profile.py / admin_profile.xml since 2023.,,needs-info,needs-namespaced-project,v4.10.0,Tracked internally as [internal-ID-redacted] per davidmreed comment 2023-02-22. Discovered adjacent latent bug at cumulusci/utils/__init__.py:229 where namespaced_org=True with no project namespace raises TypeError: unsupported operand type(s) for + 'NoneType' and 'str' - distinct from #3544 but worth a separate ticket. +3549,cli,A,feature,REPRODUCED-on-v4.10.0,"cumulusci/tasks/salesforce/Deploy.py exposes test_level/specified_tests but does not capture or write JUnit/JSON test output (no junit_output option, no _write_output for tests).",,keep-open,"enhancement,area:metadata-deploy",v4.10.0,Related to #3564. Reasonable feature ask; not implemented. +3561,metadata-etl,B,bug,NOT-REPRODUCED-on-v4.10.0,"Bug was reported by yippie (who is also the PR author). Fix landed in commit 56e10665e (PR #3566, May 2024) by the original reporter. RetrieveUnpackaged._init_options now stores file content under options['package_xml_content'] and leaves the file-path option intact (RetrieveUnpackaged.py:29-31), so the second _init_options invocation in MetaDeploy no longer corrupts the path",,close-as-fixed,,v4.10.0,Suggest closing with a back-reference to PR #3566 in case the original reporter never got back to flip the issue state. +3570,cli,A,feature,REPRODUCED-on-dev,cumulusci/core/flowrunner.py supports per-step ignore_failure (StepSpec.allow_failure) but has no concept of finally:/on_error:/cleanup: steps in a flow definition. No 'always-run' or post-error step type exists.,,keep-open,"enhancement,area:flows",dev,Big design change; unlikely to ship without strong demand.; Task 4 reverify: bug still present on origin/dev@1925a3083 (only commit touching root-cause file since v4.10.0 was ruff refactor 3d620762d). +3585,metadata-etl,A,bug,REPRODUCED-on-v4.10.0,PackageXmlGenerator on .object containing unbound xsi:nil='true' raises XML parse error; no namespace shim added,,keep-open,"severity:medium,area:metadata-etl,type:bug,sfdx-compat",v4.10.0,Fix: register xsi namespace before parsing or pre-strip xsi:nil attributes; also fixes #3692 class of issues +3587,packaging,B,feature,NOT-REPRODUCED-on-v4.10.0,PackageXmlGenerator only emits / when self.managed is truthy; no warning is logged when install_class set with managed=False; live cci task run update_package_xml --install_class X confirms silent drop,,keep-open,"enhancement,good-first-issue",v4.10.0,Feature still missing. Smallest implementation: in UpdatePackageXml._init_task; if install_class/uninstall_class set but not self.options.get('managed'); emit self.logger.warning(...). +3593,packaging,A,bug,REPRODUCED-on-dev,SFDXOrgTask._get_command in cumulusci/tasks/sfdx.py:50 unconditionally appends ' -o ' for ScratchOrgConfig; no opt-out option,cumulusci/tests/triage/test_issue_3593.py,keep-open,"severity:medium,area:packaging,area:sfdx,area:dx-task,state:needs-design",dev,Repro test confirms command becomes 'sf project convert source ... -o test@example.com'; sf cli rejects -o for subcommands like 'project convert source'; suggested fix is an opt-out option or auto-detect; Task 4 reverify: bug still present on origin/dev@1925a3083 (only commit touching root-cause file since v4.10.0 was ruff refactor 3d620762d). +3600,packaging,B,feature,NOT-REPRODUCED-on-v4.10.0,cci task option processing only substitutes $project_config. via PROJECT_CONFIG_RE; YAML loader uses plain yaml.safe_load with no env-var resolver; live install_managed --version '${MY_FAKE_VERSION}' shows literal string in interactive prompt,,keep-open,enhancement,v4.10.0,Feature not implemented. Scope likely larger than install_managed (would touch all task options); design decision needed: $env:VAR vs ${VAR} syntax; backwards-compat impact on literal $-strings. +3602,robotframework,feature-request,feature,REPRODUCED-on-dev,"cumulusci/robotframework/SalesforcePlaywright.py:60 open_test_browser signature is (self, size=None, useralias=None, wait=True, record_video=None) - no kwarg accepts browser options or **kwargs. cumulusci/robotframework/Salesforce.robot:103 ""Open Test Browser"" robot keyword only takes size/alias/wait/useralias. Chrome options are constructed by Get Chrome Options (Salesforce.robot:157) which hard-codes --disable-notifications with no extension hook.",,keep-open,"enhancement,robotframework,playwright,good-second-issue",dev,"Small-medium feature: add browser_options/extra_options kwarg to both Selenium and Playwright open_test_browser implementations, forward to webdriver options / new_browser kwargs. Pytest asserts signature exposes such a hook." +3603,dependencies,A,bug,REPRODUCED-on-v4.10.0,"Source ref/tag not found leaks raw `404 [No message]` from GitHubSource.resolve() (cumulusci/core/source/github.py L126). Repo-not-found cases (1,2) and `release:` strategy fail (case 5) are already wrapped in DependencyResolutionError; ref-not-found case (3) is not, and dep strategy fall-through (case 4) names the dep but not the strategies tried.",,keep-open,"theme:dependencies,error-handling,good-first-issue",v4.10.0,"Partial repro. Cases 1,2,5 were addressed by commit 738d4a8a4 (2021); cases 3 and 4 remain." +3604,dependencies,A,feature,REPRODUCED-on-v4.10.0,No CCI task exists to write computed dependencies into `sfdx-project.json`. Confirmed via `cci task list` and grep: zero references to writing/updating sfdx-project.json. [internal-ID-redacted] was filed by maintainer in 2023 but no implementation has shipped through v4.10.0.,,keep-open,"theme:dependencies,enhancement,wi-created",v4.10.0,Pure feature request; gap persists. wi-created label already present. +3605,packaging,A,feature,NOT-REPRODUCED-on-v4.10.0,PackageUpload (upload_production backing class) exposes major_version and minor_version options (cumulusci/tasks/salesforce/package_upload.py:39-46); _validate_versions handles major bump,,closed:fixed-by-pr-#3651,"area:packaging,area:1gp,state:resolved",v4.10.0,Implemented in commit 87b94440e (PR #3651 'Deploy Major and Minor Version option in upload_production task') +3607,cli,A,bug,INCONCLUSIVE-needs-org-with-managed-package,"Code path traced in cumulusci/tasks/apex/testrunner.py: retry_failures regex compiled at line 209-222, _is_retriable_failure (line 405) checks Message and StackTrace via re.search. Repro test at _(repro evidence; see narrative)_ confirms 'UNABLE_TO_LOCK_ROW' regex DOES match the user's quoted message in pure Python. Likely user-side: wrapped exception so Message lacks the literal token, or managed-class symbol-table skip at line 448-452.",,closed:stale-24mo,"bug,area:apex,needs-info",v4.10.0,Code logic is correct as written. To definitively reproduce we need a managed package and an org producing the exact failure shape. 30+ months no follow-up from reporter. +3609,cli,A,bug,INCONCLUSIVE-needs-live-cli-test,"cumulusci/tasks/sfdx.py is now a thin shell wrapper around 'sf {command}' (SFDX_CLI = 'sf' in v4.x; was 'sfdx' in 3.76.0 when reported). The user's syntax 'plugins:install ...' is sfdx-style with colon; 'sf' uses 'plugins install' (space). Underlying timeout originates from the sfdx/sf CLI itself, not CCI.",,closed:stale-24mo,"bug,upstream:sf-cli",v4.10.0,"Not a CCI bug. CCI faithfully shells out. Old issue, CLI changed substantially since." +3610,apex,Fixed-on-dev,negative-test,NOT-REPRODUCED-on-dev,Fixed by PR #3681 (commit 84389d998); cumulusci/tasks/apex/testrunner.py:500-510 handles None method name; regression tests already exist on dev,,closed:pr-resolved-#3681,,dev,"Fixed by PR #3681 (commit 84389d998b4783ddd2ff062f486a2366709cac27, ""Handling exception when the Tooling API returns a test result with a null method name""). cumulusci/tasks/apex/testrunner.py lines 500-510 detect None in method_names, remove the None key, enqueue the affected class for retry, and bump counts['Retriable']. Regression tests test_run_task_None_methodname_fail / test_run_task_None_methodname_pass pass on origin/dev@1925a3083." +3612,cli,A,feature,NOT-REPRODUCED-on-v4.10.0,"Issue is about the SFDO-Tooling/cci-vscode VSCode extension repo, not CumulusCI itself. Out of scope for this repository.",,closed:not-reproducible-on-v4.10.0,"enhancement,wontfix,wrong-repo",v4.10.0,Should be filed against SFDO-Tooling/cci-vscode. +3613,metadata-etl,B,bug,REPRODUCED-on-v4.10.0,Live repro: `cci task run add_page_layout_fields --org [scratch-org] -o api_names Account` -> 'Cannot find metadata file ...layouts/Account.layout' from MetadataSingleEntityTransformTask (base.py:332). Same task with correct API name format `Account-Account Layout` succeeds. Underlying functionality works; the user-visible bug is the unhelpful error when the api_name does not match the Layout file naming convention -,,improve-error-message,"bug,good-first-issue",v4.10.0,"Original report has no log of the exact api_names value passed; the cropped screenshot only shows 'page layouts'. Two improvements would help: (1) in _transform; if path does not exist, log the list of files actually retrieved into source_metadata_dir to help users identify naming mismatch; (2) in AddFieldsToPageLayout._init_options; warn when api_name does not contain '-'." +3615,dependencies,A,bug,NOT-REPRODUCED-on-v4.10.0,"`--resolution_strategy preproduction` is documented in cumulusci.yml as an alias for `latest_release` (which contains [tag, latest_release, unmanaged] and intentionally omits `latest_beta`). User wanted `include_beta`. Tests confirm preproduction == production == latest_release stack. Working as documented.",,closed:not-reproducible-on-v4.10.0,"theme:dependencies,docs",v4.10.0,"Naming is confusing -- ""preproduction"" sounds like ""beta-friendly"" but it isn't. Docs-improvement candidate; no code bug." +3618,cli,A,feature,REPRODUCED-on-dev,"cumulusci/cli/org.py:519-545 (org_remove) and 605-625 (org_scratch_delete) accept a single org_name argument via orgname_option_or_argument. No comma-separated list, no batch mode.",,keep-open,"enhancement,area:cli,good-first-issue",dev,Modest feature; could be implemented by accepting nargs=-1 or comma-split arg.; Task 4 reverify: bug still present on origin/dev@1925a3083 (only commit touching root-cause file since v4.10.0 was ruff refactor 3d620762d). +3619,dependencies,A,bug,REPRODUCED-on-v4.10.0,"Two-part bug confirmed. (A) `parse_pins()` raises DependencyParseError if a `dependency_pins` entry includes `password_env_name:` -- because GitHubDependencyPin only declares `github` and `tag`. (B) When a dynamic dep has `password_env_name` and a pin matches, `pin.pin()` calls GitHubTagResolver().resolve() directly, bypassing resolve_dependency()'s password-propagation logic; resulting `package_dependency.password_env_name` is None.",,keep-open,"theme:dependencies,bug",v4.10.0,Strong repro. Fix needs both (A) add password_env_name field to GitHubDependencyPin and (B) propagate it onto package_dependency in pin.pin() -- mirroring resolvers.py L644-654. +3649,bulkdata,A,feature,REPRODUCED-on-v4.10.0,"update_data.py:184 and :211 both pass api_options={} hardcoded to get_dml_operation. No task option exposes bulk job concurrency mode (Serial/Parallel). step.py BulkApiDmlOperation honors api_options[""bulk_mode""] but UpdateData never sets it. Other tasks (snowfakery, load_dataset) DO let users pick bulk_mode; update_data is the gap.",,keep-open,"keep-open,enhancement,area:bulkdata,severity:minor,good-first-issue,v5-candidate:yes",v4.10.0,Author offered to contribute. Small fix: add bulk_mode option to UpdateData.task_options and pipe it through to api_options. Mirrors LoadData pattern. +3663,cli,A,feature,REPRODUCED-on-dev,cumulusci/core/flowrunner.py:510-516 _run_step builds the when Jinja context from only project_config and org_config. No prior-task return values are exposed to the when expression.,,keep-open,"enhancement,area:flows",dev,Would need to thread results dict (or ^^ resolver) into the when context.; Task 4 reverify: bug still present on origin/dev@1925a3083 (only commit touching root-cause file since v4.10.0 was ruff refactor 3d620762d). +3692,metadata-etl,A,bug,REPRODUCED-on-v4.10.0,digitalExperiences key absent from metadata_map.yml; PackageXmlGenerator raises MetadataParserMissingError on Enhanced LWR sites,,keep-open,"severity:medium,area:metadata-etl,type:bug",v4.10.0,Add digitalExperiences/digitalExperienceConfigs entries to metadata_map.yml; needed for Winter '24+ LWR sites +3699,bulkdata,A,feature,REPRODUCED-on-v4.10.0,"extract.py _soql_for_mapping does not append ORDER BY. mapping_parser MappingStep has no order_by/sort field. soql_filter however allows arbitrary trailing SOQL so users CAN write soql_filter: ""Name != 'X' ORDER BY CreatedDate"" - this works because append_filter_clause just concatenates after WHERE. So a workaround exists; an explicit field would be a UX win.",,closed:stale-24mo,"closed:stale-24mo,enhancement,area:bulkdata",v4.10.0,Workaround via soql_filter ORDER BY makes this lower priority. Author hasn't followed up. Closing as stale is fine; can reopen if explicit order_by becomes a v5 ask. +3700,bulkdata,A,bug,REPRODUCED-on-v4.10.0,"mapping_parser.py:373-377 _get_required_permission_types returns (""updateable"", ""createable"") for any UPSERT. Master-detail lookup fields in Salesforce are createable but NOT updateable, so _check_field_permission rejects them, raising ""Field xxx__c does not have the correct permissions ('updateable', 'createable') for this operation."" Repro test confirms with simulated MD describe.",,keep-open,"keep-open,bug,area:bulkdata,severity:major,good-first-issue,v5-candidate:yes",v4.10.0,"Real bug, applies to any upsert involving an MD child. Fix should detect master-detail fields (relationshipName + cascadeDelete) and require only createable for those, OR loosen the upsert lookup-field check to ""createable OR updateable""." +3701,bulkdata,A,feature,REPRODUCED-on-v4.10.0,"mapping_parser.py:171/190/228/241/422 special-case ""Id"" - it always represents the SF Id and goes to sf_id-typed columns. There is no mechanism to make a different field (e.g. an external-id like BCM_Unique_Id__c) act as the row primary key in the extracted SQLite. The ""Id : OtherField"" mapping the user wrote is interpreted as ""extract SF Id into column OtherField"", not ""make OtherField the primary key"".",,closed:stale-24mo,"closed:stale-24mo,enhancement,area:bulkdata",v4.10.0,"Closely tied to #3699 (sort/diff motivation). Author hasn't followed up. Workaround: extract SF Id into a chosen column, then post-process. The deeper PK-replacement feature would touch many places." +3717,ci-integration,A,bug,INCONCLUSIVE-needs-cumulus-actions-workflow,"cumulusci/core/config/project_config.py repo_info property (lines 220-255) only auto-detects from environment when CUMULUSCI_AUTO_DETECT is set, and only handles Heroku CI (HEROKU_TEST_RUN_ID/BRANCH/COMMIT_VERSION). Repo-wide grep for ""GITHUB_REF"" / ""GITHUB_HEAD_REF"" / ""GITHUB_SHA"" / ""GITHUB_ACTIONS"" returns ZERO matches in cci source. So when run inside a GitHub Actions job the only way for repo_branch to be populated is (a) CUMULUSCI_REPO_BRANCH env var set by the workflow, or (b) git inference via current_branch(self.repo_root). On push events GHA checks out a detached HEAD, so (b) returns None. The reporter's symptom (repo_branch=None for push-triggered ci_feature, but works on workflow_dispatch where the branch ref is checked out by name) is exactly what this code path produces. The fix lives in the cumulus-actions/standard-workflows YAMLs (set CUMULUSCI_REPO_BRANCH from github.event.ref or github.head_ref before invoking cci) - not in the cci codebase. Same precedent as #3418.",,unchanged,"external-config,cumulus-actions,needs-info",v4.10.0,"Mirrors #3418 precedent (INCONCLUSIVE-needs-cumulus-actions-workflow). Could optionally be addressed inside cci by adding GitHub Actions auto-detection in repo_info (similar to Heroku block), reading GITHUB_REF/GITHUB_SHA - that would be a separate enhancement." +3721,packaging,A,feature,REPRODUCED-on-v4.10.0,create_package_version.py:184 still defaults version_name to literal 'Release'; upload_production hardcodes name: Release in cumulusci.yml:685,,keep-open,"severity:low,area:packaging,area:1gp,area:2gp",v4.10.0,muselab-d2x fork commit 7aaf348f3 implements jinja2 templating for PackageUpload; not merged upstream; would need port + design for create_package_version too +3734,packaging,B,bug,REPRODUCED-on-v4.10.0,PackageUpload._validate_versions SOQL ORDER BY ... PatchVersion DESC; ReleaseState DESC LIMIT 1 returns a Beta patch (e.g. 6.13.1 Beta) as latest; then minor_version is set to the patch's MinorVersion (13); colliding with already-Released 6.13 server-side,,keep-open,bug,v4.10.0,User's own analysis in last 3 comments is correct; current 'cannot-reproduce' label is stale; should be removed. Fix candidates: filter out Beta+Patch from the latest-version query; or filter ReleaseState='Released' for minor-detection and use a separate query for Beta. +3745,packaging,B,code-review,NOT-REPRODUCED-on-v4.10.0,latest_beta resolver looks up GitHub Releases (include_beta strategy) per install_package_version.py L96-100; reporter ran create_package_version standalone without release_2gp_beta which publishes the beta tag. Working as designed; reporter accepted the explanation 2024-02-13 and indicated closure intent.,,closed:stale-24mo,n/a,v4.10.0,"No code defect; doc-improvement opportunity. Original closed:stale-24mo proposal stands. Could optionally add closed:not-a-bug if that vocabulary exists, but stale-24mo is fine." +3746,packaging,B,code-review,REPRODUCED-on-dev,"create_package_version._get_base_version_number SOQL at L535-541 of cumulusci/tasks/create_package_version.py selects highest Package2Version with no IsDeprecated filter. The same file at L297 DOES filter IsDeprecated=FALSE for Package2 lookups, confirming the omission at L535 is asymmetric and matches the report verbatim.",,kept-open,"severity:medium,area:packaging,target:v4-patch,needs-fix-trivial",dev,Trivial 1-line fix (add 'AND IsDeprecated = false' to the SOQL WHERE clause). Currently proposed closed:stale-24mo; recommend flip to kept-open + target:v4-patch given low fix cost and clear customer impact (wrong version numbers on next package version create after a delete).; Task 4 reverify: bug still present on origin/dev@1925a3083 (only commit touching root-cause file since v4.10.0 was ruff refactor 3d620762d). +3754,cli,A,feature,REPRODUCED-on-v4.10.0,cumulusci/cli/utils.py:65-79 hardcodes 'https://pypi.org/pypi/cumulusci/json' with no env-var override or config flag. check_latest_version (line 82) cannot be disabled or redirected.,,keep-open,"enhancement,area:cli",v4.10.0,"User-suggested options (env flag to disable, custom URL) are all viable." +3758,packaging,A,bug,REPRODUCED-on-v4.10.0,push_upgrade_org flow in cumulusci/cumulusci.yml:1161-1177 still calls 'flow: config_qa' as final step; should be config_managed per bug report,cumulusci/tests/triage/test_issue_3758.py,keep-open,"severity:medium,area:packaging,area:flows,good-first-issue",v4.10.0,Single-line YAML fix; both flows have same steps so behavior is currently equivalent but semantically wrong +3762,metadata-etl,B,bug,closed:duplicate-of-#3544,"Reporter (noahisapilot) explicitly self-identifies as duplicate of #3544 in their first comment on 2024-03-06. Both report the same root cause: update_admin_profile fails on a namespaced scratch org with PersonAccounts because the retrieved profile contains 'Account.Business_Account' record type with no namespace, but the namespace gets injected onto recordType references. The reporter's analysis even links the offending line at update_profile.py L238 (now L236 in v4.10.0)",,close-as-duplicate,,v4.10.0,Self-confirmed duplicate. No live repro performed per dup-confirm protocol. Canonical #3544 is still OPEN at v4.10.0 with a 'wi-created' label . +3768,bulkdata,B,bug,REPRODUCED-on-v4.10.0,"snowfakery.py architecturally creates a separate working dir per batch via shutil.copytree(template_path, data_dir) (queue_manager.py:322). _cleanup_object_tables (snowfakery.py:721) drops every non-sf_ids table from the template before it is copied to subsequent batch dirs, so just_once Account rows are gone from batch_2+. random_reference: Account in batch_2+ would resolve only against rows generated within that batch; with just_once: true, no Accounts are generated, so the random_reference cannot pick anything. The user's observed first-batch-works / later-batches-don't matches this exactly.",,keep-open,"keep-open,bug,area:bulkdata,severity:major,v5-candidate:maybe",v4.10.0,Mixed CCI/Snowfakery responsibility. Could be fixed in CCI by preserving just_once-referenced object data (not just sf_ids) in the template carried to subsequent batches. Coordination with snowfakery dev branch likely needed. +3771,metadata-etl,A,bug,REPRODUCED-on-v4.10.0,transforms.py transform_xpath() wraps tags in *[local-name()=...] but leaves predicate inner refs (e.g. price>40) namespace-bound; PR #3772 from leboff fork not merged,,keep-open,"severity:medium,area:source-transforms,type:bug,has-pr",v4.10.0,PR #3772 from external fork rewrites approach to strip xmlns; consider rebasing or implementing reporter's suggested 'remove xmlns then re-add' approach +3773,metadata-etl,A,bug,REPRODUCED-on-v4.10.0,retrieve_profile_api.py _queries_retrieve_permissions never queries FieldPermissions table; only ObjectPermissions/SetupEntityAccess/PermissionSetTabSetting,,keep-open,"severity:medium,area:retrieve-profile,type:bug",v4.10.0,Architectural gap; field perms on standard objects without object perms are silently dropped; would benefit from org-side end-to-end verification but code evidence is conclusive +3849,python-modernization,Active-regression,import-only,REPRODUCED-on-dev,pyproject.toml pins selenium<4 + robotframework-seleniumlibrary<6 with no urllib3<2 cap; fresh pip resolves urllib3>=2 and breaks selenium 3.141.0,cumulusci/tests/triage/test_issue_3849.py,keep-open,"needs:dependency-modernization,target:v5",dev,"pyproject.toml still pins selenium<4 and robotframework-seleniumlibrary<6 (lines 50, 54) with no urllib3 upper bound; requests permits urllib3<3,>=1.26 so a fresh pip install picks urllib3>=2.x. Selenium 3.141.0's connection pool uses the pre-2.0 Timeout sentinel API and breaks at Robot import. The local .venv survives only because uv.lock pins urllib3==1.26.20. The modernization (drop selenium<4 / robotframework-seleniumlibrary<6 OR add explicit urllib3<2) has not landed." +3852,cli,A,bug,REPRODUCED-on-v4.10.0,"pyproject.toml:52 still pins 'sarge' unconstrained; installed sarge 0.1.7.post1 lacks Capture.flush (verified: hasattr(sarge.Capture,'flush')==False). cumulusci/core/config/sfdx_org_config.py:212 still calls self.sfdx_info inside refresh_oauth_token, which triggers the AttributeError on Python 3.13 logging path.",,keep-open,"bug,upstream:sarge,py313",v4.10.0,"Per maintainer note in thread: cosmetic only (no functional impact), waiting on sarge 0.1.8 release. Could pin sarge to a git rev (gabrielrholl's note) but not preferred." +3854,cli,A,bug,REPRODUCED-on-v4.10.0,cumulusci/tasks/bulkdata/extract.py:371-374 still raises ConfigError 'Total mapping operations (X) do not match total non-empty rows (Y) for lookup_key' identical to user report. Validation introduced in PR #3741 / commit 2c5d0056e per swirkens' comment is still active in v4.10.0.,,keep-open,"bug,area:bulkdata,regression",v4.10.0,Workaround in thread: downgrade to 3.84.1. Real fix needed for polymorphic lookups. +3873,robotframework,feature-request,feature,REPRODUCED-on-dev,"cumulusci/robotframework/Salesforce.py and SalesforcePlaywright.py both subclass BaseLibrary (base_library.py) which imports cumulusci-internal services (CumulusCI, SalesforceAPI); the libraries cannot be used standalone without a CumulusCI project context",,keep-open,"enhancement,robotframework,scope-large",dev,Large architectural ask. No PR submitted in 16 months. Would require decoupling FakerMixin/BaseLibrary/Salesforce from cumulusci.core. No concrete API to test; tagging as feature-still-missing. Reasonable to keep open for future Copado-QForce-style refactor or close as wont-fix if scope is out-of-charter. +3884,packaging,B,partial-runtime,INCONCLUSIVE-needs-project-with-managed-deps,"Source review: PackageNamespaceVersionDependency.install (dependencies.py L437-475) and PackageVersionIdDependency.install (L499-528) BOTH guard with skip-if-already-at-this-or-newer-version logic. CumulusCI repo itself has no project__dependencies block, so end-to-end dev_org rerun cannot exercise the path. Likely the report conflates 'Resolving dependencies...' log noise or unmanaged-metadata redeploys with reinstalls.",,closed:missing-fields,n/a,v4.10.0,"Original closed:missing-fields (rule 3 missing cci-version) is appropriate. Source review shows skip logic is in place; report may reflect unmanaged-metadata redeploy or user perception of log output. Without a customer project to reproduce on, no further action." +3886,dependencies,A,bug,REPRODUCED-on-v4.10.0,"Warning ""Optional dependencies are missing... cumulusci[select]"" emits at import time of cumulusci/tasks/bulkdata/select_utils.py whenever numpy/pandas/annoy/sklearn aren't installed. extract.py -> mapping_parser/step -> select_utils import chain triggers it on every extract_dataset run, even when no select strategy is configured. Behavior added in PR #3858 (89a5b5ddb) and unchanged through v4.10.0. Extraction itself works fine; only the noise persists.",,keep-open,"theme:dependencies,bulkdata,ux,log-noise",v4.10.0,"Dual-themed (bulkdata + dependencies); classified under dependencies per the bundle instructions. Fix candidates: defer warning until select strategy is actually used, or downgrade to debug-level." +3889,packaging,A,feature,REPRODUCED-on-v4.10.0,UninstallPackage only accepts namespace (1GP); UninstallPackageZipBuilder uses InstalledPackage destructive changes; no 04t/SubscriberPackageVersion-based uninstall task,,keep-open,"severity:medium,area:packaging,area:2gp,area:unlocked-package",v4.10.0,sf cli 'package uninstall -p 04t...' is the underlying API; new UninstallPackageVersion task could wrap Tooling API SubscriberPackageVersion delete +3899,packaging,B,partial-runtime,INCONCLUSIVE-needs-1gp-packaging-org,"unschedule_apex (cumulusci.yml L646-651) sends 1 line of trivial Apex via Tooling API: 'for (CronTrigger t : [SELECT Id FROM CronTrigger]) { System.abortJob(t.Id); }'. Ran cleanly on scratch org [scratch-org]. Reporter's error references Salesforce platform-internal Java classes (system.scheduler.cron.JobType, common.udd.constants.CronJobTypeEnum) - root cause is upstream Salesforce platform NPE, not CCI.",,kept-open,"severity:minor,area:packaging,external/upstream-salesforce,v5-candidate:no",v4.10.0,CCI sends correct trivial Apex; the System.UnexpectedException originates inside Salesforce's scheduler subsystem. Cannot fix in CCI. Recommend annotating with external/upstream-salesforce label (or equivalent) and considering close-as-not-our-bug after a brief look for any Salesforce known-issue reference. +3902,install-managed-security-type-04t,C,code-only,INCONCLUSIVE-needs-managed-package-04t,v4.10.0 install_package_version.py:162-167 routes 04t versions through PackageVersionIdDependency.install -> install_package_by_version_id -> _install_package_by_version_id which posts {'SecurityType': options.security_type} to the Tooling API PackageInstallRequest (package_install.py:170). Verified runtime serialization: SecurityType.ADMIN serializes to JSON 'NONE'. Both 04t and namespace+version paths pass security_type identically. No code-side defect found.,,needs-info,needs-managed-package-fixture,v4.10.0,"Could not provision a real managed package 04t to validate runtime API behavior. Code path is correct; observed user behavior (tab visible to non-admins) likely originates from Salesforce Tooling API treatment of SecurityType=NONE for upgrades or from package metadata, not from CumulusCI." +3910,scratch-org-config,bug,unit-pytest,REPRODUCED-on-dev,"ScratchOrg.namespaced declared as ``str`` in cumulusci/utils/yaml/cumulusci_yml.py:150; auto-generated cumulusci/schema/cumulusci.jsonschema.json:424 has ""type"": ""string""; ScratchOrg.parse_obj({""namespaced"": True}) silently coerces to ""True"" string. Open PR #3911 fixes both. Six XFAIL assertions added.",,keep-open,"bug,has-open-pr",dev,Fixing the schema alone is insufficient because make schema regenerates it from the Pydantic model. PR #3911 correctly updates both files; recommend nudging it through review. +3929,packaging,B,runtime,NOT-REPRODUCED-on-v4.10.0,"Ran create_community with name=TestWebsite template='Customer Service' url_path_prefix=testwebsite against scratch org [scratch-org] (orgs/dev.json with Communities feature). Community 0DBRK000000QtNR4A0 created in ~117s with normal polling escalation (1->2->3->...->8s) and exited the poll loop cleanly - no 300s timeout, no retry. Matches OP comment 2025-10-22 that the underlying SF CLI/server-side issue is fixed.",,closed:not-reproducible-on-v4.10.0,n/a,v4.10.0,Currently kept-open with needs-repro label; recommend flip to closed:not-reproducible-on-v4.10.0 (NEW vocabulary per spec amendment). Confirmed working end-to-end. Original cause was upstream SF CLI/Communities API bug now fixed. +3931,metadata-etl,B,bug,REPRODUCED-on-v4.10.0,Unit repro (with synthetic profile XML containing a layoutAssignments element that has a child but no child) raises 'AttributeError: NoneType object has no attribute text' at update_profile.py:291 inside _set_record_types. The buggy line is `if elem.find('recordType').text == rt['record_type']:` which assumes every layoutAssignments element has a recordType child; this is not true (a layoutAssignments without recordType applies to records without a record-type binding),,keep-open,bug,v4.10.0,Reported on cci 4.6.0 and reproduces unchanged on 4.10.0. Minimal fix at update_profile.py:290-293: change to `rt_elem = elem.find('recordType'); if rt_elem is not None and rt_elem.text == rt['record_type']: ...`. Also worth scanning sibling code in _set_record_types for similar None-deref patterns on optional children. +3936,bulkdata,B,bug,INCONCLUSIVE-needs-flaky-network,"salesforce_api/utils.py get_simple_salesforce_connection (lines 30-43) constructs Salesforce() with no timeout kwarg and only retries 502/503/504 via Retry(total=5). No CCI-side option exposes connect/read timeout for SF API calls. The reported error ""HTTPSConnectionPool ... Read timed out. (read timeout=None)"" with timeout=None usually means the proxy/VPN closed the socket; CCI cannot mitigate without exposing a configurable timeout AND a retry policy for read timeouts.",,unchanged,"keep-open,bug,area:bulkdata,severity:major,needs-repro,v5-candidate:yes",v4.10.0,Already kept-open by maintainer (recent 2025-12-03). Environment-specific reproduction (corporate VPN). Confirmed structural gap: no exposed timeout option. v5 candidate: add timeout option + read-timeout retry to get_simple_salesforce_connection and to bulk job HTTP polling. +3938,metadata-etl,A,bug,REPRODUCED-on-v4.10.0,rest_deploy.py _monitor_deploy_status (line 119) returns silently on Failed; __call__ also only logs on non-201 status_code; no exception raised,,keep-open,"severity:critical,area:rest-deploy,type:bug,silent-failure",v4.10.0,CRITICAL: rest_deploy:True silently passes failed deploys; flows continue thinking deploy succeeded. Recently filed (2025-12-16). Likely affects MetaDeploy plans +3939,metadata-etl,A,bug,REPRODUCED-on-v4.10.0,metadata.py BaseMetadataApiCall.__call__ wraps every Exception from _process_response (incl. ApexTestException at line 540) in MetadataParseError losing original message,,keep-open,"severity:high,area:salesforce-api,type:bug,error-handling",v4.10.0,"Fix: re-raise CCI exception classes (MetadataApiError, MetadataComponentFailure, ApexTestException) without wrapping. Also recently filed; same reporter as #3938; both block production deploys" +3951,metadata-etl,B,bug,REPRODUCED-on-v4.10.0,Live repro: `cci task run set_duplicate_rule_status -o api_names Standard_Rule_for_Leads_with_Duplicate_Contacts -o active False` -> 'Cannot find metadata file .../duplicateRules/Standard_Rule_for_Leads_with_Duplicate_Contacts.duplicateRule'. Same command with the canonical API name format `Lead.Standard_Rule_for_Leads_with_Duplicate_Contacts` succeeds end-to-end (extract -> transform -> deploy -> Success). The functional task is not actually broken; the bug is the unhelpful error when the user omits the Object prefix that the Metadata API requires for DuplicateRule,,improve-error-message,"bug,good-first-issue,documentation",v4.10.0,"Two improvements: (1) update set_duplicate_rule_status docs and task option help to show that api_names need the . format; (2) in MetadataSingleEntityTransformTask._transform (base.py:332), when the file is missing, list the files actually retrieved so users can spot the naming gap. Could also pre-validate api_names contain a dot for entity types that require it." +3953,metadata-etl,B,bug,REPRODUCED-on-v4.10.0,"Live CLI repro: `cci task run add_picklist_entries --org X -o picklists 'Account.Status__c' -o entries '[{""fullName"": ""TestValue"", ""label"": ""Test Value""}]'` -> 'The fullName key is required on all picklist values'. AddPicklistEntries._init_options (picklists.py:68) checks `'fullName' in entry for entry in self.options['entries']` directly, but no JSON parser is run on the CLI value first; the CLI passes 'entries' through as a string, so iteration walks the characters of the JSON string, none of which contain the substring 'fullName'. process_list_arg is run on 'picklists' but not on 'entries'",,keep-open,"bug,good-first-issue",v4.10.0,"Minimal fix in AddPicklistEntries._init_options: if isinstance(self.options.get('entries'), str): self.options['entries'] = json.loads(self.options['entries']). Apply same pattern to record_types option for consistency. Same class of bug exists in AddFieldsToPageLayout (`fields` and `pages` options); see line 'value is not a valid list' from issue-3613 testing." +3955,robotframework,bug-in-source,bug,REPRODUCED-on-dev,"cumulusci/robotframework/SalesforcePlaywright.py:106 splits size with str.split('x', 1) yielding two strings, then passes them directly to browser.new_context(viewport={""width"": width, ""height"": height}) on line 109-111 without int casting; Playwright rejects strings with ""viewport.width: expected integer, got string""",,keep-open,"bug,robotframework,playwright,good-first-issue",dev,Trivial 1-line fix: cast width/height to int. Pytest stubs the Browser library and asserts new_context receives int dimensions. diff --git a/docs/triage/v5/repro-results.md b/docs/triage/v5/repro-results.md new file mode 100644 index 0000000000..a9c0d1700c --- /dev/null +++ b/docs/triage/v5/repro-results.md @@ -0,0 +1,4248 @@ +# Reproducibility Pass Results - the reproducibility pass + +## Scope + +Two rounds of subagents: + +- **the initial reproducibility pass** (the triage): packaging + metadata-etl themes (45 issues; pre-v4.0.0 dropped per user; #3544 included as Cluster A canonical). +- **the follow-up reproducibility pass** (the triage): cli + bulkdata + dependencies + ci-integration themes (53 issues; pre-v4.0.0 dropped; cross-theme dups assigned to one subagent). +- **Total**: 98 of 142 open issues triaged via live v4.10.0 verification. +- Method: each subagent in an isolated git worktree pinned to `origin/main` (= release v4.10.0 at commit `129238663`); per-bucket org provisioning via DevHub `the configured DevHub`. +- See `themes.md` for the prior dry-run baseline; this file augments those proposals with v4.10.0 verdicts. + +## Totals (98 issues) + +| Verdict | Count | +| ---------------------------------------------- | ----: | +| `REPRODUCED-on-v4.10.0` | 68 | +| `NOT-REPRODUCED-on-v4.10.0` | 18 | +| `INCONCLUSIVE-needs-cumulus-actions-workflow` | 2 | +| `INCONCLUSIVE-needs-1gp-packaging-org` | 1 | +| `INCONCLUSIVE-needs-2GP-CI-pipeline` | 1 | +| `INCONCLUSIVE-needs-flaky-network` | 1 | +| `INCONCLUSIVE-needs-live-cli-test` | 1 | +| `INCONCLUSIVE-needs-managed-package-04t` | 1 | +| `INCONCLUSIVE-needs-namespaced-project` | 1 | +| `INCONCLUSIVE-needs-org-with-managed-package` | 1 | +| `INCONCLUSIVE-needs-project-with-managed-deps` | 1 | +| `INCONCLUSIVE-needs-scratch-slot` | 1 | +| `closed:duplicate-of-#3544` | 1 | + +## Per-round tally + +| Verdict | Count | +| ---------------------------------------------- | ----: | +| `REPRODUCED-on-v4.10.0` | 28 | +| `NOT-REPRODUCED-on-v4.10.0` | 10 | +| `INCONCLUSIVE-needs-1gp-packaging-org` | 1 | +| `INCONCLUSIVE-needs-2GP-CI-pipeline` | 1 | +| `INCONCLUSIVE-needs-cumulus-actions-workflow` | 1 | +| `INCONCLUSIVE-needs-managed-package-04t` | 1 | +| `INCONCLUSIVE-needs-namespaced-project` | 1 | +| `INCONCLUSIVE-needs-project-with-managed-deps` | 1 | +| `closed:duplicate-of-#3544` | 1 | + +| Verdict | Count | +| --------------------------------------------- | ----: | +| `REPRODUCED-on-v4.10.0` | 40 | +| `NOT-REPRODUCED-on-v4.10.0` | 8 | +| `INCONCLUSIVE-needs-cumulus-actions-workflow` | 1 | +| `INCONCLUSIVE-needs-flaky-network` | 1 | +| `INCONCLUSIVE-needs-live-cli-test` | 1 | +| `INCONCLUSIVE-needs-org-with-managed-package` | 1 | +| `INCONCLUSIVE-needs-scratch-slot` | 1 | + +## Quick-action shortlist (high-confidence - proposed-pass1 deviates from dry-run baseline) + +Subagents surfaced the following items where the v4.10.0 verdict suggests a clear change from the dry-run proposed action: + +| # | Theme | Verdict | Recommendation | Note | +| ----- | ------------------------------- | ---------------------------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------- | +| #733 | cli | `REPRODUCED-on-v4.10.0` | `closed:stale-24mo` | runtime.py:131-133 still raises ClickException with same hard error message; no interactiv… | +| #1348 | cli | `REPRODUCED-on-v4.10.0` | `closed:stale-24mo` | No 'gitlab' or 'bitbucket' references anywhere in cumulusci/; ci_feature flow still uses g… | +| #1350 | cli | `NOT-REPRODUCED-on-v4.10.0` | `closed:not-reproducible-on-v4.10.0` | project_config.py:52-57 sets up synthetic 'tasks' namespace package; include_source() at l… | +| #1432 | cli | `REPRODUCED-on-v4.10.0` | `closed:stale-24mo` | core/tasks.py:186-196 \_validate_options() only checks required; old-style task_options dic… | +| #1769 | bulkdata | `REPRODUCED-on-v4.10.0` | `closed:stale-24mo` | Test code-smell: `lookups["Id"] = MappingLookup(name="Id", table="accounts", key_field="sf… | +| #2096 | bulkdata | `NOT-REPRODUCED-on-v4.10.0` | `closed:not-reproducible-on-v4.10.0` | REST DML (`step.py:778-784 RestApiDmlOperation._record_to_json`) calls `process_bool_arg` … | +| #2140 | cli | `REPRODUCED-on-v4.10.0` | `closed:stale-24mo` | runtime.py get_org() calls keychain.get_org which raises OrgNotFound; cli/org.py:530-531 j… | +| #2402 | cli | `REPRODUCED-on-v4.10.0` | `closed:stale-24mo` | cli/flow.py:119-145 flow_run only has --delete-org flag; no --rebuild-org option; no rg ma… | +| #2505 | bulkdata | `NOT-REPRODUCED-on-v4.10.0` | `closed:feature-implemented` | `MappingStep.soql_filter` field added (mapping_parser.py:120). `extract.py:142-147` applie… | +| #2507 | cli | `REPRODUCED-on-v4.10.0` | `closed:stale-24mo` | No undo_insert task in repo; bulkdata/load.py and snowfakery have enable_rollback option b… | +| #2697 | cli | `INCONCLUSIVE-needs-scratch-slot` | `closed:stale-24mo` | namespaced field is sourced from cci config not auto-derived from SFDX qa.json; keychain c… | +| #3015 | cli | `REPRODUCED-on-v4.10.0` | `closed:stale-24mo` | cli/org.py:519-543 org_remove always calls delete_org() if can_delete; no --keep-org or -o… | +| #3024 | cli | `REPRODUCED-on-v4.10.0` | `closed:stale-24mo` | Flow groups in cumulusci/cumulusci.yml still appear in original order: Metadata Transforma… | +| #3161 | cli | `REPRODUCED-on-v4.10.0` | `closed:stale-24mo` | flowrunner.py:317-320 \_obfuscate_if_sensitive masks if task_options info.sensitive==True; … | +| #3167 | metadata-etl | `NOT-REPRODUCED-on-v4.10.0` | `close-as-implemented` | page*layout key on record_types is fully implemented in ProfileGrantAllAccess.\_set_record*… | +| #3283 | bulkdata | `NOT-REPRODUCED-on-v4.10.0` | `closed:fixed-by-pr-#3361` | PR #3361 (commit b0bfb70e0, "Support updates and upserts with blank dates represented by s… | +| #3307 | cli | `REPRODUCED-on-v4.10.0` | `closed:stale-24mo` | cli/project.py project_init only renders internal Jinja templates from cumulusci/files/tem… | +| #3320 | metadata-etl | `NOT-REPRODUCED-on-v4.10.0` | `closed:feature-implemented` | deactivate_flow task is shipped in cumulusci/cumulusci.yml:10-15 using ActivateFlow class … | +| #3347 | release-unlocked-beta-typeerror | `NOT-REPRODUCED-on-v4.10.0` | `close-with-comment` | create_package_version.py:158-159 now raises TaskOptionsError(PERSISTENT_ORG_ERROR) early … | +| #3360 | bulkdata | `NOT-REPRODUCED-on-v4.10.0` | `closed:feature-implemented` | action: select was added by commit b15945203 (Aug 2024) - well before v4.10.0. select_util… | +| #3466 | packaging | `NOT-REPRODUCED-on-v4.10.0` | `closed:feature-implemented` | RunApexTests in cumulusci/tasks/apex/testrunner.py exposes test_suite_names option (line 1… | +| #3470 | cli | `REPRODUCED-on-v4.10.0` | `closed:stale-24mo` | cumulusci.yml:823 only ci_master flow defined; no ci_main alias. davidmreed acknowledged b… | +| #3479 | ci-integration | `NOT-REPRODUCED-on-v4.10.0` | `closed:not-reproducible-on-v4.10.0` | Reported error "Expecting value: line 1 column 1 (char 0)" is the bare json.JSONDecodeErro… | +| #3561 | metadata-etl | `NOT-REPRODUCED-on-v4.10.0` | `close-as-fixed` | Bug was reported by yippie (who is also the PR author). Fix landed in commit 56e10665e (PR… | +| #3605 | packaging | `NOT-REPRODUCED-on-v4.10.0` | `closed:fixed-by-pr-#3651` | PackageUpload (upload_production backing class) exposes major_version and minor_version op… | +| #3607 | cli | `INCONCLUSIVE-needs-org-with-managed-package` | `closed:stale-24mo` | Code path traced in cumulusci/tasks/apex/testrunner.py: retry_failures regex compiled at l… | +| #3609 | cli | `INCONCLUSIVE-needs-live-cli-test` | `closed:stale-24mo` | cumulusci/tasks/sfdx.py is now a thin shell wrapper around 'sf {command}' (SFDX_CLI = 'sf'… | +| #3612 | cli | `NOT-REPRODUCED-on-v4.10.0` | `closed:not-reproducible-on-v4.10.0` | Issue is about the SFDO-Tooling/cci-vscode VSCode extension repo, not CumulusCI itself. Ou… | +| #3615 | dependencies | `NOT-REPRODUCED-on-v4.10.0` | `closed:not-reproducible-on-v4.10.0` | `--resolution_strategy preproduction` is documented in cumulusci.yml as an alias for `late… | +| #3699 | bulkdata | `REPRODUCED-on-v4.10.0` | `closed:stale-24mo` | extract.py \_soql_for_mapping does not append ORDER BY. mapping_parser MappingStep has no o… | +| #3701 | bulkdata | `REPRODUCED-on-v4.10.0` | `closed:stale-24mo` | mapping_parser.py:171/190/228/241/422 special-case "Id" - it always represents the SF Id a… | +| #3745 | packaging | `NOT-REPRODUCED-on-v4.10.0` | `closed:stale-24mo` | latest*beta resolver looks up GitHub Releases (include_beta strategy) per install_package*… | +| #3762 | metadata-etl | `closed:duplicate-of-#3544` | `close-as-duplicate` | Reporter (noahisapilot) explicitly self-identifies as duplicate of #3544 in their first co… | +| #3884 | packaging | `INCONCLUSIVE-needs-project-with-managed-deps` | `closed:missing-fields` | Source review: PackageNamespaceVersionDependency.install (dependencies.py L437-475) and Pa… | +| #3929 | packaging | `NOT-REPRODUCED-on-v4.10.0` | `closed:not-reproducible-on-v4.10.0` | Ran create_community with name=TestWebsite template='Customer Service' url_path_prefix=tes… | + +## Per-subagent SUMMARY chunks + +#### Bucket A - Packaging triage summary + +Triage pass for packaging bucket A. Worktree pinned at `v4.10.0` / commit `129238663`. +10 issues processed; no GitHub mutations; no Salesforce org used. + +#### Verdict counts + +| Verdict | Count | Issues | +| ------------------------- | ----- | ------------------------------------------------------ | +| REPRODUCED-on-v4.10.0 | 8 | #2979, #3429, #3440, #3441, #3593, #3721, #3758, #3889 | +| NOT-REPRODUCED-on-v4.10.0 | 2 | #3466, #3605 | +| INCONCLUSIVE-\* | 0 | - | +| SKIPPED | 0 | - | + +#### Repro tests written (under `_(repro evidence)_`) + +| Test file | Purpose | Result on v4.10.0 | +| -------------------- | ------------------------------------------------------------------------ | ---------------------------- | +| `test_issue_3466.py` | confirms `test_suite_names` option exists on `RunApexTests` | passes (feature implemented) | +| `test_issue_3593.py` | demonstrates `SFDXOrgTask` still appends `-o ` unconditionally | fails (bug present) | +| `test_issue_3605.py` | confirms `major_version`/`minor_version` exist on `PackageUpload` | passes (feature implemented) | +| `test_issue_3758.py` | asserts `push_upgrade_org` final step calls `config_managed` | fails (bug present) | + +`#2979`, `#3429`, `#3440`, `#3441`, `#3721`, `#3889` are all features whose absence is verifiable purely by code inspection (no test scaffolding needed); see `narrative.md` for `path:line` evidence. + +#### Pass-1 recommended actions + +| Recommendation | Issues | +| ---------------------------- | ------------------------------------------------------ | +| `keep-open` | #2979, #3429, #3440, #3441, #3593, #3721, #3758, #3889 | +| `closed:feature-implemented` | #3466 | +| `closed:fixed-by-pr-#3651` | #3605 | + +#### Cross-cutting findings + +1. **Multi-package SFDX umbrella (#2979, #3429, #3440)** - three open enhancements all point at the same gap: CumulusCI assumes a single package per repo. `default_package_path` exists but is only consumed by `create_package_version`. A single small design refactor (Deploy gets a `path: $project_config.default_package_path` default, `default_package_path` becomes name-aware, and a `cumulusci.yml` override mechanism is added) could resolve all three. Worth folding them into one umbrella issue or theme on the pass-2 labeling pass. + +2. **Null/sentinel overrides in flow steps (#3441)** - yippie's comment generalizes the request: CCI lacks any way to "unset" or "reset to default" an option that a flow step has set. This is a CCI-wide ergonomics gap (not just `version_base`), and probably belongs as its own meta-issue. + +3. **PR #3969 (`extra-yaml-cli-flag`) is in flight for #3429** - adds `--extra-yaml` and `CUMULUSCI_EXTRA_YAML`. Not yet in v4.10.0. Recommend the parent agent flip #3429 to `closed:fixed-by-pr-#3969` once that PR merges; until then `keep-open` is correct. + +4. **muselab-d2x fork has a fix for #3721** - commit `7aaf348f3` ("Change version naming on PackageUpload task to use the predicted version number and a jinja2 template expression") implements jinja2 templating for `PackageUpload.version_name`. Lives only on `d2x/*` remotes. Could be ported upstream as a small PR; would also need a sibling change in `create_package_version.py:184` for 2GP coverage. + +5. **One-line YAML fix candidate (#3758)** - `push_upgrade_org` last step should be `config_managed`, not `config_qa`. Currently both expand to the same task list, so it's not a behavior regression today, but the docs link customers to the wrong page. Excellent `good-first-issue` for an external contributor; explicitly out of scope for this triage pass. + +6. **`SFDXOrgTask` org-flag append (#3593)** - recurring pain point as sf cli surface evolves; a generic `pass_org` / `no_org` opt-out option (or a curated whitelist of no-org subcommands) is a more durable fix than the user's `#` workaround. Verifying actual sf cli behavior of `project convert source` would need an org/sf cli; the CCI side of the bug is unchanged on v4.10.0. + +7. **2GP unlocked uninstall (#3889)** - natural shape is a new `UninstallPackageVersion` task that calls Tooling API `SubscriberPackageVersion` delete directly, avoiding the sf cli stability concern the user calls out. Aligns with broader 2GP investment. + +#### Bucket A - metadata-etl summary + +**Worktree**: `/Users/jestevez/work/rel/CumulusCI/.worktrees/[scratch-org]` +**Pinned commit**: `129238663` (Release v4.10.0) +**Issues processed**: 11 / 11 + +#### Verdict counts + +| verdict | count | issues | +| ------------------------- | ----- | -------------------------------------------------------------------- | +| REPRODUCED-on-v4.10.0 | 10 | #3137, #3331, #3518, #3543, #3585, #3692, #3771, #3773, #3938, #3939 | +| NOT-REPRODUCED-on-v4.10.0 | 1 | #3320 | +| INCONCLUSIVE | 0 | - | +| SKIPPED | 0 | - | + +#### Severity / urgency picks (most worth flagging in pass-1) + +- **#3938 (CRITICAL, recently filed 2025-12-16)** - `rest_deploy: True` silently + swallows failed deployments and reports success. Affects MetaDeploy plans + and any flow using `rest_deploy`. Same reporter as #3939; likely both + blocking the same production-deploy workflow. +- **#3939 (HIGH, recently filed 2025-12-16)** - Even SOAP-path deploys lose + their actual error text because `BaseMetadataApiCall.__call__` wraps every + exception in the generic "Could not process MDAPI response" message. + Apex test failures, component failures, and API errors are all clobbered. +- **#3518 (HIGH)** - `add_picklist_entries` always marks a default for record + types because of a missing `()` after `.lower` (picklists.py:177); blocks + any non-default picklist additions on objects with record types. + +#### Cross-cutting findings + +- **Error-handling pattern (#3938 + #3939)**: Both critical bugs come from + the same anti-pattern - an outer try/except at the orchestration layer + swallows exceptions raised by inner code that was correctly trying to + surface a user-actionable error. Worth a single PR that audits both + code paths and keeps `CumulusCIException` subclasses unwrapped. +- **`metadata_map.yml` accuracy (#3331 + #3692 + arguably #3585)**: Three + separate bugs trace back to the YAML metadata-name registry being + out-of-date or inconsistent: wrong type name (`AssignmentRule` vs + `AssignmentRules`), missing entry (`digitalExperiences`), and lack of + parser robustness against newer SFDX-emitted XML quirks (`xsi:nil="true"`). + A single sweep against the current MDAPI catalog would close all three. +- **External fork PR for #3771**: Commit `2bf6ce6a3` ("Improve namespace + handling in find_replace") exists on `remotes/leboff/...` but never + landed on origin/main. Bringing that PR back through review would close + the issue. +- **#3320 mismatch**: Reporter asked for a `deactivate_flow` task; one is + already shipped in `cumulusci/cumulusci.yml:10-15`. Likely a docs / + discoverability issue rather than a code one - worth flagging in pass-2 + as `closed:feature-implemented` plus a small doc improvement. +- **Architectural gap for #3773**: `RetrieveProfileApi._queries_retrieve_permissions` + doesn't query `FieldPermissions` at all. This is a class-of-omission + bug: any object that has profile field-perms but no object-perms + (very common for standard objects like `AccountContactRelation`) is + silently dropped. Likely there are other under-queried permission + surfaces too - worth a focused review. + +#### Output artifacts + +- `_(repro evidence)_` - machine-readable results (12 rows) +- `_(repro evidence)_` - per-issue evidence and recommended actions +- `_(repro evidence; see narrative)_` - 11 throwaway repro tests; + all 11 currently fail on v4.10.0 (= bugs reproduced). + +#### Constraint compliance + +- No `git push` from worktree. +- No GitHub mutations (only read-only inspection of bundled JSON). +- No writes under `cumulusci/robotframework/`. +- All repro tests live under `_(repro evidence)_`, not in the worktree + source tree. +- No third-party packages added; tests use only `pytest`, `pyyaml`, and + in-tree CCI modules. +- `uv run pytest ...` used for test execution. + +# + +**Theme**: packaging +**Bucket**: B (scratch-org-required) +**Worktree**: the triage worktree @ `129238663` (release v4.10.0) +**Issues processed**: 6 / 6 + +#### Verdict tally + +| Verdict | Count | Issues | +| ------------------------------------------------- | ----- | ------------ | +| REPRODUCED-on-v4.10.0 | 2 | #3446, #3734 | +| NOT-REPRODUCED-on-v4.10.0 (feature unimplemented) | 2 | #3587, #3600 | +| INCONCLUSIVE-needs-cumulus-actions-workflow | 1 | #3418 | +| INCONCLUSIVE-needs-2GP-CI-pipeline | 1 | #3542 | +| **Total** | **6** | | + +#### Recommended pass1 actions + +| Issue | Action | Reasoning | +| ----- | --------- | ------------------------------------------------------------------ | +| #3418 | unchanged | external github action; needs cross-repo investigation | +| #3446 | keep-open | reproduced; simple fix (validate version) + UX (Push API guidance) | +| #3542 | unchanged | external CI/CD; needs reporter info on workflow SHA semantics | +| #3587 | keep-open | feature still missing; small enhancement | +| #3600 | keep-open | feature still missing; needs design decision on syntax | +| #3734 | keep-open | reproduced; remove stale `cannot-reproduce` label | + +#### Cross-cutting findings + +1. **Two clear bugs reproducing on v4.10.0 (#3446, #3734)** with well-localized root causes: + +- #3446 is a missing-validation bug in `cumulusci/tasks/push/tasks.py` `_run_task` / `_parse_version`. +- #3734 is a logic bug in `cumulusci/tasks/salesforce/package_upload.py` `_validate_versions` where a Beta patch on a previous minor is treated as the latest version. + +2. **Two unimplemented features (#3587, #3600)** suitable for "good-first-issue" tagging: + +- #3587 wants a one-line warning in `UpdatePackageXml._init_task`. +- #3600 wants env-var substitution in task options - broader scope (touches all options, not just `install_managed`); needs design. + +3. **Two issues that escape cci's repo boundary (#3418, #3542)**: + +- #3418 is in `SFDO-Community/standard-workflows`; cci has no `auth:sfdxurl:store` code path. +- #3542 is a SHA-mismatch problem between the workflow that posts a commit status and the local checkout that reads it; could be fixed in `cumulus-actions/standard-workflows` or by adding a parent-commit fallback in `get_version_id_from_commit`. + +4. **Mislabeled triage state on #3734**: the user's last three comments contain a correct, fully-localized root-cause analysis. The `cannot-reproduce` and `awaiting-more-details` labels are stale and should be dropped in pass2 in favor of `bug`. +5. **Code stability**: none of the relevant code paths for these six issues have changed materially between the issue dates and v4.10.0 (verified via `git log` for `cumulusci/tasks/push/`, `cumulusci/tasks/salesforce/package_upload.py`, `cumulusci/tasks/github/commit_status.py`, `cumulusci/tasks/metadata/package.py`, `cumulusci/utils/yaml/safer_loader.py`, `cumulusci/core/tasks.py`). + +#### Scratch orgs used + +- `[scratch-org]` (cci alias) / `[scratch-org-alias]` (sf alias) - config `dev`, used for live `install_managed` and `push_qa` invocations. + +No second alias was needed; `feature` config was not required. + +#### Cleanup status + +**Cleanup completed successfully**. Verified at end of session: + +``` +$ uv run cci org list 2>&1 | grep -i 'pkg-b1' + (no rows match) +$ sf org list 2>&1 | grep -i 'pkg-b1' + (no rows match) +``` + +Steps taken: + +1. `uv run cci org scratch_delete [scratch-org]` - sf-side org deleted via `sf org delete scratch -p -o test-ra9moqy3oqup@example.com`. +2. `uv run cci org remove [scratch-org]` - cci keychain alias removed (cci was still listing the alias even after `scratch_delete` because the alias entry persists locally). + +#### Output files (under `_(repro evidence)_`) + +- `repro-results.csv` - one row per issue (header + 6 data rows). +- `narrative.md` - per-issue method/evidence/recommendation. +- `tests/repro_3446_push_qa_no_version.py` - reproduces NoneType.split. +- `tests/repro_3587_update_package_xml_no_warning.py` - confirms install_class silently dropped. +- `tests/repro_3600_install_managed_no_env_var.py` - confirms no env-var substitution. +- `tests/repro_3734_upload_production_beta_patch.py` - confirms Beta-patch sets colliding minor. + +#### Deviations from constraints + +None. + +# + +#### Verdict tally (5 issues processed) + +| Verdict | Count | Issues | +| ---------------------------------------------- | ----- | ------------ | +| `REPRODUCED-on-v4.10.0` | 1 | #3746 | +| `NOT-REPRODUCED-on-v4.10.0` | 2 | #3745, #3929 | +| `INCONCLUSIVE-needs-project-with-managed-deps` | 1 | #3884 | +| `INCONCLUSIVE-needs-1gp-packaging-org` | 1 | #3899 | +| `SKIPPED-policy-*` | 0 | - | + +Total: 5 / 5 (100%). + +#### Cross-cutting findings + +1. **Two of five issues had already self-resolved upstream**: #3745 (user-confusion about ci_beta requiring release_2gp_beta to publish the GitHub release tag - accepted by the reporter in-thread) and #3929 (an upstream Salesforce CLI / Communities API bug that is now fixed per OP comment 2025-10-22 and confirmed end-to-end against scratch org). The triage policy's stale-24mo and needs-repro proposals respectively were defensible at proposal time but #3929 should now flip to `closed:not-reproducible-on-v4.10.0`. + +2. **One real, trivially-fixable v4.10 bug surfaced**: #3746 (`create_package_version._get_base_version_number` SOQL has no `IsDeprecated = false` filter). The same file has the filter on a parallel `Package2` lookup at line 297, so the omission at line 535 is asymmetric and the fix is one clause. This is a `target:v4-patch` candidate hidden under a stale-24mo proposal. + +3. **One issue is provably an upstream Salesforce platform bug, not CCI**: #3899. The reported error (`system.scheduler.cron.JobType.getJobImplementation()` being null) references Salesforce-internal Java classes; CCI sends correct Apex; trivial Apex runs cleanly on a fresh scratch org. The triage labelling vocabulary should grow an `external/upstream-salesforce` Pass-2 label so future triage can disposition this class of report quickly. + +4. **One issue is plausibly user-misperception of log noise**: #3884 (dev_org "reinstalls" same package version). Source review confirms skip-if-already-installed logic is present in v4.10.0 for both managed-package install paths. Without a customer project that has managed deps, we cannot disprove a corner case, but the proposed `closed:missing-fields` (rule 3) is the right disposition because the report lacks the `cumulusci.yml` excerpt that would let us identify which dependency type they observed. + +5. **Methodology note**: 2 of 5 verdicts were reachable by code review alone (#3745, #3746), 1 required a brief scratch-org runtime check that succeeded (#3899 partial; #3929 full), and 1 required a runtime check that fully resolved the question (#3929). The scratch org was reused across all runs - quota cost: 1 dev-config scratch (already provisioned, not torn down per "Scratch org cleanup" below since other subagents may still be relying on the DevHub quota math). + +#### Scratch org aliases used + +- `[scratch-org]` (config: `orgs/dev.json`, expires 2026-05-15 08:51 PT, instance `customization-java-47-dev-ed.scratch`). +- Used for: #3899 unschedule_apex runtime check, #3929 create_community runtime check. +- Already existed at session start (presumably from a prior controller run); reused, not recreated, so no fresh DevHub-create quota was consumed by this subagent. + +#### Output files written + +- [`_(repro evidence)_`](_(repro evidence)_) - 5 rows + header, full triage CSV schema per plan §2.5c step 5. +- [`_(repro evidence)_`](_(repro evidence)_) - per-issue narrative, sorted by verdict (REPRODUCED first). +- [`_(repro evidence)_`](_(repro evidence)_) - this file. +- [`_(repro evidence)_`](_(repro evidence)_) - 5 evidence files, one per issue, referenced from `repro-results.csv` and `narrative.md`. + +#### Deviations from the prompt + +- The a scratch org was already provisioned at session start (left over from a prior run, healthy and unexpired). Reused it directly rather than creating a new one. Documented above; conserves DevHub quota. +- Cleanup decision deferred to the controller - see "Scratch org cleanup" note below. + +#### Scratch org cleanup (executed) + +``` +$ uv run cci org scratch_delete [scratch-org] +[05/14/26 02:07:26] Deleting scratch org with command: sf org delete scratch -p + -o test-qu66hqqypnu3@example.com +[05/14/26 02:07:28] (success) + +$ uv run cci org remove [scratch-org] +(silently removes the cci keychain entry) + +$ sf org list scratch --target-dev-hub the configured DevHub | grep qu66hqqypnu3 || echo "absent" +absent + +$ uv run cci org list | grep [scratch-org] || echo "absent" +absent +``` + +DevHub `the configured DevHub` quota slot returned. No worktree branch pushed. No GitHub mutations performed. No files written under `cumulusci/robotframework/`. + +# + +Theme: **metadata-etl** · Bucket **B** · Worktree the triage worktree @ commit `129238663` (release v4.10.0) + +#### Verdict tally (10 issues processed) + +| Verdict | Count | Issues | +| ------------------------------------------- | ----- | ------------------------------------------------ | +| REPRODUCED-on-v4.10.0 | 7 | #808, #2826, #3165, #3613, #3931, #3951, #3953 | +| NOT-REPRODUCED-on-v4.10.0 (already fixed) | 1 | #3561 (fix in PR #3566, May 2024) | +| NOT-REPRODUCED-on-v4.10.0 (feature shipped) | 1 | #3167 (PR #3243, June 2022) | +| closed:duplicate | 1 | #3762 (dup of #3544; self-confirmed by reporter) | + +Of the 7 REPRODUCED: + +- 4 are real correctness bugs (#808, #2826, #3165, #3931) +- 2 are UX/error-message bugs (#3613, #3951) - underlying functionality works with correct input +- 1 is a CLI option-parsing bug (#3953) - task is unusable from CLI + +#### Cross-cutting findings + +##### 1. CLI doesn't auto-parse JSON list options for several ETL tasks + +`AddPicklistEntries.entries`, `AddFieldsToPageLayout.fields/pages`, and likely other dict-list options can be set in `cumulusci.yml` (where the YAML loader parses them into lists/dicts) but break from the CLI when passed as JSON strings. Each task currently has to re-implement parsing if it wants to support CLI use. + +Issues affected: **#3953** (confirmed) and **add_page_layout_fields** (found while testing #3613). + +Likely fix venues: + +- One-line `json.loads` in each affected task's `_init_options`. Cheap, low risk. +- Or: a generic helper on `BaseTask._init_options` that walks `task_options` declared as list/dict and JSON-parses string values. Higher impact, deeper change. +- Or: leverage Pydantic-validated `task_options` (already used in `AddFieldsToPageLayout` for `AddFieldsToLayoutOptions`) to coerce strings -> JSON via a validator. + +##### 2. `MetadataSingleEntityTransformTask` "Cannot find metadata file" error is unhelpful + +`MetadataSingleEntityTransformTask._transform` (base.py:332) raises a generic message when the user-provided `api_names` value doesn't match any retrieved file. Shared root cause behind **#3613** (Layout) and **#3951** (DuplicateRule), and probably also explains user confusion with other entities like `Profile`, `Flow`, `RecordType` whose Metadata API names follow non-obvious patterns. + +Suggested fix: when the file is missing, also log the list of files actually retrieved into `source_metadata_dir`. Single change, multi-issue improvement. + +##### 3. `update_admin_profile`'s `record_types` plus `_expand_package_xml` gating is fragile + +The `record_types` -> `_expand_package_xml_objects` dependency is buried inside `_expand_package_xml`, which is gated on `include_packaged_objects=True`. This means a user who specifies `record_types` for an object outside the default `admin_profile.xml` package list - without also setting `include_packaged_objects: true` - silently skips the package.xml expansion needed for their record types. + +Issues affected: **#3165** (record_type for Case fails). Probably also a contributing factor in **#3544 / #3762** (the namespaced-org Person Accounts crash). + +The fix is a one-line refactor: always call `_expand_package_xml_objects` (which only walks the user's options); only call the full `_expand_package_xml` (Tooling API query) when `include_packaged_objects=True`. + +##### 4. Several issues are "long-tail" enhancement requests still open + +**#808** (2018), **#2826** (2021), **#3167** (2022, now implemented), **#3561** (2023, now fixed). The pattern suggests value in periodic dup/staleness sweeps that pair issues with PRs (#3167 ↔ #3243, #3561 ↔ #3566) - both were closed by a PR but the issue was never auto-closed. + +#### Scratch org aliases used + +- `[scratch-org]` - used for live repros of #3613, #3951, #3953. Pre-existed from a prior subagent setup; reused as instructed. Will be deleted in the cleanup step. Created 2026-05-14 with `dev.json`, expires 2026-05-15. + +No additional scratch orgs created. + +#### Output files + +- `_(repro evidence)_` - verdict CSV (10 rows + header) +- `_(repro evidence)_` - per-issue narrative (10 sections) +- `_(repro evidence)_` - this file +- `_(repro evidence; see narrative)_` - Python repro (FileNotFoundError) +- `_(repro evidence; see narrative)_` - Python repro (package.xml expansion gap) +- `_(repro evidence)_` - CLI output (Cannot find metadata file) +- `_(repro evidence)_` - CLI output (success with correct API name) +- `_(repro evidence; see narrative)_` - Python repro (NoneType.text) +- `_(repro evidence)_` - captured output +- `_(repro evidence)_` - CLI output (Cannot find metadata file) +- `_(repro evidence)_` - CLI output (fullName key required) + +#### Deviations + +None. All 10 issues processed within the protocol. No GitHub mutations. No commits to the worktree. No edits to `cumulusci/robotframework/`. No `git push`. No package additions to `pyproject.toml`/`uv.lock`. + +# + +#### Verdict tally + +| Verdict | Count | Issues | +| ---------------------------------------- | ----- | ------ | +| `NOT-REPRODUCED-on-v4.10.0` | 1 | #3347 | +| `INCONCLUSIVE-needs-managed-package-04t` | 1 | #3902 | +| `INCONCLUSIVE-needs-namespaced-project` | 1 | #3544 | +| `REPRODUCED-on-v4.10.0` | 0 | - | +| **Total processed** | **3** | | + +#### Scratch org aliases used + +| Alias | Config | DevHub | Status | +| --------------- | ----------------- | --------------------- | ------------------------------------------------------------------------------ | +| `[scratch-org]` | `person_accounts` | the configured DevHub | Created (existed at session start, reused), used for #3544, slated for cleanup | + +No other aliases created. #3902 and #3347 were code-only investigations. + +#### Per-issue outcomes + +##### #3347 - `release_unlocked_beta` TypeError → fixed + +Cryptic `TypeError: expected str, bytes or os.PathLike object, not NoneType` is replaced on v4.10.0 by an early `TaskOptionsError(PERSISTENT_ORG_ERROR)` at `cumulusci/tasks/create_package_version.py:158-159`, fix landed in commit `2a9cadcb1` (2023-10-12). Existing test `test_create_package_version.py::TestPackageConfig::test_org_config` validates the new behavior and passes. **Recommend close-with-comment.** + +##### #3902 - `install_managed` security_type with 04t → code looks correct, runtime not validatable + +Code path inspection on v4.10.0 confirms `install_managed` faithfully passes `security_type` to the Salesforce Tooling API `PackageInstallRequest.SecurityType` for both 04t and namespace+version code paths. `SecurityType.ADMIN` correctly serializes to JSON `"NONE"`. No CumulusCI defect identified; observed user behavior likely originates from Salesforce platform behavior (upgrade vs fresh install, App-level tab visibility, etc.). **Cannot fully validate without a reusable managed package 04t fixture - verdict INCONCLUSIVE-needs-managed-package-04t.** + +##### #3544 - `update_admin_profile` with PersonAccounts + namespacing → not validatable in CumulusCI repo + +Bug requires the intersection of PersonAccounts AND a namespaced project. CumulusCI itself has no project namespace, so the repro condition cannot be fully satisfied without registering a namespace in the configured DevHub (out of scope). Partial repro: `update_admin_profile` ran SUCCESSFULLY against a non-namespaced PersonAccounts scratch on v4.10.0, confirming the bug is specific to the namespaced+PersonAccounts intersection. No code-level fix found referencing #3544 / [internal-ID-redacted] in `update_profile.py` since 2023. **Verdict INCONCLUSIVE-needs-namespaced-project.** + +#### Per-issue blocker explanations + +| Issue | Blocker | Mitigation | +| ----- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | +| #3347 | None - code-only verifiable | Existing pytest passes on HEAD | +| #3902 | No reusable managed package 04t fixture available within the configured DevHub workflow | Recommend Pass 2 label `needs-managed-package-fixture` | +| #3544 | CumulusCI repo has no project namespace; cannot satisfy `namespaced:true` repro condition | Recommend Pass 2 label `needs-namespaced-project` and ask reporter to retest on v4.10.0 | + +#### Adjacent finding (not in scope but worth recording) + +While exercising `-o namespaced_org True` on a non-namespaced project for #3544, surfaced a latent `TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'` at `cumulusci/utils/__init__.py:229`: + +```229:229:cumulusci/utils/__init__.py + namespaced_org = namespace + "__" if namespaced_org else "" +``` + +Distinct from #3544 (init-time vs deploy-time). Suggest a separate small cleanup ticket. + +#### Outputs + +- `_(repro evidence)_` - header-conforming CSV with 3 rows +- `_(repro evidence)_` - per-issue narrative sections +- `_(repro evidence)_` - this file +- `_(repro evidence)_` - directory created but no new test files written (used existing in-repo test for #3347; manual cci command for #3544; pure code inspection for #3902) + +#### Constraints honored + +- No `git push` from worktree. +- No GitHub mutations. +- No edits under `cumulusci/robotframework/`. +- No edits to `pyproject.toml`/`uv.lock`. +- All commands run via `uv run …` from the worktree. +- DevHub: `the configured DevHub` only. +- Scratch alias prefix: `[scratch-org]-`. + +# + +Worktree: the triage worktree @ `129238663` = release v4.10.0. + +#### Verdict tally (13 issues) + +| Verdict | Count | Issues | +| ------------------------------- | ----- | -------------------------------------------------------------------------- | +| REPRODUCED-on-v4.10.0 | 11 | #733, #1348, #1432, #2140, #2402, #2507, #3015, #3024, #3161, #3307, #3470 | +| NOT-REPRODUCED-on-v4.10.0 | 1 | #1350 | +| INCONCLUSIVE-needs-scratch-slot | 1 | #2697 | + +By type: 10 features, 3 bugs (one of which is partially-fixed: #1432; one fully-fixed: #1350; one inconclusive: #2697). + +#### Recommended pass1 labels + +| Recommendation | Count | Issues | +| ----------------------------------------------- | ----- | ---------------- | +| `closed:stale-24mo` (confirms dry-run proposal) | 12 | all except #1350 | +| `closed:not-reproducible-on-v4.10.0` | 1 | #1350 | + +So **12 of 13 dry-run `closed:stale-24mo` proposals stand**. One should be re-categorised as `closed:not-reproducible-on-v4.10.0` (#1350). + +#### Cross-cutting findings + +1. **Most of the bundle is stable feature requests with very little movement** (#733 from 2018, #1348 from 2019, #2140 from 2020, etc.). The `closed:stale-24mo` action is appropriate; the work-items in some cases ([internal-ID-redacted], [internal-ID-redacted], [internal-ID-redacted]) have been tracked for years without delivery. + +2. **#1350 is the only clear "we shipped a fix" issue**: a synthetic `tasks` namespace package was added (`cumulusci/core/config/project_config.py:52-57`) plus `_add_tasks_directory_to_python_path()` so cross-project task imports now resolve. The follow-up _name-collision_ concern raised by `prescod` in 2022 is a separate concern and would warrant its own issue if anyone hit it again. + +3. **#1432 and #2507 and #3161 each show "partial mitigation"** - infrastructure has landed but the original ergonomic request is not fully satisfied. Worth flagging in `notes` so these don't get re-triaged as still-open feature debt without context. Adding `partially-fixed` / `partially-implemented` labels in pass2 surfaces this. + +4. **Inclusive-language angle (#3470)**: the rename remains undone but the underlying need (flow aliasing) is acknowledged. If pass2 includes a `inclusive-language` label, this is the only candidate. + +5. **No issues should remain open** - all 13 are either fixed, stale, or design-as-intended (the design-as-intended one, #2697, is best closed with a `needs-info` framing if anyone reopens). + +#### Scratch org aliases used + +**No scratch needed**. All 13 issues were resolved via code-scan (Bucket A) plus one unit test. #2697 was the only candidate for Bucket B and the code reading was conclusive enough to defer scratch provisioning (DevHub-slot conservation per instructions). + +#### Output files + +- `_(repro evidence)_` - 13 rows, header included. +- `_(repro evidence)_` - H3 section per issue. +- `_(repro evidence)_` - this file. +- `_(repro evidence; see narrative)_` - unit repro for #1432 (passes on v4.10.0). + +#### Deviations + +None. + +# + +**Pinned commit**: `129238663` (Release v4.10.0) +**Branch**: the triage worktree +**Worktree**: the triage worktree +**Issues processed**: 13 / 13 +**Scratch orgs provisioned**: none - all repros completed via Bucket A (code-scan + one isolated unit test) + +#### Verdict tally + +| Verdict | Count | Issues | +| ------------------------------------------- | ----- | -------------------------------------------------------------------- | +| REPRODUCED-on-v4.10.0 | 10 | #3485, #3492, #3506, #3549, #3570, #3618, #3663, #3754, #3852, #3854 | +| INCONCLUSIVE-needs-org-with-managed-package | 1 | #3607 | +| INCONCLUSIVE-needs-live-cli-test | 1 | #3609 | +| NOT-REPRODUCED-on-v4.10.0 | 1 | #3612 | + +#### Pass-1 recommendation tally + +| Recommendation | Count | Issues | +| ------------------------------------ | ----- | -------------------------------------------------------------------- | +| `keep-open` | 10 | #3485, #3492, #3506, #3549, #3570, #3618, #3663, #3754, #3852, #3854 | +| `closed:stale-24mo` | 2 | #3607, #3609 | +| `closed:not-reproducible-on-v4.10.0` | 1 | #3612 | + +Both kept-open issues from the dry-run (#3852, #3854) remain `keep-open` here - verdicts confirm the maintainer's label. + +#### Cross-cutting findings + +1. **Flow `when:` clause is structurally limited.** Three issues in this batch (#3506, #3570, #3663) all expose the same surface: the `when:` evaluator at `cumulusci/core/flowrunner.py:510-516` only sees `project_config` + `org_config`, and `when:` is only honored on `task:` steps (line 669) - never on `flow:` steps (lines 674-697). A single epic could absorb #3506 + #3663 and is adjacent to #3570's "finally/error path" request. + +2. **`-o` / option-parsing surface is rigid.** #3492 (project-level overrides) and #3618 (multi-org operations) are both small CLI parser improvements. Both landed in this batch with `keep-open` and `good-first-issue`-eligible scope. + +3. **JUnit/test-output story for Apex is half-done.** #3485 (malformed JUnit from `run_tests`) and #3549 (no test output from `deploy`) both concern producing CI-consumable test results. A consistent JUnit emitter shared by both tasks would close both. + +4. **Update-check / network-dependence concerns.** #3754 (no way to disable PyPI ping) is small and self-contained. Adding a `CUMULUSCI_DISABLE_VERSION_CHECK` env var around `cumulusci/cli/utils.py:check_latest_version` (line 82) closes it in one short PR. + +5. **Two genuine regressions are still active**: + +- #3852 (sarge `Capture.flush`) is upstream-dependent (sarge 0.1.8 unreleased). Cosmetic only on Python 3.13. +- #3854 (`capture_sample_data` validation) is a regression introduced by PR #3741 / `2c5d0056e`. Workaround documented (downgrade to 3.84.1) but the bug is real for polymorphic-lookup users. + +6. **#3609 (dx plugins:install) is upstream-only.** `cumulusci/tasks/sfdx.py` is now a 5-line shell wrapper around `sf {command}`; any `Timed out` error originates in the SF CLI itself. + +7. **#3612 belongs in a different repo** (`SFDO-Tooling/cci-vscode`) and should be transferred or closed as wrong-repo. + +#### Output files + +- `_(repro evidence)_` - 13 rows, header validated, parses cleanly. +- `_(repro evidence)_` - H3 section per issue with verdict + evidence + pass-1/pass-2 recs. +- `_(repro evidence)_` - this file. +- `_(repro evidence; see narrative)_` - one repro test (passes, confirming retry regex logic is correct in isolation). + +#### Scratch-org cleanup + +No scratch orgs provisioned - Bucket A sufficed for all 13. Nothing to delete. + +#### Deviations + +None. + +# + +**Theme**: bulkdata · **Bucket**: B (org-eligible) · **Pinned commit**: `129238663` (= release v4.10.0) · **Issues processed**: 9 / 9 + +#### Verdict tally + +| Verdict | Count | Issues | +| ------------------------- | ----- | ---------------------------------------- | +| REPRODUCED-on-v4.10.0 | 6 | #1769, #2013, #2325, #2506, #2508, #2951 | +| NOT-REPRODUCED-on-v4.10.0 | 3 | #2096, #2505, #3283 | +| INCONCLUSIVE | 0 | - | + +#### Pass-1 recommendations + +| Recommendation | Count | Issues | +| ---------------------------------- | ----- | --------------------------------- | +| keep-open | 5 | #2013, #2325, #2506, #2508, #2951 | +| closed:not-reproducible-on-v4.10.0 | 1 | #2096 | +| closed:feature-implemented | 1 | #2505 | +| closed:fixed-by-pr-#3361 | 1 | #3283 | +| closed:stale-24mo | 1 | #1769 | + +#### Bug vs. feature + +- 5 bugs: #1769 (test code-smell), #2013 (extract multi-step duplicate table), #2096 (REST booleans), #2951 (Pricebook sequencing), #3283 (empty date). +- 4 features: #2325 (validation rule toggle), #2505 (extract WHERE), #2506 (debug tempfiles), #2508 (manual retries). + +#### Cross-cutting findings + +1. **Pattern of extending metadata-toggle tasks is fertile**: `disable_tdtm_trigger_handlers`, `restore_tdtm_trigger_handlers`, `set_duplicate_rule_status` form a clear template (`MetadataSingleEntityTransformTask` subclass). #2325 (ValidationRule), and historical asks for similar toggles, can all be solved by ~25-line additions following this pattern. +2. **`MappingStep.soql_filter` is mature**: per-step WHERE-clause filtering is now first-class, used by extract.py, the new declarative extract generator, and the hardcoded extract defaults. This closes #2505 cleanly and could be a reusable answer for any future "filter at extract time" requests. +3. **`hardcoded_default_declarations.py` quietly mitigates several historic data-shape bugs**: PricebookEntry filtering (#2951), sample-account exclusion, business hours, etc. Worth surfacing this file to triage doc readers - many "I extracted my org and it broke" issues are now invisibly handled here. +4. **Snowfakery has a working `get_debug_mode` integration; load/extract do not**: #2506 is half-complete. Wiring `get_debug_mode()` into `load.py`/`extract.py` and conditionally retaining tempdirs would close the gap with negligible risk. +5. **Rollback ≠ retry**: `enable_rollback` (added since 2021) is sometimes confused with the retry asked for in #2508. They are orthogonal: rollback undoes successful inserts on failure; retry would re-attempt failed records. Both have value. +6. **REST loader has caught up with Bulk on type coercion** (`process_bool_arg` is now used by `RestApiDmlOperation._record_to_json`); the empty-string→null fix (#3361) for upsert/update is also in place. Several "REST is stricter than Bulk" historic complaints are likely resolved by these two changes alone. + +#### Scratch org aliases used + +**None.** All 9 issues were classifiable from source-code reading and small in-process unit tests; no `cci org scratch` invocation was needed. + +#### Repro test artifacts + +- `_(repro evidence; see narrative)_` - confirms #2013 SQLAlchemy error +- `_(repro evidence; see narrative)_` - confirms #2096 spec compliance (20/20) +- `_(repro evidence; see narrative)_` - confirms #3283 fix in v4.10.0 (3/3) + +All three test files run green against the v4.10.0 source via `uv run pytest ` from this worktree. + +#### Deviations + +None. No GitHub mutations. No git pushes. No edits under `cumulusci/robotframework/`. No edits inside main worktree at `/Users/jestevez/work/rel/CumulusCI`. No package additions to pyproject/lock. + +# + +- **Theme**: bulkdata +- **Worktree**: the triage worktree @ `129238663` (= v4.10.0) +- **Issues processed**: 9 / 9 +- **Scratch orgs provisioned**: 1 - `repro-bulk-p2-dev` (created 2026-05-14 09:48:57 UTC, username `test-9oxwkamse2zq@example.com`, Org Id `00DRt00000Q7HYE`). Provisioned during initial triage but ultimately not needed because all 9 issues turned out to be conclusive from code review and/or in-process unit tests against `cumulusci/tasks/bulkdata/`. Already cleaned up - see "Scratch org cleanup" section below. + +#### Verdict tally + +| Verdict | Count | Issues | +| ---------------------------------- | ----: | ----------------------------------------------- | +| `REPRODUCED-on-v4.10.0` | 7 | #3349, #3353, #3649, #3699, #3700, #3701, #3768 | +| `NOT-REPRODUCED-on-v4.10.0` | 1 | #3360 | +| `INCONCLUSIVE-needs-flaky-network` | 1 | #3936 | +| `closed:duplicate-of-#NNNN` | 0 | - | + +#### Pass-1 recommendations + +| Recommendation | Count | Issues | +| ---------------------------- | ----: | ------------------------------------ | +| `keep-open` | 5 | #3349, #3353, #3649, #3700, #3768 | +| `closed:stale-24mo` | 2 | #3699, #3701 | +| `closed:feature-implemented` | 1 | #3360 | +| `unchanged` | 1 | #3936 (already maintainer kept-open) | + +#### Bug vs feature breakdown + +- **Bugs** (4): #3349, #3700, #3768, #3936 +- All four still present in v4.10.0 (with #3936 environment-dependent for the actual timeout, but the underlying missing-timeout-config gap is confirmed). +- 2 are good-first-issue candidates (#3700 small fix; #3349 medium). +- **Features** (5): #3353, #3360, #3649, #3699, #3701 +- 1 already implemented and should close: #3360 (`action: select`). +- 1 small good-first-issue: #3649 (add `bulk_mode` option to `update_data`). +- 1 active community ask: #3353 (cross-source recipe paths). +- 2 low-priority with workarounds: #3699 (workaround via `soql_filter ORDER BY`), #3701 (related to #3699). + +#### Cross-cutting findings + +1. **`api_options={}` is hardcoded in multiple bulkdata tasks.** `update_data.py:184/211` is the case in #3649, but the same pattern appears in `extract.py:156`. A small refactor to make `api_options` (specifically `bulk_mode`) a first-class task option across all bulk tasks would resolve #3649 and similar friction. + +2. **`MappingStep._get_required_permission_types` is too conservative for upserts.** #3700 is the master-detail variant; the same logic also affects formula fields (read-only) and other intentionally-non-updateable fields used as upsert lookups. A field-shape-aware permission check (using `cascadeDelete` / `relationshipName` / `nillable` from describe) would handle multiple field categories at once. + +3. **RecordType-table naming derives from `sf_object` not `table` (#3349) and the same problem caused #2013.** Both bugs share a root cause: the code assumes a 1:1 mapping between `sf_object` and SQLite table within an extract run. A v5 refactor of mapping_parser to make the `table:` key the unambiguous primary key would fix both. + +4. **Snowfakery `just_once` + `batch_size` (#3768) reflects a deliberate cleanup choice.** `_cleanup_object_tables` drops all object tables from the template before propagating it to subsequent batches, keeping only `_sf_ids`. To fix #3768, CCI needs to detect which objects are referenced via `random_reference`+`just_once` and preserve their rows in the template DB. This is non-trivial and intersects with the Snowfakery dev cutover (see master plan `snowfakery-coordination`). + +5. **No timeout configuration anywhere (#3936).** `get_simple_salesforce_connection` and bulk-job polling never expose a timeout. For corporate-VPN users this manifests as `Read timed out. (read timeout=None)` from a server-side socket close. A v5 fix should add `org.timeout` config and read-timeout retry. + +6. **`action: select` (added Aug 2024) resolves #3360 and likely overlaps several closed enhancement requests not in this bundle.** A grep of historical issues for "lookup existing records" / "reference existing data" might find more candidates to close. + +#### Repro tests written (under `_(repro evidence)_`) + +- `test_3349_recordtype_table_collision.py` - proves `MappingStep` collides record-type table names for two steps sharing `sf_object`. +- `test_3700_master_detail_upsert_perm.py` - proves `_check_field_permission` rejects MD lookup fields under UPSERT. + +Both tests exercise `cumulusci.tasks.bulkdata.mapping_parser` only, no org needed, ~0.3s each. + +#### Scratch org cleanup + +`repro-bulk-p2-dev` was provisioned (see `scratch-info.log`) and has been confirmed cleaned up at the end of the session: + +- `uv run cci org list` shows no `repro-bulk-p2-*` entries. +- `uv run cci org scratch_delete repro-bulk-p2-dev` returns `Org with name 'repro-bulk-p2-dev' does not exist.` (= already cleaned). +- `sf data query --query "SELECT Id, ScratchOrg, SignupUsername FROM ActiveScratchOrg" --target-org the configured DevHub` returns one row whose username does NOT match `test-9oxwkamse2zq@example.com` - i.e. our scratch org is no longer active on the DevHub. +- `sf org list --json` filtered for `repro-bulk-p2` / `test-9oxwkamse2zq` returns `[]`. + +No `repro-bulk-p2-*` aliases or scratch orgs remain. + +#### Deviations + +None. + +# + +- **Worktree**: `/Users/jestevez/work/rel/CumulusCI/.worktrees/repro-deps` +- **HEAD SHA**: `12923866380211161c309f3afb55e67ef18a8890` (= v4.10.0 pin) +- **Total processed**: 5 / 5 +- **Scratch orgs provisioned**: 0 (all issues resolved via Bucket A code-scan + unit-level repro) + +#### Verdict tally + +| Verdict | Count | Issues | +| ------------------------- | ----- | -------------------------- | +| REPRODUCED-on-v4.10.0 | 4 | #3603, #3604, #3619, #3886 | +| NOT-REPRODUCED-on-v4.10.0 | 1 | #3615 | +| INCONCLUSIVE | 0 | - | +| closed:duplicate-of-#NNNN | 0 | - | + +Note: #3604 is a feature request; "REPRODUCED" here means the requested +capability is still missing in v4.10.0. + +#### Recommended pass1 actions + +| Recommendation | Issues | +| ---------------------------------- | -------------------------- | +| keep-open | #3603, #3604, #3619, #3886 | +| closed:not-reproducible-on-v4.10.0 | #3615 | + +#### Cross-cutting findings + +1. **Dependency error messages are inconsistent**. `cumulusci/core/source/github.py` + wraps repo-not-found and release-not-found cases in + `DependencyResolutionError`, but lets ref-not-found leak as raw + `github3.exceptions.NotFoundError` (`'404 [No message]'`). Similarly + `resolvers.py:663`'s "Unable to resolve dependency" message names the dep + but not the strategies tried. Both surface in #3603. A short follow-up + ticket "wrap remaining github3 NotFoundError leaks in dependency code" + would resolve this cleanly. + +2. **Pin model is missing fields that are routine on dependencies**. + `GitHubDependencyPin` (#3619) only carries `github`/`tag` - but a real + pinned dep often needs `password_env_name`. The pin-resolution path + (`pin.pin()`) also bypasses the password-propagation block in + `resolve_dependency()`, so even when a dep declares the password, it gets + silently dropped. Fix is small (add field, propagate in `pin.pin()`). + +3. **Resolution-strategy alias names are confusing**. (#3615) `preproduction` + sounds like "pre-prod = beta" but is in fact an alias for `latest_release`. + This is a documentation/UX problem, not a code bug. Worth a cross-link in + `docs/data.md` or release notes. + +4. **Module-import-time log warnings produce noise on every cci run**. + `select_utils` (#3886) emits at module import; because `extract.py` pulls + it in transitively, the warning fires regardless of whether the user + actually invokes `select` functionality. Common pattern fix: lazy-emit at + first use of optional code path. + +5. **None of these 5 issues required org access**. All were resolvable via + targeted unit tests against in-process mocks. This generalizes the + triage-cost estimate downward for future "dependencies" buckets. + +#### Scratch org aliases used + +None. + +#### Cleanup + +No scratch orgs were provisioned; no cleanup required. + +#### Output files + +- `_(repro evidence)_` +- `_(repro evidence)_` +- `_(repro evidence)_` +- `_(repro evidence; see narrative)_` (4 tests, all passing) +- `_(repro evidence; see narrative)_` (3 tests, all passing) +- `_(repro evidence; see narrative)_` (4 tests, all passing) +- `_(repro evidence; see narrative)_` (2 tests, all passing) + +#### Deviations + +None against bundle constraints. Two observations worth noting: + +1. Mid-session, `git status -sb` briefly showed the branch label as + the triage worktree; by end-of-session it had updated to the + expected the triage worktree. The HEAD SHA was always `129238663` + (= v4.10.0), so all repro evidence is valid against the intended source + state. + +2. `_(repro evidence)_` already contained 3 stale repro files from a prior + subagent run (`test_3603_404_messaging.py`, `test_3619_pin_no_password.py`, + `test_3886_select_optional_deps_warning.py`) that conflicted with my own + tests by sharing module-level imports of `select_utils`. I deleted those + stale files and re-ran my tests cleanly (13/13 passing). Other subagents' + outputs in adjacent `_(repro evidence)_` directories were not touched. + +#### Final test verification + +``` +$ uv run pytest _(repro evidence)_ -v +======================== 13 passed, 1 warning in 0.35s ========================= +``` + +# + +Worktree: `/Users/jestevez/work/rel/CumulusCI/.worktrees/repro-ci-int` @ `129238663` (= v4.10.0) +Theme: `ci-integration` (mixed bucket - issues span Heroku/CI workflow, GitHub Actions, sfdx CLI integration) +Issues processed: 4 (all 4 in bundle-12.json) + +#### Verdict tally + +| Verdict | Count | Issues | +| --------------------------------------------- | ----: | ------------ | +| `REPRODUCED-on-v4.10.0` | 2 | #2153, #3471 | +| `NOT-REPRODUCED-on-v4.10.0` | 1 | #3479 | +| `INCONCLUSIVE-needs-cumulus-actions-workflow` | 1 | #3717 | +| **Total** | **4** | | + +Bug vs feature breakdown: + +- 1 feature (#2153) - REPRODUCED (still unimplemented) +- 3 bugs (#3471 REPRODUCED, #3479 NOT-REPRODUCED, #3717 INCONCLUSIVE) + +#### Recommended pass1 actions + +| Issue | Action | Reasoning | +| ----- | ------------------------------------ | ------------------------------------------------------------------------------------------- | +| #2153 | `keep-open` | Small, well-scoped enhancement to `MergeBranch._create_conflict_pull_request`; still wanted | +| #3471 | `keep-open` | Real bug; replace `compare.behind_by` at `merge.py:251` with a count of merged commits | +| #3479 | `closed:not-reproducible-on-v4.10.0` | Root cause is user GHA shell expansion; cci already wraps the JSON parse error in v4.10.0 | +| #3717 | `unchanged` | Lives at cci ↔ cumulus-actions boundary; mirrors #3418 precedent | + +#### Cross-cutting findings + +1. **`MergeBranch` (`cumulusci/tasks/github/merge.py`) is the locus of two of the four issues** (#2153 and #3471). Both have small, well-localized fixes: + +- #2153: After `self.repo.create_pull(...)` in `_create_conflict_pull_request`, look up open PRs whose `head==branch_name` (the conflicted child branch) and post a comment linking back to the auto-generated conflict PR with resolution steps. +- #3471: Replace `compare.behind_by` in the success log line with `len(list(compare.commits))` (or report the merge SHA returned by `self.repo.merge`). Add a unit test in `test_merge.py` that mocks a `compare` with `behind_by=0` but `files=[…]` to lock in the new behavior. + +2. **cci has no GitHub Actions environment auto-detection.** `project_config.py::repo_info` only handles Heroku CI plus generic `CUMULUSCI_REPO_*` overrides. Repo-wide grep for `GITHUB_REF|GITHUB_HEAD_REF|GITHUB_SHA|GITHUB_ACTIONS` returns zero matches. This is the structural cause behind #3717 (and a contributing factor any time a `push`-triggered GHA job runs cci against a detached HEAD). Two viable directions, both out of scope for triage: + +- Fix in `cumulus-actions/standard-workflows`: set `CUMULUSCI_REPO_BRANCH` from `${{ github.head_ref || github.ref_name }}` and `CUMULUSCI_REPO_COMMIT` from `${{ github.sha }}` before invoking cci. +- Add a `# GitHub Actions` block in `repo_info` parallel to the existing Heroku block. + +3. **Stale 2023-era user-config issues with no reporter follow-up** (#3479) are good `closed:not-reproducible-on-v4.10.0` candidates when (a) the symptom is improved or wrapped in v4.10.0 and (b) the reviewer's clarifying question went unanswered for 2+ years. #3479 fits both criteria. + +4. **Code stability for this theme is high.** Between the issue dates (2020-2023) and v4.10.0: + +- `cumulusci/tasks/github/merge.py` has had only refactors (ruff migration, `create_pull_request_on_conflict` option, `skip_future_releases` fixes) - no change to the `compare.behind_by` reporting line or to PR-creation behavior on conflict. +- `cumulusci/core/config/sfdx_org_config.py` has had the JSON-error wrapper since 2020-11-24. +- `cumulusci/core/config/project_config.py::repo_info` still only auto-detects Heroku. + +#### Scratch org aliases used + +**No scratch needed** - all 4 issues resolved by code-scan against the worktree (Bucket A). DevHub `the configured DevHub` was not touched. + +#### Deviations from instructions + +None. No GitHub mutations, no pushes, no edits under `cumulusci/robotframework/`, no work in the main worktree, no `pyproject.toml`/`uv.lock` changes, no scratch orgs provisioned. + +#### Output files + +- `_(repro evidence)_` +- `_(repro evidence)_` +- `_(repro evidence)_` + +## Per-issue narratives + +Concatenated from each subagent's `narrative.md`. Sorted by issue number. Headings normalized to `### #NNNN`. + +### #733: Prompt to delete scratch org when creating one that already exists + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Method**: code-scan + +**Evidence**: + +```126:140:cumulusci/cli/runtime.py + def check_org_overwrite(self, org_name): + try: + org = self.keychain.get_org(org_name) + if org.scratch: + if org.created: + raise click.ClickException( + f"Scratch org has already been created. Use `cci org scratch_delete {org_name}`" + ) + else: + raise click.ClickException( + f"Org {org_name} already exists. Use `cci org remove` to delete it." + ) + except OrgNotFound: + pass + return True +``` + +Behaviour identical to the 2018 report - hard error, no interactive Y/N prompt. + +**Recommended action**: + +- pass1: `closed:stale-24mo` - 7-year-old `cli-usability` enhancement, no traction. +- pass2 labels: `enhancement,cli-usability,stale` + +--- + +### #808: deploy_packaging flow runs uninstall_packaged_incremental with wrong package name + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug +**Org used**: none (static analysis; live repro requires a real packaging org with a managed package whose name differs from `project__package__name`). + +**Method**: +Read `cumulusci/tasks/salesforce/UninstallPackaged.py` and compared with `cumulusci/tasks/salesforce/install_package_version.py`. + +**Evidence**: + +- `UninstallPackaged._init_options` (UninstallPackaged.py:22-25): + +```22:25:cumulusci/tasks/salesforce/UninstallPackaged.py + def _init_options(self, kwargs): + super(UninstallPackaged, self)._init_options(kwargs) + if "package" not in self.options: + self.options["package"] = self.project_config.project__package__name +``` + +- Compare to `InstallPackageVersion._init_options` (install_package_version.py:75-79) which DOES use the fall-back chain `name_managed -> name -> namespace`. The asymmetry is the bug. +- Bug pattern is unchanged since 2018; no fix has landed. + +**Recommended action**: + +- pass1: `keep-open` - small, contained fix. +- pass2 labels: `bug`, `good-first-issue` + +**Notes**: jlantz's 2018 follow-up about deprecating `project__package__name_managed` (legacy NPSP-only feature) is a separate, larger conversation; for this triage the minimal symmetric fix is enough. + +--- + +### #1348: Multiple Git Provider Support + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Method**: code-scan + +**Evidence**: + +`rg -li "gitlab" cumulusci/` and `rg -li "bitbucket" cumulusci/` both return zero matches. The `ci_feature` flow still hardcodes GitHub-specific tasks: + +```767:789:cumulusci/cumulusci.yml + ci_feature: + group: Continuous Integration + ... + steps: + 0.5: + task: github_parent_pr_notes + ... + 5: + task: github_automerge_feature +``` + +**Recommended action**: + +- pass1: `closed:stale-24mo` - large architectural change (multi-VCS abstraction); 6yr no traction; user `zenibako` confirmed using cci on GitLab via custom flows is feasible. +- pass2 labels: `enhancement,stale,wontfix-candidate` + +--- + +### #1350: Unable to run tasks in remote projects + +**Verdict**: NOT-REPRODUCED-on-v4.10.0 +**Repro type**: bug +**Method**: code-scan + +**Evidence**: + +The original `ModuleNotFoundError: No module named 'tasks'` is fixed via a synthetic `tasks` namespace package and per-source path extension: + +```52:57:cumulusci/core/config/project_config.py +sys.modules.setdefault( + "tasks", types.ModuleType("tasks", "Synthetic package for all repo tasks") +) +import tasks + +tasks.__path__ = [] +``` + +```657:679:cumulusci/core/config/project_config.py + # If I can't load remote code, make sure that my + # included repos can't either. + if not self.allow_remote_code: + spec.allow_remote_code = False + else: + project_config._add_tasks_directory_to_python_path() + + return project_config + + def _add_tasks_directory_to_python_path(self): + # https://stackoverflow.com/a/2700924/113477 + if not self.allow_remote_code: + return False + + directory = str(Path(self.repo_root) / "tasks") + if directory not in tasks.__path__: + self.logger.debug(f"Adding {directory} to tasks.__path__") + tasks.__path__.append(directory) +``` + +The follow-up name-collision concern raised by `prescod` in 2022 (NPSP and EDA both registering `tasks.is_rd2_enabled`) is a **separate** issue from the original report. + +**Recommended action**: + +- pass1: `closed:not-reproducible-on-v4.10.0` - original bug is fixed. +- pass2 labels: `fixed` + +--- + +### #1432: CCI Inconsistencies in validating arguments + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug +**Method**: code-scan + unit test + +**Evidence**: + +Old-style `task_options` dict still does not validate unknown keys: + +```186:196:cumulusci/core/tasks.py + def _validate_options(self): + missing_required = [] + for name, config in list(self.task_options.items()): + if config.get("required") is True and name not in self.options: + missing_required.append(name) + + if missing_required: + required_opts = ",".join(missing_required) + raise TaskOptionsError( + f"{self.__class__.__name__} requires the options ({required_opts}) and no values were provided" + ) +``` + +Repro test passes (= unknown `colour` typo is silently accepted via YAML/Python path): + +``` +$ uv run pytest _(repro evidence; see narrative)_ -q +. [100%] +1 passed in 0.25s +``` + +Test path: `_(repro evidence; see narrative)_`. + +**Mitigation**: Tasks that opted into the new Pydantic `Options` class (lines 159-184) now reject extras with `"extra options"`. So the bug is partially fixed for new-style tasks but persists for legacy ones. + +**Recommended action**: + +- pass1: `closed:stale-24mo` - 5yr; partial mitigation in place; full fix would require reworking every legacy `task_options` dict task. +- pass2 labels: `bug,stale,partially-fixed` + +--- + +### #1769: Unusual case in test_load + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug (test code-smell) +**Org used**: none - code-only +**Method**: `git show` of the original 2020 commit referenced in the issue + grep of the current `test_load.py`. + +**Evidence**: +The original line 352 in `158a2d4356f` (May 2020) was: + +```python +lookups["Id"] = {"table": "accounts", "key_field": "sf_id"} +``` + +In v4.10.0 the same pattern survives, just wrapped in `MappingLookup`: + +```736:739:cumulusci/tasks/bulkdata/tests/test_load.py + lookups["Id"] = MappingLookup(name="Id", table="accounts", key_field="sf_id") + lookups["Primary_Contact__c"] = MappingLookup( + table="contacts", name="Primary_Contact__c" + ) +``` + +The pattern repeats at lines 754, 773, 801, 1119, 1187, 1255 - declaring `Id` as a "lookup" key inside the `lookups` dict so `_expand_mapping` can express the after-step's UPDATE-on-Id dependency. davidmreed acknowledged in 2020 it was "a horrible hack" he intended to clean up, but six years later it is still there, with zero downstream complaints. + +**Recommended action**: + +- pass1: `closed:stale-24mo` - pure test-fixture nit; never escalated to a real bug; original commenters have moved on. +- pass2 labels: `test-cleanup, low-priority` + +--- + +### #2013: Mapping files with steps that are not 1-1 with SObjects are unreliable for extraction + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug +**Org used**: none - code-only +**Method**: Direct unit test against `cumulusci.tasks.bulkdata.utils.create_table` with two `MappingStep` instances both named `Account`. + +**Evidence**: +`create_table_if_needed` (`utils.py:133-139`) tries to detect duplicate tables but the SQLAlchemy `Table()` constructor raises first: + +```133:139:cumulusci/tasks/bulkdata/utils.py +def create_table_if_needed(tablename, metadata, fields: T.List[Column]) -> Table: + t = Table(tablename, metadata, *fields) + inspector = inspect(metadata.bind) + if inspector.has_table(tablename): + raise BulkDataException(f"Table already exists: {tablename}") + t.create(metadata.bind) + return t +``` + +Reproduction (`_(repro evidence; see narrative)_`) yields the exact 2020 traceback: + +``` +Exception type: InvalidRequestError +Exception message: Table 'Account' is already defined for this MetaData instance. +Specify 'extend_existing=True' to redefine options and columns on an existing +Table object. +``` + +**Recommended action**: + +- pass1: `keep-open` - bug is real, easy to reproduce, easy to fix (catch the SQLAlchemy error and re-raise as `BulkDataException`, or validate at mapping-parse time). +- pass2 labels: `bug, bulkdata, extract_dataset, error-handling` + +--- + +### #2096: REST dataloads throw errors that Bulk loads do not + +**Verdict**: NOT-REPRODUCED-on-v4.10.0 +**Repro type**: bug +**Org used**: none - code-only +**Method**: Inspect `RestApiDmlOperation._record_to_json` and `process_bool_arg`; parametric unit test against the full Data Loader spectrum. + +**Evidence**: +REST DML pre-converts boolean columns: + +```778:784:cumulusci/tasks/bulkdata/step.py + def _record_to_json(self, rec): + result = dict(zip(self.fields, rec)) + for boolean_field in self.boolean_fields: + try: + result[boolean_field] = process_bool_arg(result[boolean_field] or False) + except TypeError as e: + raise BulkDataException(e) +``` + +`process_bool_arg` (`core/utils.py:75-83`) accepts the entire spectrum from the Data Loader guide cited in the issue (yes/y/true/on/1 → True; no/n/false/off/0 → False; case-insensitive). Test `_(repro evidence; see narrative)_` confirms 20/20 spectrum values pass. + +**Recommended action**: + +- pass1: `closed:not-reproducible-on-v4.10.0` +- pass2 labels: `resolved, bulkdata` + +--- + +### #2140: Prompt Org Configs when Org Does Not Exist and Command Runs Against It + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Method**: code-scan + +**Evidence**: + +```95:104:cumulusci/cli/runtime.py + def get_org(self, org_name=None, fail_if_missing=True): + if org_name: + org_config = self.keychain.get_org(org_name) + else: + org_name, org_config = self.keychain.get_default_org() + if org_config: + org_config = self.check_org_expired(org_name, org_config) + elif fail_if_missing: + raise click.UsageError("No org specified and no default org set.") + return org_name, org_config +``` + +`keychain.get_org` raises `OrgNotFound` -> `cli/org.py:530-531` shows `"Org {name} does not exist in the keychain"`. No interactive prompt offering available scratch configs. + +**Recommended action**: + +- pass1: `closed:stale-24mo` - 5yr `cli-usability` enhancement with no traction. +- pass2 labels: `enhancement,cli-usability,stale` + +--- + +### #2153: Add comment to original PR which tags all branch subscribers when a merge + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Method**: Bucket A - code-scan against `cumulusci/tasks/github/merge.py` and grep for `create_comment` / `issue_comment` across `cumulusci/tasks/github`. + +**Evidence**: + +`cumulusci/tasks/github/merge.py` `_create_conflict_pull_request` (the only place an auto-merge PR is created): + +```264:288:cumulusci/tasks/github/merge.py + def _create_conflict_pull_request(self, branch_name, source): + """Attempt to create a pull request from source into branch_name if merge operation encounters a conflict""" + if branch_name in self._get_existing_prs( + self.options["source_branch"], self.options["branch_prefix"] + ): + self.logger.info( + f"Merge conflict on branch {branch_name}: merge PR already exists" + ) + return + + try: + pull = self.repo.create_pull( + title=f"Merge {source} into {branch_name}", + base=branch_name, + head=source, + body="This pull request was automatically generated because " + "an automated merge hit a merge conflict", + ) + self.logger.info( + f"Merge conflict on branch {branch_name}: created pull request #{pull.number}" + ) + except github3.exceptions.UnprocessableEntity as e: + self.logger.error( + f"Error creating merge conflict pull request to merge {source} into {branch_name}:\n{e.response.text}" + ) +``` + +The method only creates the conflict PR; it never opens a comment on any PR (the original child PR or otherwise). Repo-wide grep for `create_comment|issue_comment|pr.create_comment|comment.*pull_request` under `cumulusci/tasks/github` returns no hits in production code (only test fixtures). Davis Agli's response in the thread agreed with the issue reporter that the appropriate place would be a comment on the original PR. Feature is unimplemented in v4.10.0. + +**Recommended action**: + +- pass1: `keep-open` - small, well-scoped enhancement; reasonable "good-second-issue". +- pass2 labels: `enhancement, github, merge-conflict` + +--- + +### #2325: Task to turn off validation rules to allow data insert + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Org used**: none - code-only +**Method**: Grep cumulusci.yml + `cci task list` for any `validation_rule` / `disable_*` task. + +**Evidence**: + +- Trigger analog: `disable_tdtm_trigger_handlers` / `restore_tdtm_trigger_handlers` (`cumulusci.yml:738-747`). +- DuplicateRule analog: `set_duplicate_rule_status` → `cumulusci.tasks.metadata_etl.duplicate_rules.SetDuplicateRuleStatus` (a 25-line `MetadataSingleEntityTransformTask` subclass with `entity = "DuplicateRule"`). +- ValidationRule equivalent: **none.** `cci task list | grep -i -E "validation|rule"` returns only `set_duplicate_rule_status`. + +The pattern to copy is trivially established. The user's specific use case (relative-date hearings during dataset insert) is exactly what `disable_tdtm_trigger_handlers` solves for triggers; ValidationRule needs the same. + +**Recommended action**: + +- pass1: `keep-open` - feature still missing, clear implementation pattern, modest scope. +- pass2 labels: `enhancement, bulkdata, metadata_etl, good-first-issue` + +--- + +### #2402: Create a --rebuild-org parameter for cci flow run + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Method**: code-scan + +**Evidence**: + +```119:145:cumulusci/cli/flow.py +@flow.command(name="run", help="Runs a flow") +@click.argument("flow_name") +@click.option("--org", help="Specify the target org. By default, runs against the current default org") +@click.option("--delete-org", is_flag=True, help="If set, deletes the scratch org after the flow completes") +@click.option("--debug", is_flag=True, help="Drops into pdb, the Python debugger, on an exception") +@click.option("-o", nargs=2, multiple=True, help="...") +@click.option("--no-prompt", is_flag=True, help="...") +@pass_runtime(require_keychain=True) +def flow_run(runtime, flow_name, org, delete_org, debug, o, no_prompt): +``` + +Only `--delete-org` exists. `rg -i "rebuild.org"` returns zero hits. + +**Recommended action**: + +- pass1: `closed:stale-24mo` - 5yr; (no movement); user can accomplish via `cci org scratch_delete X && cci flow run`. +- pass2 labels: `enhancement,stale` + +--- + +### #2505: Filtering records to be extracted + +**Verdict**: NOT-REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Org used**: none - code-only +**Method**: Grep `mapping_parser.py` and `extract.py` for `soql_filter` / WHERE-clause support. + +**Evidence**: +Per-step SOQL filter is now first-class: + +```120:120:cumulusci/tasks/bulkdata/mapping_parser.py + soql_filter: Optional[str] = None # soql_filter property +``` + +```142:147:cumulusci/tasks/bulkdata/extract.py + if mapping.soql_filter is not None: + soql = self.append_filter_clause( + soql=soql, filter_clause=mapping.soql_filter + ) +``` + +The new extract-mapping generator wires user-declared `where:` into `soql_filter` (`extract_mapping_file_generator.py:26`), and the existing hardcoded extract declarations (`extract_dataset_utils/hardcoded_default_declarations.py`) already use this for per-sObject filtering. Tests exercise the plain filter, the WHERE-prefixed variant, and combinations with record_type (`test_extract.py:1216, 1248, 1280`). + +**Recommended action**: + +- pass1: `closed:feature-implemented` +- pass2 labels: `resolved, bulkdata, extract_dataset` + +--- + +### #2506: Bulk Operations should have a --debug mode which maintains logs and tempfiles + +**Verdict**: REPRODUCED-on-v4.10.0 (partial) +**Repro type**: feature +**Org used**: none - code-only +**Method**: Grep all bulkdata modules for `get_debug_mode` / `delete=False` / `TemporaryDirectory` usage. + +**Evidence**: + +- Snowfakery task (`snowfakery.py:241,355,385,565`) calls `get_debug_mode()` and at `:386` logs `f"Working Directory: {tempdir}"` per loop iteration. +- `extract.py`, `step.py` - **zero** references to `debug_mode` or `get_debug_mode`. +- `load.py:283` uses `tempfile.TemporaryFile` with no debug-mode override; the file is auto-deleted on context exit regardless of debug. + +So the ask is half-met: Snowfakery cooperates with debug mode; the workhorse `load_dataset` / `extract_dataset` tasks do not. + +**Recommended action**: + +- pass1: `keep-open` - partial implementation; remaining work is straightforward (wire `get_debug_mode()` into load/extract, conditionally use `TemporaryDirectory(delete=False)` and emit path). +- pass2 labels: `enhancement, bulkdata, extract_dataset, load_dataset, debugging` + +--- + +### #2507: Undo mode for CumulusCI Insert + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Method**: code-scan + +**Evidence**: + +No `undo_insert` task exists (`rg -l undo_insert` returns nothing). Closest functionality is `enable_rollback` on `load_dataset` and `snowfakery` tasks: + +```97:99:cumulusci/tasks/bulkdata/load.py + "enable_rollback": { + "description": "When True, performs rollback operation incase of error. Defaults to False" + }, +``` + +That only triggers rollback on error during the load; it does not provide the post-hoc "delete everything we ever inserted" capability the requester described. + +**Recommended action**: + +- pass1: `closed:stale-24mo` - 4yr feature with partial mitigation already shipped. +- pass2 labels: `enhancement,stale,partially-fixed` + +--- + +### #2508: Manual load retries + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Org used**: none - code-only +**Method**: `cci task list | grep -i retry` and grep load.py for retry/failed-record persistence. + +**Evidence**: + +- `cci task list` returns no retry-named task. +- `load.py` has an `enable_rollback` option (`:97-98`, `RollbackType` enum at `:1051`) but rollback **undoes successful inserts when failures occur** - the opposite of "retry the failures." +- `RowErrorChecker` (`utils.py:158`) only logs and optionally raises; it does not persist a failed-rows artifact that could be replayed. + +**Recommended action**: + +- pass1: `keep-open` - distinct from rollback; would need (a) failed-row CSV/SQL persistence + (b) a new `retry_failed_load` task that consumes it. +- pass2 labels: `enhancement, bulkdata, load_dataset, reliability` + +--- + +### #2697: 'namespaced' field stale in `cci org info` after switching org def + +**Verdict**: INCONCLUSIVE-needs-scratch-slot +**Repro type**: bug +**Method**: code-scan (no scratch provisioned) + +**Evidence**: + +The cci `namespaced` field is set from the cci YAML config, not derived from the SFDX scratch def file: + +```57:75:cumulusci/core/keychain/base_project_keychain.py + def create_scratch_org( + self, org_name, config_name, days=None, set_password=True, release=None + ): + """Adds/Updates a scratch org config to the keychain from a named config""" + scratch_config = self.project_config.lookup(f"orgs__scratch__{config_name}") + ... + scratch_config["scratch"] = True + scratch_config.setdefault("namespaced", False) +``` + +This means `cci org info` reflects the cci `orgs__scratch__qa.namespaced` value, not whatever the SFDX `qa.json` says. Since the user changed only the SFDX `qa.json` (not the cci YAML), cci's view of `namespaced` is unchanged. This is **likely working as designed** but conflicts with the user's mental model. + +I did not provision a scratch org to confirm because (a) the code reading is unambiguous and (b) DevHub-slot conservation. Marking inconclusive only because final verification of the `cci org info` output panel was not exercised live. + +**Recommended action**: + +- pass1: `closed:stale-24mo` - 5yr stale; design/docs friction rather than a defect; request more info if reopened. +- pass2 labels: `bug,stale,needs-info` + +--- + +### #2826: deploy_unmanaged flow is supposed to silently do nothing if there's not actually a package directory + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug (enhancement-flavored) +**Org used**: none (purely local task) + +**Method**: +Inspected `cumulusci/tasks/metadata/package.py`: + +- `PackageXmlGenerator.parse_types` (line 107) calls `os.listdir(self.directory)` without checking existence. +- `UpdatePackageXml._init_task` (line 590) and `_run_task` (line 612) do not guard for missing path. + +Wrote a unit-level repro at `_(repro evidence; see narrative)_` that instantiates `UpdatePackageXml` against a temp dir containing no `src/`. Result: raises `FileNotFoundError: [Errno 2] No such file or directory: '/var/.../src'`. Bug present. + +**Recommended action**: + +- pass1: `keep-open` - small, contained fix. +- pass2 labels: `bug`, `good-first-issue` + +**Notes**: Could be fixed at the task layer (skip with logger.info when path missing) or at the flow layer (`when:` guard on the deploy_unmanaged steps). Task-layer is more defensible because the same task may be referenced from custom flows. + +--- + +### #2951: Error in task load_dataset - Standard_price_not_defined + +**Verdict**: REPRODUCED-on-v4.10.0 (with mitigation) +**Repro type**: bug +**Org used**: none - code-only +**Method**: Grep load.py / step.py for any PricebookEntry-specific sequencing logic; inspect `hardcoded_default_declarations.py`. + +**Evidence**: +No special handling exists in the loader to sequence Standard-Price-Book PricebookEntries before custom-pricebook PricebookEntries within a single mapping step. Within one Bulk job, records are processed in parallel within batches and Salesforce throws `STANDARD_PRICE_NOT_DEFINED` when a custom price is created before the matching standard price. + +Mitigation already in place for the `extract_dataset` → `load_dataset` round-trip: + +```14:18:cumulusci/tasks/bulkdata/extract_dataset_utils/hardcoded_default_declarations.py + ExtractDeclaration( + sf_object="PricebookEntry", + where="Pricebook2.Id != NULL and Pricebook2.Name != 'Standard Price Book'", + ), + ExtractDeclaration(sf_object="Pricebook2", where="Name != 'Standard Price Book'"), +``` + +So default extracts skip the Standard Price Book entirely, and the typical flow doesn't hit the bug. But the reporter built a mapping by hand that included both, and that path is still broken. + +**Recommended action**: + +- pass1: `keep-open` - bug is latent. Fix options: (a) loader auto-splits PricebookEntry into two implicit batches (standard pricebook first); (b) document the requirement and validate at parse time that PricebookEntry steps are not "mixed." +- pass2 labels: `bug, bulkdata, load_dataset, pricebook, documentation` + +--- + +### #2979: deploy task should deploy from default entry in packageDirectories + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature + +**Method**: +Inspected the `deploy` task definition in `cumulusci/cumulusci.yml` and the `default_package_path` helper in `cumulusci/core/config/project_config.py`. Searched for `default_package_path` usages across the codebase to see whether any deploy/Deploy task consumes it. + +**Evidence**: + +- `cumulusci/cumulusci.yml:227-229` - the `deploy` task still hard-codes `path: src` for `cumulusci.tasks.salesforce.Deploy`. +- `cumulusci/core/config/project_config.py:517-525` - `default_package_path` correctly reads `packageDirectories[*].default` from `sfdx-project.json` when `project__source_format == "sfdx"`. +- The only consumer of `default_package_path` is `cumulusci/tasks/create_package_version.py:230`. The Deploy task does not consult it. + +**Recommended action**: + +- pass1: `keep-open` - feature still missing; davisagli's 2021 design comment (3-tier fallback `path` -> sfdx default -> `src`) remains the natural plan. +- pass2 labels: `severity:low,area:packaging,area:sfdx,state:needs-design` + +--- + +### #3015: Remove imported dx org from cci list without deleting actual scratch + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Method**: code-scan + +**Evidence**: + +```519:543:cumulusci/cli/org.py +@org.command(name="remove", help="Removes an org from the keychain") +@orgname_option_or_argument(required=True) +@click.option("--global-org", is_flag=True, help="...") +@pass_runtime(require_project=False, require_keychain=True) +def org_remove(runtime, org_name, global_org): + try: + org_config = runtime.keychain.get_org(org_name) + except OrgNotFound: + raise click.ClickException(f"Org {org_name} does not exist in the keychain") + + if org_config.can_delete(): + click.echo("A scratch org was already created, attempting to delete...") + try: + org_config.delete_org() + ... + runtime.keychain.remove_org(org_name, global_org) +``` + +No `--keep-org` flag. davisagli's manual workaround (delete `~/.cumulusci//.org` directly) still applies. + +**Recommended action**: + +- pass1: `closed:stale-24mo` - 4yr; . +- pass2 labels: `enhancement,stale` + +--- + +### #3024: Order of flow groups in `cumulusci/cumulusci.yml` + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Method**: code-scan + +**Evidence**: + +Sequence of unique `group:` values in `cumulusci/cumulusci.yml` (first appearance): + +``` +Metadata Transformations +Salesforce Users +Salesforce +Salesforce Preflight Checks +Utilities +Data Operations +Salesforce Communities +Setup +Salesforce Packages +Salesforce Metadata +Marketing Cloud +OmniStudio +Salesforce DX +GitHub +Release Operations +Push Upgrades +Salesforce Bulk API +Robot Framework +NPSP/EDA +Continuous Integration +Post-Install Configuration +Dependency Management +``` + +`Continuous Integration` is buried near the bottom; `Org Setup` (the user's preferred name) does not exist (uses `Setup`); ordering does not match the user's request. + +**Recommended action**: + +- pass1: `closed:stale-24mo` - 4yr cosmetic VS Code extension request; the true fix is sorting at the consumer (the extension) rather than rearranging the canonical YAML. +- pass2 labels: `enhancement,stale` + +--- + +### #3137: cci task run update_package_xml and Salesforce Case Custom Object + +**Verdict**: REPRODUCED-on-v4.10.0 (treated as feature request) +**Repro type**: feature + +**Method**: +Inspected `cumulusci/tasks/metadata/package.py` `CustomObjectParser` (lines +443-482). It explicitly skips any object file that doesn't end in `__c`, +`__mdt`, `__e`, or `__b`. Inspected `UpdatePackageXml` task_options +(package.py:563-584) - no opt-in option such as `include_standard_objects`. +The maintainer's response on the issue agreed the behavior is by-design +(holdover from managed-package world) and committed only to "investigate"; +no design has landed. + +**Evidence**: + +- `cumulusci/tasks/metadata/package.py:451-458` skips standard objects. +- `cumulusci/tasks/metadata/package.py:562-584` `UpdatePackageXml.task_options` + has only `path`, `output`, `package_name`, `managed`, `delete`, + `install_class`, `uninstall_class`. + +**Recommended action**: + +- pass1: keep-open - a real product gap remains; mark for design discussion. +- pass2 labels: `severity:low,area:metadata-etl,type:enhancement,state:needs-design` + +--- + +### #3161: Ability to Hide Option Values When Using Task Options + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Method**: code-scan + +**Evidence**: + +```300:320:cumulusci/core/flowrunner.py + def _log_options(self, task: "BaseTask"): + ... + for key, info in task.task_options.items(): + value = task.options.get(key) + if value is not None: + if type(value) is not list: + value = self._obfuscate_if_sensitive(value, info) + task.logger.info(f" {key}: {value}") + ... + + def _obfuscate_if_sensitive(self, value: str, info: dict) -> str: + if info.get("sensitive"): + value = 8 * "*" + return value +``` + +A masking infrastructure was added (task-option metadata can opt in via `sensitive: True`), but: + +1. The Robot `vars` option is not marked sensitive (`cumulusci/tasks/robotframework/robotframework.py:54-56`). +2. There's no CLI/Robot-side flag for the user to mark an ad-hoc `-o` value as sensitive. + +So the user's specific request (mask multi-line GitHub Actions secrets passed via `-o robot__vars …`) is **partially mitigated** but not fully solved. + +**Recommended action**: + +- pass1: `closed:stale-24mo` - 4yr; partial fix in place. +- pass2 labels: `enhancement,stale,partially-implemented` + +--- + +### #3165: Update Admin Profile task fails when specifying record types without custom package.xml + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug +**Org used**: none for the unit repro; live test would need a Case "Case" record type in the scratch org + +**Method**: +Read `cumulusci/tasks/salesforce/update_profile.py` carefully: + +- `_generate_package_xml(RETRIEVE)` calls `_expand_package_xml(package_xml)` only when `include_packaged_objects=True` (line 137-138). +- `_expand_package_xml` calls `_expand_package_xml_objects(package_xml)` (line 182). +- `_expand_package_xml_objects` (line 184-201) is what walks `record_types` and inserts each referenced object into `/`. +- Therefore: when `include_packaged_objects` is False (the default unless `minimum_cumulusci_version >= 3.9.0`), record_types referencing objects not in the default `cumulusci/files/admin_profile.xml` (e.g. `Case`) never get added. +- Confirmed by the existing test `test_init_options__include_packaged_objects` (test_ProfileGrantAllAccess.py:609-615) which explicitly asserts `task._expand_package_xml.assert_not_called()` in the False branch. + +Wrote `_(repro evidence; see narrative)_`. Output (key parts): + +``` +include_packaged_objects = False +--- generated package.xml --- + + + + * + Account + Campaign + ... + Opportunity + CustomObject + + ... + +--- end --- +REPRODUCED: 'Case' is NOT in CustomObject members. +``` + +**Recommended action**: + +- pass1: `keep-open` - single-line refactor. +- pass2 labels: `bug` + +**Notes**: Smallest fix at update_profile.py:137 - always call `self._expand_package_xml_objects(package_xml)` regardless of `include_packaged_objects`, and only call the broader `self._expand_package_xml` (which does the Tooling API query) when `include_packaged_objects=True`. `_expand_package_xml_objects` itself only walks the user-supplied options, no API call. + +--- + +### #3167: Add ability to define page layout assignments with record types using the update_admin_profile task + +**Verdict**: NOT-REPRODUCED-on-v4.10.0 (feature implemented) +**Repro type**: feature +**Org used**: none + +**Method**: +Read `cumulusci/tasks/salesforce/update_profile.py`: + +- `task_options['record_types']` description (lines 32-34) explicitly documents the `page_layout` key. +- `_set_record_types` lines 280-298 handle `page_layout`: locate any existing `` matching the record type and update its ``, or append a new `` block. + +`git log -S "page_layout" -- cumulusci/tasks/salesforce/update_profile.py` shows the feature landed in commit `f2ff04bd5` (PR #3243 by davidmreed, merged 2022-06-16) - well before v4.10.0. + +**Recommended action**: + +- pass1: `close-as-implemented` with reference to PR #3243. +- pass2 labels: `enhancement` + +**Notes**: Could also add a small docs example. + +--- + +### #3283: json parser error when empty string passed for date field during upsert or update + +**Verdict**: NOT-REPRODUCED-on-v4.10.0 +**Repro type**: bug +**Org used**: none - code-only +**Method**: Verify PR #3361 (commit `b0bfb70e0`) is in `129238663` via `git merge-base --is-ancestor`; small unit test against `RestApiDmlOperation._record_to_json` for UPDATE/UPSERT/INSERT operations. + +**Evidence**: +Fix is in place at `step.py:795-796`: + +```789:797:cumulusci/tasks/bulkdata/step.py + if self.operation is DataOperationType.INSERT: + result = { + k: result[k] + for k in result + if result[k] is not None and result[k] != "" + } + elif self.operation in (DataOperationType.UPDATE, DataOperationType.UPSERT): + result = {k: (result[k] if result[k] != "" else None) for k in result} +``` + +Repro test (`_(repro evidence; see narrative)_`) verifies: empty `Birthdate` becomes JSON null on UPDATE and UPSERT (no longer triggering JSON_PARSER_ERROR), and is omitted entirely for INSERT. The reporter's own final comment ("Fixed in #3361") aligns. + +**Recommended action**: + +- pass1: `closed:fixed-by-pr-#3361` +- pass2 labels: `resolved, bulkdata, load_dataset` + +### #3307: Project Template Support for `cci project init` + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Method**: code-scan + +**Evidence**: + +`rg "template" cumulusci/cli/project.py` only finds references to internal Jinja templates rendered from `cumulusci/files/templates/project` (lines 220-329). No `--template` CLI option exists. `project_init` (line 41) takes no template-source argument. + +**Recommended action**: + +- pass1: `closed:stale-24mo` - 4yr; explicitly described by the requester as "low priority / nice to have". +- pass2 labels: `enhancement,stale` + +--- + +### #3320: Metadata ETL task to Deactivate a Flow + +**Verdict**: NOT-REPRODUCED-on-v4.10.0 +**Repro type**: feature + +**Method**: +Greped for `[Dd]eactivate.*[Ff]low|DeactivateFlow|deactivate_flow` across the +codebase and inspected `cumulusci/cumulusci.yml`. + +**Evidence**: + +- `cumulusci/cumulusci.yml:10-15` ships a `deactivate_flow` task that + reuses `cumulusci.tasks.salesforce.activate_flow.ActivateFlow` with + `status: False`. +- `cumulusci/tasks/salesforce/activate_flow.py:33-65` toggles + `activeVersionNumber` to 0 when `status` is falsy, i.e. real deactivation. + +**Recommended action**: + +- pass1: closed:feature-implemented - `cci task run deactivate_flow` is + shipped; reporter likely missed it. +- pass2 labels: `area:metadata-etl,type:enhancement,resolution:already-implemented` + +--- + +### #3331: Task update_package_xml does not write correct package.xml for AssignmentRules + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug + +**Method**: +Read `cumulusci/tasks/metadata/metadata_map.yml` and wrote two tests: one +that asserts the YAML mapping is correct, one that runs `PackageXmlGenerator` +on a sample `assignmentRules/Case.assignmentRules` fixture and asserts the +emitted `` element is `AssignmentRules` (plural). + +**Evidence**: + +- `cumulusci/tasks/metadata/metadata_map.yml:45-48` maps the + `assignmentRules` folder to `type: AssignmentRule` (singular). MDAPI + expects the plural type name (cf. `autoResponseRules:` at lines 60-63 + which is already correctly `AutoResponseRules`). +- Test output: both `test_issue_3331_*` tests fail; observed + `AssignmentRule` instead of `AssignmentRules`. + +**Recommended action**: + +- pass1: keep-open - one-line YAML fix; reporter offered a PR. +- pass2 labels: `severity:medium,area:metadata-etl,type:bug,good-first-issue` + +--- + +### #3347: Cannot release an unlocked beta package with `release_unlocked_beta` + +**Verdict:** `NOT-REPRODUCED-on-v4.10.0` + +### Original symptom + +User on CumulusCI 3.64.0 (2022-08) ran `cci flow run release_unlocked_beta` and the first task `create_package_version` died with a confusing low-level error: + +``` +TypeError: expected str, bytes or os.PathLike object, not NoneType + at create_package_version.py line 377: + with open(self.org_config.config_file, "r") as f: +``` + +Root cause: `org_config.config_file` is `None` for persistent orgs (DevHub, Developer Edition, etc.), but the user (likely) ran the flow against the DevHub directly. `release_unlocked_beta` requires a scratch target org; the old code fell through with an opaque error. + +### Evidence on v4.10.0 + +`cumulusci/tasks/create_package_version.py` lines 44-46 and 158-159: + +```44:46:cumulusci/tasks/create_package_version.py +PERSISTENT_ORG_ERROR = """ +Target org scratch org definition file missing. Persistent orgs like a Dev Hub can't be used for 2GP package uploads. +""" +``` + +```158:159:cumulusci/tasks/create_package_version.py + if not self.org_config.config_file: + raise TaskOptionsError(PERSISTENT_ORG_ERROR) +``` + +The early `_init_options` check raises a clear `TaskOptionsError` before any `open()` is attempted. Fix landed in commit `2a9cadcb1` on 2023-10-12 (`added_clear_error`), refined in `8f62d3153` and `8328bfb9d`. + +### Test verification + +```text +$ uv run pytest "cumulusci/tasks/tests/test_create_package_version.py::TestPackageConfig::test_org_config" -x +1 passed in 0.22s +``` + +The test (`test_create_package_version.py:159-167`) explicitly sets `org_config.config_file = None` and asserts that `CreatePackageVersion` raises `TaskOptionsError` matching `PERSISTENT_ORG_ERROR`. + +### Recommendation + +- **Pass 1:** close-with-comment ("Fixed by commit `2a9cadcb1` (PR adding clear error message). v4.10.0 raises a clear `TaskOptionsError` directing users to use a scratch org target.") +- **Pass 2 label:** `resolved-by-clear-error-message` +- The underlying limitation - cannot use a persistent (DevHub) org as the target for 2GP package upload - is preserved by design and now communicated clearly. + +--- + +### #3349: Make generated dataset recordType tables unique based on table instead of sf_object + +**Verdict:** `REPRODUCED-on-v4.10.0` - bug, structural + +`MappingStep.get_source_record_type_table()` and `get_destination_record_type_table()` in `cumulusci/tasks/bulkdata/mapping_parser.py:177-179` build the SQLite table name solely from `self.sf_object` (`f"{self.sf_object}_rt_mapping"` and `f"{self.sf_object}_rt_target_mapping"`). Two mapping steps targeting the same `sf_object` (the canonical case is `Account` Person vs Business with different `record_type:` values) thus produce the same table name. `load.py:552` and `extract.py:259/393` both use those names without per-step disambiguation. + +Repro test (`_(repro evidence; see narrative)_`) constructs two `MappingStep` objects with `sf_object="Account"` and different `record_type` values and asserts that both source and target table names collide - assertion passes, confirming the bug. + +The maintainer label `wi-created` is present, so this is exempt from the stale-24mo close. **Recommended pass1: `keep-open`.** Suggested fix: include `self.table` (or a hash of `record_type`+`filter`) in the generated table name when more than one mapping step shares an `sf_object`. + +### #3353: Enable Snowfakery task to use recipes from other repositories + +**Verdict:** `REPRODUCED-on-v4.10.0` - feature still unimplemented + +`Snowfakery._validate_options` in `cumulusci/tasks/bulkdata/snowfakery.py:159-162` validates the `recipe` option via `Path(recipe).exists()` only. There is no `SOURCE_NAME:path` parsing, and no call to `project_config.sources` / `project_config.get_source(...)` anywhere in `snowfakery.py`. The recipe string is passed straight to Snowfakery as a filesystem path. + +This is a documented community ask (resurfaced in 2024-08 by `davidjray` and `jnesong`), not just a single reporter. Workaround today is `cci org import` from inside the source repository. + +**Recommended pass1: `keep-open`.** Suggested fix: pre-process the `recipe` option to detect a `SOURCE_NAME:path` prefix and resolve it via `project_config.get_source(name).fetch().path` before existence check. + +### #3360: Read Only Object Lookup for Load_Dataset + +**Verdict:** `NOT-REPRODUCED-on-v4.10.0` - feature implemented + +The `action: select` mapping step (added by commit `b15945203`, "Core Logic for Selecting Records from Target Org", 2024-08-19, well before v4.10.0) provides exactly this behavior. It is fully wired through `cumulusci/tasks/bulkdata/select_utils.py`, the `SELECT` branch in `step.py`, and the `mapping_select.yml` test fixture. `select_options.strategy` (`similarity`, etc.), `select_options.filter`, and `select_options.priority_fields` allow the user to populate a lookup table from existing org records without DML, which is precisely the "read-only" semantic the issue requested. + +**Recommended pass1: `closed:feature-implemented`.** Close-comment should cite the SELECT action and `mapping_select.yml`. + +### #3418: Error creating 1gp release + +**Verdict**: INCONCLUSIVE-needs-cumulus-actions-workflow +**Repro type**: bug +**Org used**: none (no cci code path under test) + +**Method**: +Read the cci docs and source for any code that would invoke `auth:sfdxurl:store` or otherwise parse the `PACKAGING_ORG_AUTH_URL` secret. The error message in the issue is verbatim text from the sfdx CLI (`auth:sfdxurl:store`), invoked by the `SFDO-Community/standard-workflows/.github/workflows/production-1gp.yml` workflow before cci ever runs. Searched the repo for `sfdxurl|SFDX_AUTH_URL|PACKAGING_ORG_AUTH_URL` - only hit is `docs/github-actions.md` (documentation pointing users at the standard-workflows repo). + +**Evidence**: + +- `docs/github-actions.md:101-119` - examples reference `SFDO-Community/standard-workflows/.github/workflows/{beta,production}-1gp.yml@main` and the `packaging-org-auth-url` secret. +- No occurrence of `auth:sfdxurl:store` anywhere in the cci tree. +- davidmreed's 2022 comment on the issue: "I believe I know the issue here and I will seek to address it this evening" - implies the fix landed in the third-party workflow, not in cci. +- Issue is still OPEN on GitHub but no `closedByPullRequestsReferences`. + +**Recommended action**: + +- pass1: `unchanged` - keep open until we can confirm whether SFDO-Community fix is in place; transfer or cross-link there if appropriate. +- pass2 labels: `needs-info`, `needs-repro` + +--- + +### #3429: Support overriding `cumulusci.yml` to be used for configuration + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature + +**Method**: +Searched for `CUMULUSCI_YML`, `CUMULUSCI_EXTRA_YAML`, `extra_yaml`, `--extra-yaml`, `--configFile` across the v4.10.0 source. Inspected `BaseProjectConfig.__init__` and `_load_config` to see how YAML is layered. Verified the `extra-yaml-cli-flag` branch is NOT an ancestor of HEAD. + +**Evidence**: + +- `cumulusci/core/config/project_config.py:82` - `config_filename = "cumulusci.yml"` is hardcoded. +- `cumulusci/core/config/project_config.py:118-184` - only an `additional_yaml` kwarg (programmatic, used by MetaCI) is supported; no env var or CLI plumbing. +- `git merge-base --is-ancestor 9d650ace2 HEAD` returns non-zero - PR #3969 (commits prefixed `feat(cli): add resolve_extra_yaml helper for --extra-yaml flag`) is in flight on branch `extra-yaml-cli-flag` but not merged. +- Bundle records `closedByPullRequestsReferences = [#3969]`. + +**Recommended action**: + +- pass1: `keep-open` - feature is genuinely actionable on v4.10.0; auto-close once #3969 merges via `closed:fixed-by-pr-#3969`. +- pass2 labels: `severity:medium,area:packaging,area:cli,state:in-progress` + +--- + +### #3440: Enhance `default_package_path` to serve multi-package projects better + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature + +**Method**: +Re-read `default_package_path` against the request. The user wants name-based lookup with warnings when the package alias does not match the project name and a hard fail when no default and no `force-app` exist. + +**Evidence**: + +- `cumulusci/core/config/project_config.py:517-525` - implementation is the simple "first packageDirectory with `default: true`" pattern; falls back to `force-app`, then `src`. No name-based lookup, no multi-package warning, no hard fail when both are missing. + +**Recommended action**: + +- pass1: `keep-open` - same multi-package umbrella as #2979 / #3429; would best be solved together. +- pass2 labels: `severity:low,area:packaging,area:sfdx,area:multi-package` + +--- + +### #3441: `cci task run create_package_version` should allow `version_base: default` + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature + +**Method**: +Inspected `version_base` handling in `cumulusci/tasks/create_package_version.py` and the `release_unlocked_beta` flow definition in `cumulusci.yml`. + +**Evidence**: + +- `cumulusci/tasks/create_package_version.py:63-112` - `version_base: Optional[str]`; documented values are `None`, a literal version number, or `latest_github_release`. +- `cumulusci/tasks/create_package_version.py:529-563` - `_get_base_version_number` only branches on `None` (default) and `"latest_github_release"`; any other string is parsed as a literal version. There is no `"default"`/`"highest"` sentinel and no support for unsetting via flow override. +- `cumulusci/cumulusci.yml:1216-1225` - `release_unlocked_beta` hard-codes `version_base: latest_github_release` for `create_package_version`. Per yippie's comment, CCI lacks a generic null-override mechanism for flow steps. + +**Recommended action**: + +- pass1: `keep-open` - could be solved minimally with a `default`/`highest` sentinel in `_get_base_version_number`, or generalized as a CCI null-override feature. +- pass2 labels: `severity:low,area:packaging,area:flow-overrides,area:cli` + +--- + +### #3446: CCI task push_qa crashes for Unlocked package with no namespace + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug +**Org used**: `[scratch-org]` (dev) - used to confirm push_qa even runs against a non-packaging org; the user's actual NoneType crash happens before the SOQL would succeed on a real packaging org. + +**Method**: +The user's command omitted `--version` and `--version_id`. Inspected `cumulusci/tasks/push/tasks.py` `_run_task`: when neither is set, it calls `self._get_version(package, self.options.get("version"))`, which calls `self._parse_version(version)` on `version=None`, which calls `version.split(".")`. This raises `AttributeError: 'NoneType' object has no attribute 'split'` - exactly the error from the linked gist in the issue. Verified by direct unit-style invocation: + +```python +BaseSalesforcePushTask._parse_version(None, None) # -> AttributeError +``` + +Also ran `cci task run push_qa --orgs --metadata_package_id 0337S000000DUMMY` against a scratch org; the path failed earlier on the org (no Push API on a dev scratch - `sObject type 'MetadataPackage' is not supported`), but on a real Push-API-enabled DevHub it would surface the NoneType crash. + +The user's follow-up comment ("Push API not activated by default") is a separate UX issue: when Push API is disabled, the SOQL `SELECT ... FROM MetadataPackage` fails with `INVALID_TYPE`. They'd like a friendlier error. + +**Evidence**: + +- `cumulusci/tasks/push/tasks.py:33` - `version_parts = version.split(".")` (no None-guard above). +- `cumulusci/tasks/push/tasks.py:283-297` - `_run_task` does not validate `version` before calling `_get_version`. +- Test: `_(repro evidence; see narrative)_` passes on v4.10.0. +- Live invocation against scratch org: `Error: Malformed request ... sObject type 'MetadataPackage' is not supported` (orthogonal to the NoneType but illustrates the second comment). + +**Recommended action**: + +- pass1: `keep-open` - real bug, simple fix. +- pass2 labels: `bug`, `good-first-issue` + +--- + +### #3466: Ability to specify a test suite to run instead of just `test_name_match` + +**Verdict**: NOT-REPRODUCED-on-v4.10.0 +**Repro type**: feature + +**Method**: +Searched for `test_suite_names` in `cumulusci/tasks/apex/testrunner.py`. Wrote `_(test pending harvest)_` to assert the option exists, has a sensible description, and is mutually exclusive with `test_name_match`. + +**Evidence**: + +- `cumulusci/tasks/apex/testrunner.py:173-175` - `test_suite_names` is a documented `task_options` field accepting a comma-separated list of ApexTestSuite names. +- `cumulusci/tasks/apex/testrunner.py:188-190, 246-253, 308-376` - option is wired through `_init_options`, validated as mutually exclusive with `test_name_match`, and used by `_get_test_classes_from_test_suite_names` (queries ApexTestSuite + TestSuiteMembership). +- Repro test passes (2/2) on v4.10.0. + +**Recommended action**: + +- pass1: `closed:feature-implemented` - the request is fully covered. davidmreed's reply linked [internal-ID-redacted]; that backlog item appears to have shipped. +- pass2 labels: `area:packaging,area:apex,state:resolved` + +--- + +### #3470: Rename `ci_master` to `ci_main` (or alias) + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Method**: code-scan + +**Evidence**: + +```823:835:cumulusci/cumulusci.yml + ci_master: + group: Continuous Integration + description: Deploy the package metadata to the packaging org and prepare for managed package version upload. Intended for use against main branch commits. + steps: + ... +``` + +`rg "ci_main"` returns no matches. davidmreed's 2022 reply indicated this requires flow-aliasing infrastructure first. + +**Recommended action**: + +- pass1: `closed:stale-24mo` - 4yr stale; preserve as `closed:stale-24mo` rather than dismiss; the inclusive-language motivation is real and could be revisited if flow aliasing lands. +- pass2 labels: `enhancement,stale,inclusive-language` + +### #3471: `Merged 0 commits into branch:` message displays when a non-Source Code change is + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug +**Method**: Bucket A - code-scan and `git log` against `cumulusci/tasks/github/merge.py`. + +**Evidence**: + +The reported log message originates from `_merge`: + +```241:262:cumulusci/tasks/github/merge.py + def _merge(self, branch_name, source, commit): + """Attempt to merge a commit from source to branch with branch_name""" + compare = self.repo.compare_commits(branch_name, commit) + if not compare or not compare.files: + self.logger.info(f"Skipping branch {branch_name}: no file diffs found") + return + + try: + self.repo.merge(branch_name, commit) + self.logger.info( + f"Merged {compare.behind_by} commits into branch: {branch_name}" + ) + except GitHubError as e: + if e.code != http.client.CONFLICT: + raise + + if self.options["create_pull_request_on_conflict"]: + self._create_conflict_pull_request(branch_name, source) + else: + self.logger.info( + f"Merge conflict on branch {branch_name}: skipping pull request creation" + ) +``` + +The log line at 251 reports `compare.behind_by` from github3's CompareCommits. `behind_by` is computed from the GitHub compare-commits endpoint and reflects how many commits the destination branch is behind the merged commit _as of the comparison's chosen merge-base_; for "effectively no-op" content merges (e.g. README/test.txt scenarios where downstream content already matches via merge-base), the API can return 0 even though `self.repo.merge(branch_name, commit)` at line 249 just shipped a real commit. The reporter's pattern (README and `test.txt` reproduce; cumulusci.yml / source-code changes do not) is consistent with this hypothesis. + +`git log --all --oneline -- cumulusci/tasks/github/merge.py` since 2023-01-01 shows only refactors (ruff migration, internal repo migration, the `create_pull_request_on_conflict` option, `skip_future_releases` fixes); the `compare.behind_by` reporting line has not changed since the original `MergeBranch` implementation. Existing `test_merge.py` only exercises the happy path where `behind_by == 1`; no test covers the misleading 0-case. + +**Recommended action**: + +- pass1: `keep-open` - small, well-localized fix (replace `compare.behind_by` with `len(list(compare.commits))` or report the SHA returned from `self.repo.merge(...)`); add a test covering the `behind_by=0` case. +- pass2 labels: `bug, github, merge, low-priority` + +--- + +### #3479: Error with "cci org import" in github action + +**Verdict**: NOT-REPRODUCED-on-v4.10.0 +**Repro type**: bug +**Method**: Bucket A - code-scan of `cumulusci/cli/org.py` `org_import`, `cumulusci/core/config/sfdx_org_config.py` `sfdx_info`, and `git log` for the relevant wrapping fix. + +**Evidence**: + +The reported error is the bare `Expecting value: line 1 column 1 (char 0)` (raw `json.JSONDecodeError`). The actual error path in v4.10.0: + +`cumulusci/cli/org.py`: + +```238:255:cumulusci/cli/org.py +@org.command(name="import", help="Import an org from Salesforce DX") +@click.argument("username_or_alias") +@orgname_option_or_argument(required=True) +@pass_runtime(require_keychain=True) +def org_import(runtime: CliRuntime, username_or_alias: str, org_name: str): + # Import the org from the SFDX keychain as an SfdxOrgConfig + # The `sfdx` key ensures we can reload using the right class. + org_config = SfdxOrgConfig( + {"username": username_or_alias, "sfdx": True}, + org_name, + runtime.keychain, + global_org=False, + ) + + # Determine if we received a locally-created scratch org + # or some other org (which we'll treat as persistent) + + info = org_config.sfdx_info +``` + +`cumulusci/core/config/sfdx_org_config.py`: + +```38:55:cumulusci/core/config/sfdx_org_config.py + if p.returncode: + self.logger.error(f"Return code: {p.returncode}") + for line in stderr_list: + self.logger.error(line) + for line in stdout_list: + self.logger.error(line) + message = f"\nstderr:\n{nl.join(stderr_list)}" + message += f"\nstdout:\n{nl.join(stdout_list)}" + raise SfdxOrgException(message) + + else: + try: + org_info = json.loads("".join(stdout_list)) + except Exception as e: + raise SfdxOrgException( + "Failed to parse json from output.\n " + f"Exception: {e.__class__.__name__}\n Output: {''.join(stdout_list)}" + ) +``` + +Both nonzero return codes and JSON parse failures from `sfdx org display --json` are wrapped in `SfdxOrgException` with explicit context (this wrapping landed in commit `017bc49f4` on 2020-11-24, well before the 2023-01-06 issue). The bare `Expecting value: line 1 column 1 (char 0)` symptom is therefore not what a v4.10.0 user would see for this scenario - they would get the `Failed to parse json from output. Exception: JSONDecodeError. Output: ...` message instead, which directly reveals the empty/garbled sfdx output and points to the upstream auth/shell problem. + +David Reed's only reply (2023-02-22, never answered by the reporter) correctly identifies the root cause as GHA shell interpolation: `echo ${{ secrets.DEV_AUTH_URL }} > sfdx_auth` without quotes mangles multiline secrets, so `sfdx force:auth:sfdxurl:store` either fails silently or produces an invalid auth - then `sfdx org display --json` returns empty/non-JSON. Fix is in the user's workflow, not in cci. + +**Recommended action**: + +- pass1: `closed:not-reproducible-on-v4.10.0` - root cause is user GHA workflow (unquoted multiline secret); cci's only relevant code path (`sfdx_info`) already wraps the error with a user-actionable message in v4.10.0; reporter never responded for 3+ years. +- pass2 labels: `awaiting-more-details, external-config` + +--- + +### #3485: "cci task run run_tests" generates incorrect test_results.xml format + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug +**Method**: code-scan + +**Evidence**: + +- `cumulusci/tasks/apex/testrunner.py:803-834` - `_write_output` opens `junit_output` and writes `'\n'` with no `` declaration and no enclosing `` element. +- The closing tag at line 834 is ``. This exactly matches the malformed XML the reporter showed. +- `junit_output` defaults to `test_results.xml` (line 201-203), unchanged. + +**Recommended action**: + +- pass1: `keep-open` - small mechanical fix, still affects users producing JUnit reports for CI. +- pass2 labels: `bug, area:apex, good-first-issue` + +### #3492: Enhance the "-o" option of "cci flow run" to accept "project\_\_custom" attribute values + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Method**: code-scan + +**Evidence**: + +- `cumulusci/cli/flow.py:152-162` - parses `-o` pairs by splitting key on `"__"` and unpacking into exactly two parts (`task_name, option_name = key.split("__")`). +- A user passing `-o project__custom__myattr value` would actually error with "too many values to unpack" because the split yields three elements; even worded as `-o project__custom value` there is no codepath that writes into `project_config.config["project"]["custom"]`. +- `coordinator = runtime.get_flow(flow_name, options=options)` (line 166) receives a `{task_name: {option_name: value}}` dict; project-level overrides have no entry point here. + +**Recommended action**: + +- pass1: `keep-open` - legitimate usability gap for matrix-style CI. +- pass2 labels: `enhancement, area:cli` + +### #3506: when clause support for flow steps which call other flows + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature (silent ignore = bug-shaped) +**Method**: code-scan + +**Evidence**: + +- `cumulusci/core/flowrunner.py:660-672` - when the step has a `task:` key, the StepSpec is built with `when=step_config.get("when")`. +- `cumulusci/core/flowrunner.py:674-697` - the `flow:` branch recurses via `_visit_step(...)` passing only `parent_options`, `parent_ui_options`, and `from_flow`; it never reads or propagates `step_config.get("when")`. Any `when:` clause attached to a flow-call step is silently dropped. + +**Recommended action**: + +- pass1: `keep-open` - confirmed silent-failure foot-gun the user reported. +- pass2 labels: `enhancement, area:flows` + +### #3518: Task add_picklist_entries always sets a default value for record types + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug + +**Method**: +Read `cumulusci/tasks/metadata_etl/picklists.py`. The smoking gun is at +line 177: `default = str(process_bool_arg(entry.get("default", False))).lower` + +- `.lower` is referenced as an attribute, not invoked. The resulting bound + method is truthy, so the `if default:` guard at line 214 always runs the + default-clobbering loop, marking the new entry as default for every record + type. Wrote two tests: a unit-level repro of the truthy bound-method, and a + function-level repro driving `_add_single_record_type_entries` directly via + `MetadataElement` to observe the mutated XML. + +**Evidence**: + +- `cumulusci/tasks/metadata_etl/picklists.py:177` missing `()` after `.lower`. +- `cumulusci/tasks/metadata_etl/picklists.py:214-221` unconditionally sets + defaults whenever `default` is truthy. +- Test output: `test_issue_3518_picklist_record_type_default_logic_bug` + asserts the value is callable (it is) - fails as expected. +- Test output: `test_issue_3518_record_type_default_not_set_when_default_false` + observed `true` on a value that was passed + `default: False`. + +**Recommended action**: + +- pass1: keep-open - small targeted fix. +- pass2 labels: `severity:high,area:metadata-etl,type:bug` + +--- + +### #3542: 2GP flows fail locally with "Could not find package version id" + +**Verdict**: INCONCLUSIVE-needs-2GP-CI-pipeline +**Repro type**: bug +**Org used**: none (cannot easily fabricate a github status posted under a different SHA) + +**Method**: +Read `cumulusci/tasks/github/commit_status.py` `GetPackageDataFromCommitStatus._run_task` and `cumulusci/core/github.py:get_version_id_from_commit`. The lookup uses `self.project_config.repo_commit` (the local checkout's git HEAD SHA) verbatim, then iterates `commit.status().statuses` for one matching the configured context. No fallback to parent commits or to `pull_request.head.sha`. If the upstream workflow posted the status under a different SHA - most commonly the synthetic merge SHA used by `actions/checkout` on `pull_request` triggers - the local lookup returns no version_id and the user sees the exact error from the issue. + +git log shows several "github_package_data" commits since 2022 (last directly related: `33bb24197 Update default API version to v59.0`; `7686731b2 Use commit_status resolution strategy when building non-SkipValidation 2GPs`) but none change the SHA-resolution semantics. The cci-side code path is **unchanged** at v4.10.0. + +Cannot fully reproduce in this run because that requires (a) a private repo configured to use a 2GP feature build workflow that records the version_id, (b) a PR build that has actually completed, (c) local checkout of that PR. Out-of-scope for a fresh scratch org repro. + +**Evidence**: + +- `cumulusci/tasks/github/commit_status.py:20` - `commit_sha = self.project_config.repo_commit` +- `cumulusci/core/github.py:361-368` - `get_version_id_from_commit` only checks the exact SHA's statuses; no fallback. + +**Recommended action**: + +- pass1: `unchanged` - needs the reporter (or someone with a 2GP CI pipeline) to confirm whether their workflow posts under merge-commit SHA vs head-commit SHA. If the latter, this is a cci bug; if the former, it's a workflow alignment bug in `cumulus-actions`. +- pass2 labels: `needs-repro`, `2gp` + +--- + +### #3543: New Option `load_sfdx_project_paths` for dx_convert_from Task + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature + +**Method**: +Read `cumulusci/tasks/dx_convert_from.py` end to end (27 lines). Greped for +`load_sfdx_project_paths|resolve_sfdx_package_dirs|sfdx_project` across the +codebase. + +**Evidence**: + +- `cumulusci/tasks/dx_convert_from.py:7-14` exposes only `extra` and + `src_dir`; no `load_sfdx_project_paths` / `resolve_sfdx_package_dirs`. +- The grep hits in `project_config.py` and `cli/project.py` are unrelated + (general sfdx-project.json reading); they aren't wired into + `DxConvertFrom`. + +**Recommended action**: + +- pass1: keep-open - feature still unimplemented; reporter offered a draft PR. +- pass2 labels: `severity:low,area:metadata-etl,type:enhancement` + +--- + +### #3544: `update_admin_profile` errors when org has Person Accounts AND a namespace + +**Verdict:** `INCONCLUSIVE-needs-namespaced-project` + +### Original symptom + +When `cci flow run dev_org --org dev` is run against a scratch org that has both: + +1. `PersonAccounts` in `features` array of the scratch_def +2. `namespaced: true` set under `orgs/scratch/` in `cumulusci.yml` + +…then the `update_admin_profile` step of `config_dev` fails. The reporter cited stackoverflow Q 206310 noting "Entity of type 'RecordType' named 'Account.Business_Account' cannot be found", indicating the deployed Profile XML references a record type that has been renamed or removed by SFDX/MetadataAPI behavior in PersonAccounts orgs. + +Internal tracking: davidmreed filed [internal-ID-redacted] (2023-02-22) but stated "I can't make any promises about delivering a behavior change." + +### Provisioning attempted + +```text +$ uv run cci org info [scratch-org] +config_file: orgs/person_accounts.json +config_name: person_accounts +namespaced: False +features: Communities, PersonAccounts, ContactsToMultipleAccounts +instance_url: https://drive-inspiration-9525.scratch.my.salesforce.com +``` + +CumulusCI's own `cumulusci.yml` declares no `project__package__namespace`. Setting `namespaced: true` on the scratch config has no effect without a registered namespace, and registering a CumulusCI namespace in the configured DevHub is out of scope for this triage pass. + +### Partial repro on v4.10.0 + +```text +$ uv run cci task run update_admin_profile --org [scratch-org] +Beginning task: ProfileGrantAllAccess +Extracting existing metadata... +[Done] +Loading transformed metadata... +Beginning task: Deploy +[InProgress]: Processing Type: Profile +[Done] +[Success]: Succeeded +``` + +The task **succeeded** against a non-namespaced PersonAccounts scratch org - confirming that the bug requires the _intersection_ of PersonAccounts + namespacing, not PersonAccounts alone. + +### Code search for fixes + +- `git log --since=2023-02-01 -- cumulusci/tasks/salesforce/update_profile.py cumulusci/files/admin_profile.xml` shows only one commit (entrypoints refactor 23295c0a2) - no functional change to PersonAccounts handling. +- `git log --grep "person.account|business_account|3544|[internal-ID-redacted]" -i` returns nothing relevant in `update_profile.py`. +- `update_profile.py` retains a generic `person_account_default` option (line 242, 277) for explicit recordType configuration but no automatic detection or filtering of `Account.Business_Account` for PersonAccounts orgs. + +### Adjacent finding (not the same bug) + +`cumulusci/utils/__init__.py:229`: + +```229:229:cumulusci/utils/__init__.py + namespaced_org = namespace + "__" if namespaced_org else "" +``` + +Passing `-o namespaced_org True` against a project with `namespace=None` raises `TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'`. Distinct from #3544's bug (which is a deploy-time RecordType-not-found error, not an init-time TypeError). Worth filing as a separate cleanup issue. + +### Recommendation + +- **Pass 1:** needs-info - ask reporter to validate against v4.10.0 with their namespaced project and confirm whether the issue persists. Include `wi-created` already on the issue . +- **Pass 2 label:** `needs-namespaced-project` - repro requires a namespaced project that this triage pipeline cannot synthesize. +- Alternative: consider treating as `wontfix` per davidmreed's recommendation (use `flows.config_dev.steps.2: task: None` workaround) until SFDC fixes the underlying SFDX/MetadataAPI Account.Business_Account renaming issue. + +### #3549: Deploy to Salesforce does not create a test output + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Method**: code-scan + +**Evidence**: + +- `cumulusci/tasks/salesforce/Deploy.py:49-94` - exposes `test_level` and `specified_tests` options and validates them. +- `cumulusci/tasks/salesforce/Deploy.py:150-154` - passes them through to the metadata API call but never captures `runTestResult`/`runTestsResult` from the response. +- `rg "junit_output|test_results"` against `cumulusci/tasks/salesforce/Deploy.py` and `cumulusci/salesforce_api/metadata.py` returns no test-output writer for the deploy path. + +**Recommended action**: + +- pass1: `keep-open` - natural feature; tracks #3564. +- pass2 labels: `enhancement, area:metadata-deploy` + +### #3561: Retrieve_unpackaged unusable in MetaDeploy + +**Verdict**: NOT-REPRODUCED-on-v4.10.0 (fix landed) +**Repro type**: bug +**Org used**: none + +**Method**: +Read `cumulusci/tasks/salesforce/RetrieveUnpackaged.py`. Current code: + +```26:36:cumulusci/tasks/salesforce/RetrieveUnpackaged.py + def _init_options(self, kwargs): + super(RetrieveUnpackaged, self)._init_options(kwargs) + + if "package_xml" in self.options: + with open(self.options["package_xml"], "r") as f: + self.options["package_xml_content"] = f.read() + + def _get_api(self): + return self.api_class( + self, self.options["package_xml_content"], self.options.get("api_version") + ) +``` + +`git log -S "package_xml_content" -- cumulusci/tasks/salesforce/RetrieveUnpackaged.py` -> commit `56e10665e` "Fix retrieve unpackaged so it is usable in metadeploy (#3566)" merged 2024-05-20 by yippie (the original issue reporter). The fix introduces `package_xml_content` as a separate option so the path-typed `package_xml` is preserved across multiple `_init_options` invocations. + +**Recommended action**: + +- pass1: `close-as-fixed` with link to PR #3566. +- pass2 labels: (none) + +**Notes**: yippie shipped their own fix; the issue was just never closed. + +--- + +### #3570: Feature Request: Flow "finally" or "error" path + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Method**: code-scan + +**Evidence**: + +- `cumulusci/core/flowrunner.py` - only `ignore_failure` (mapped to `StepSpec.allow_failure`, line 122/144) and the `finally:` Python clause inside `flow.run()` (line 500) handle failures. There is no flow-step type for `finally:` / `on_error:` / `cleanup:` / `always_run`. `rg "finally|on_error|on_failure|always_run"` confirms. +- `_run_step` (line 503-536) re-raises on `result.exception` if not `allow_failure`, which is the only failure handling. + +**Recommended action**: + +- pass1: `keep-open` - design-level feature, but problem is real (rollback, notify on partial failure). +- pass2 labels: `enhancement, area:flows` + +### #3585: Error Occurs when Using `update_package_xml` on object with `xsi:nil="true"` + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug + +**Method**: +Wrote a test that places a `.object` file containing +`` (with no `xmlns:xsi` +declared) and invokes `PackageXmlGenerator`. The underlying parser raises a +parse error because the `xsi:` prefix is unbound. + +**Evidence**: + +- `cumulusci/tasks/metadata/package.py:115-130` - when a folder has objects + it instantiates the registered parser; for `objects/` the parser uses the + metadata tree which is strict about namespaces. +- Test output: `test_issue_3585_xsi_nil_true_breaks_update_package_xml` fails + with the exact "not well-formed (invalid token)" class of error reported + by the user. + +**Recommended action**: + +- pass1: keep-open - needs either a namespace-shim before parsing or + pre-stripping of `xsi:nil` attributes. +- pass2 labels: `severity:medium,area:metadata-etl,type:bug,sfdx-compat` + +--- + +### #3587: Warning when install_class/uninstall_class set with managed=false on update_package_xml + +**Verdict**: NOT-REPRODUCED-on-v4.10.0 (feature still unimplemented) +**Repro type**: feature +**Org used**: none (update_package_xml is a local task) + +**Method**: +Inspected `cumulusci/tasks/metadata/package.py`: + +- `PackageXmlGenerator.render_xml()` lines 142-152: emits `/` only when `self.managed and self.install_class` / `self.managed and self.uninstall_class`. No warning if managed is False. +- `UpdatePackageXml._init_options` line 587-588: only normalizes `managed` to bool. No validation/warning. +- `UpdatePackageXml._init_task` line 590-610: passes options through without checking the install_class+managed=False combination. + +Confirmed live by running: + +```bash +uv run cci task run update_package_xml --install_class MyInstall --uninstall_class MyUninstall +``` + +Output (full): `Beginning task: UpdatePackageXml; Generating src/package.xml from metadata in src` - **no warning**. (src/package.xml ended up empty because src/ has no metadata in the cci repo, but the relevant signal is the absence of any warning about install_class being silently dropped.) + +Also confirmed via direct generator test (`_(repro evidence; see narrative)_`): + +- `managed=False, install_class="X"` → output XML has neither `` nor ``. +- `managed=True, install_class="X"` → output XML has them. + +**Evidence**: + +- `cumulusci/tasks/metadata/package.py:142-152` +- `cumulusci/tasks/metadata/package.py:578-610` +- Live cci output: no warning when invoked with --install_class but no --managed. + +**Recommended action**: + +- pass1: `keep-open` - feature still missing. +- pass2 labels: `enhancement`, `good-first-issue` + +--- + +### #3593: `dx` task doesn't work for some commands like `project convert source` + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug + +**Method**: +Read `cumulusci/tasks/sfdx.py` end-to-end. Wrote `cumulusci/tests/triage/test_issue_3593.py` that constructs an `SFDXOrgTask` with command `"project convert source -r src -d force-app"` and a `ScratchOrgConfig`, then asserts the resulting command would not append a target-org flag. + +**Evidence**: + +- `cumulusci/tasks/sfdx.py:46-51` - `SFDXOrgTask._get_command` unconditionally appends `" -o {username}"` for any `ScratchOrgConfig`, regardless of whether the underlying sf subcommand accepts a target-org flag. +- Repro test FAILS with the resulting command: `sf project convert source -r src -d force-app -o test@example.com` - the same shape that the issue reporter said sf cli rejects. +- Note: `cumulusci/tasks/dx_convert_from.py` (which backs the OOTB `dx_convert_from` task) was switched to extend `SFDXBaseTask` (no org), so the OOTB task is fine; the bug remains for any user-defined task that uses `SFDXOrgTask` with a no-org sf subcommand. + +**Recommended action**: + +- pass1: `keep-open` - needs an opt-out option (e.g. `pass_org: False` or a `no_org_command` whitelist). Verifying actual sf cli rejection of `-o` for `project convert source` would need an org/sf cli; the CCI side of the bug is unchanged. +- pass2 labels: `severity:medium,area:packaging,area:sfdx,area:dx-task,state:needs-design` + +--- + +### #3600: Allow install_managed to use environment variables + +**Verdict**: NOT-REPRODUCED-on-v4.10.0 (feature still unimplemented) +**Repro type**: feature +**Org used**: `[scratch-org]` (dev) - used to confirm cci accepts the literal `${VAR}` value at runtime. + +**Method**: +Inspected the option-processing path: + +- `cumulusci/core/tasks.py:35` - `PROJECT_CONFIG_RE = re.compile(r"\$project_config.(\w+)")`. This is the only substitution pattern. +- `cumulusci/core/tasks.py:127-157` - `_init_options.process_options` only calls `PROJECT_CONFIG_RE.sub(...)`; no env-var lookup. +- `cumulusci/utils/yaml/safer_loader.py:60` - uses plain `yaml.safe_load(...)` with no custom resolver. +- `cumulusci/tasks/salesforce/install_package_version.py:31-34` - `version` option description does not mention env-var support. + +Confirmed live by: + +```bash +export MY_FAKE_VERSION='1.2.3' +uv run cci task run install_managed --version '${MY_FAKE_VERSION}' \ + --namespace npsp --org [scratch-org] --interactive True +``` + +Interactive prompt printed: `Package to install: npsp ${MY_FAKE_VERSION}` - literal, **not** expanded. + +**Evidence**: + +- `cumulusci/core/tasks.py:35` - only substitution pattern. +- `cumulusci/utils/yaml/safer_loader.py:60` - plain yaml load. +- Test: `_(repro evidence; see narrative)_` passes. +- Live interactive output: `Package to install: npsp ${MY_FAKE_VERSION}`. + +**Recommended action**: + +- pass1: `keep-open` - feature still missing. Note design decision required: `$env:VAR` (consistent with `$project_config.X`) vs `${VAR}` (POSIX) vs `os.path.expandvars` semantics; backwards-compat for any literal `$`-strings. +- pass2 labels: `enhancement` + +--- + +### #3603: Any issue with git results in the unhelpful "404 not found" error + +**Bucket**: A. **Type**: bug. **Verdict**: REPRODUCED-on-v4.10.0 (partial). + +The user enumerated five situations (1) source repo missing, (2) dep repo +missing, (3) source ref/tag/branch missing, (4) dep resolution strategy fails, +(5) source resolution strategy fails - all collapsing into a generic 404 with +no source/URL/ref context. Code-scan + targeted unit test confirm: + +- Cases **1, 2** are already wrapped: both `cumulusci/core/dependencies/github.py::get_repo` + and `cumulusci/core/source/github.py::GitHubSource.__init__` catch `NotFoundError` + and raise `DependencyResolutionError("We are unable to find the repository at {url}...")`. + Fixed long ago by commit `738d4a8a4` (2021). +- Case **5** for the `release:` source spec is wrapped at + `source/github.py:103` ("Could not find release {self.spec.release}."). +- Case **3** is **NOT** wrapped. `source/github.py:126` + `self.commit = self.repo.ref(ref).object.sha` lets a raw `NotFoundError` + bubble out. The repro test (`test_case3_source_ref_not_found_message_quality`) + prints the actual exception: + +``` +Exception type: NotFoundError +Message: '404 [No message]' +``` + +Neither the repo URL nor the missing ref/tag is present. + +- Case **4** raises `DependencyResolutionError(f"Unable to resolve dependency {dependency}")` + (resolvers.py:663). The dependency description is included, but the list of + attempted strategies is not - so the user can't immediately tell which + strategy fell through. + +Recommendation: keep-open. Two clean, scoped fixes available (wrap `repo.ref()` +in `source/github.py`; enrich the resolvers.py:663 message with strategy +names). Good-first-issue territory. + +Repro: `_(repro evidence; see narrative)_` (4 tests; all pass). + +--- + +### #3604: Task request: Update sfdx-project.json dependencies based off of computed cumulusci dependencies + +**Bucket**: A. **Type**: feature. **Verdict**: REPRODUCED-on-v4.10.0 (gap still +present). + +`uv run cci task list` returns 0 tasks that write `sfdx-project.json`. A +project-wide grep for `unpackagedMetadata` returns no matches. Maintainer +acknowledged the request as [internal-ID-redacted] in a 2023 reply, label `wi-created` +already on the issue, but no implementation has shipped through v4.10.0. + +Recommendation: keep-open (`enhancement`, `wi-created` already attached). + +Repro: code-scan only; no test file (feature gap, nothing to assert against). + +--- + +### #3605: Ability to Increment Major Versions when running `upload_production` + +**Verdict**: NOT-REPRODUCED-on-v4.10.0 +**Repro type**: feature + +**Method**: +Inspected `cumulusci/tasks/salesforce/package_upload.py`. Wrote `_(test pending harvest)_` to assert `major_version` and `minor_version` task options exist and the major-bump branch is wired in `_validate_versions`. + +**Evidence**: + +- `cumulusci/tasks/salesforce/package_upload.py:39-46` - `major_version` and `minor_version` are documented `task_options`. +- `cumulusci/tasks/salesforce/package_upload.py:101-140` - `_validate_versions` honors a major-version bump, defaulting `minor_version` to `"0"` when the user supplies a higher major. +- Repro test passes (3/3). +- Closing PR identified by `git log` history: commit `87b94440e` - "Deploy Major and Minor Version option in upload_production task (#3651)". + +**Recommended action**: + +- pass1: `closed:fixed-by-pr-#3651` - feature shipped; user can run `cci task run upload_production -o major_version 34 -o minor_version 0`. +- pass2 labels: `area:packaging,area:1gp,state:resolved` + +--- + +### #3607: The `retry_failures` from the task `run_tests` is not working for me + +**Verdict**: INCONCLUSIVE-needs-org-with-managed-package +**Repro type**: bug +**Method**: code-scan + unit test + +**Evidence**: + +- `cumulusci/tasks/apex/testrunner.py:209-222` - `retry_failures` strings are compiled into regexes at task init. +- `cumulusci/tasks/apex/testrunner.py:400-408` - `_is_retriable_failure` checks both `Message` and `StackTrace` via `re.search`. +- `cumulusci/tasks/apex/testrunner.py:475-490` - increments `counts["Retriable"]` for each matching failure. +- `cumulusci/tasks/apex/testrunner.py:548` - printed as `Retried: {Retriable}`. +- Repro test `_(repro evidence; see narrative)_` confirms in pure Python that `re.compile("UNABLE_TO_LOCK_ROW").search(user_message)` returns a match for the exact message body the user pasted. Both tests pass. +- One escape hatch in code: `cumulusci/tasks/apex/testrunner.py:448-452` - for class-level errors with `managed: true` (which the user has), retries are explicitly skipped. The user's failure shows per-test details, so this should not have been the cause. + +**Recommended action**: + +- pass1: `closed:stale-24mo` - code logic is correct as written; reporter has not engaged in 30+ months; cannot reproduce without their managed package + org. +- pass2 labels: `bug, area:apex, needs-info` + +### #3609: Command 'cci task run dx --command "plugins:install ..."' fails + +**Verdict**: INCONCLUSIVE-needs-live-cli-test +**Repro type**: bug +**Method**: code-scan + +**Evidence**: + +- `cumulusci/tasks/sfdx.py:20` - `SFDX_CLI = "sf"` (changed from `sfdx` in the v4.x cutover; reporter was on 3.76.0). +- `cumulusci/tasks/sfdx.py:34-40` - `_get_command` is a thin wrapper: `f"sf {self.options['command']}"`. The CCI layer adds nothing that could introduce the "Timed out after 30000 ms" error the user saw. +- `cumulusci/cumulusci.yml:273-275` - `dx` task is registered as `cumulusci.tasks.sfdx.SFDXOrgTask` with description "Execute an arbitrary Salesforce DX command". +- Reporter's literal command `--command "plugins:install ..."` uses the colon syntax that `sfdx` accepted; `sf` typically wants `plugins install`. Different CLI now. + +**Recommended action**: + +- pass1: `closed:stale-24mo` - not a CCI bug; CCI faithfully shells out. CLI in question has changed substantially since. +- pass2 labels: `bug, upstream:sf-cli` + +### #3612: Maintain the CumulusCI for VSCode Extension + +**Verdict**: NOT-REPRODUCED-on-v4.10.0 +**Repro type**: feature (wrong repo) +**Method**: code-scan + +**Evidence**: + +- Issue body explicitly references `https://github.com/SFDO-Tooling/cci-vscode`. That is a separate repository; nothing in this CumulusCI tree is responsible for it. + +**Recommended action**: + +- pass1: `closed:not-reproducible-on-v4.10.0` - should be re-filed against `SFDO-Tooling/cci-vscode`. +- pass2 labels: `enhancement, wontfix, wrong-repo` + +### #3613: AddFieldsToPageLayout - "Cannot find metadata file" + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug (UX/error-message) +**Org used**: `[scratch-org]` + +**Method**: +Live repro: `uv run cci task run add_page_layout_fields --org [scratch-org] -o api_names "Account"` against the scratch org. Saved at `_(repro evidence)_`. Result: `Error: Cannot find metadata file /var/.../retrieve/layouts/Account.layout`. Same task with `-o api_names "Account-Account Layout"` succeeds end-to-end (deploy succeeds). The functionality is intact; the user-visible bug is the unhelpful error when the api_name does not match the Metadata API's `-` file naming convention. + +The error originates from `MetadataSingleEntityTransformTask._transform` (base.py:332): `if not path.exists(): raise CumulusCIException(f"Cannot find metadata file {path}")`. The retrieve actually succeeded (the user's report says "metadata is getting downloaded"), but `_transform` looks for the user's typed api_names verbatim as filenames. + +**Recommended action**: + +- pass1: `improve-error-message` - keep open as a UX bug. +- pass2 labels: `bug`, `good-first-issue` + +**Notes**: Two complementary improvements would help: + +1. In `_transform` (base.py:332), include the actual list of files retrieved into `source_metadata_dir` in the error message so the user can spot the naming mismatch. +2. In `AddFieldsToPageLayout._init_options`, warn when an api_name does not contain `-` (Layout API names always do). + +The user was on Windows in 2023 - note that the user might also have been hitting a backslash path issue, but the underlying class of bug is the same: api_name format mismatch. + +--- + +### #3615: update_dependencies does not honor resolution strategy + +**Bucket**: A. **Type**: bug (filed). **Verdict**: NOT-REPRODUCED-on-v4.10.0. + +The user expected `cci task run update_dependencies --resolution_strategy preproduction` +to install a beta of an Unlocked dependency. In `cumulusci/cumulusci.yml`: + +```yaml +dependency_resolutions: + preproduction: latest_release + production: latest_release + resolution_strategies: + latest_release: [tag, latest_release, unmanaged] # no latest_beta + include_beta: [tag, latest_beta, latest_release, unmanaged] +``` + +So `preproduction` is an alias for the `latest_release` stack and intentionally +omits `latest_beta`. To install the beta unlocked package, the correct +invocation is `--resolution_strategy include_beta`. The repro test confirms +all three properties: `preproduction` lacks `latest_beta`, `include_beta` has +it, and `preproduction == production`. Working as documented. + +Recommendation: closed:not-reproducible-on-v4.10.0. Possible follow-up: docs/UX +improvement (the name `preproduction` is misleading; consider clarifying in +docs/data.md or renaming to `release_only` in a future major). + +Repro: `_(repro evidence; see narrative)_` (3 tests; all +pass). + +--- + +### #3618: Allow for list when deleting/removing CumulusCI orgs + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Method**: code-scan + +**Evidence**: + +- `cumulusci/cli/org.py:519-545` - `org_remove` decorated with `@orgname_option_or_argument(required=True)`, takes a single `org_name`. +- `cumulusci/cli/org.py:605-625` - `org_scratch_delete` same pattern, single `org_name`. +- No `nargs=-1`, no comma-split helper; passing `org1,org2` would be treated as a single literal alias and fail keychain lookup. + +**Recommended action**: + +- pass1: `keep-open` - legitimately useful for cleanup workflows; small implementation surface. +- pass2 labels: `enhancement, area:cli, good-first-issue` + +### #3619: Dependency_pins does not honor passwords + +**Bucket**: A. **Type**: bug. **Verdict**: REPRODUCED-on-v4.10.0. + +Two distinct reproducible defects in `cumulusci/core/dependencies/dependencies.py`: + +1. **Parsing error (Part A)**: `GitHubDependencyPin` (L81-101) declares only + `github: str` and `tag: str`. Adding `password_env_name:` to a + `dependency_pins:` entry triggers + `DependencyParseError: Unable to parse dependency pin: {...}` from + `parse_dependency_pin()`. + +2. **Silent password drop (Part B)**: When a dynamic dependency carries a + `password_env_name`, the pin path at L171-187 short-circuits to + `pin.pin(self, context)`, which (L100) calls + `GitHubTagResolver().resolve(d, context)` directly - bypassing + `resolve_dependency()`'s password-propagation block (resolvers.py L644-654). + The resulting `package_dependency.password_env_name` is `None`, so + `PackageNamespaceVersionDependency.install()`'s + `os.environ.get(self.password_env_name)` runs against `None` and the + install-key is never sent. + +The contrast test confirms the non-pinned path _does_ propagate the password, +isolating the defect to `pin.pin()`. + +Recommendation: keep-open. Two-line fix sketch: + +- Add `password_env_name: Optional[str] = None` to `GitHubDependencyPin`. +- In `pin.pin()`, after `d.ref, d.package_dependency = ...`, mirror the + block from resolvers.py L644-654 to copy + `d.password_env_name` (or the pin's own) onto `d.package_dependency`. + +Repro: `_(repro evidence; see narrative)_` (4 tests; all pass). + +--- + +### #3649: Support serial loads with update_data task + +**Verdict:** `REPRODUCED-on-v4.10.0` - feature still unimplemented + +`UpdateData.load_data` and the rollback path in `cumulusci/tasks/bulkdata/update_data.py:184` and `:211` both call `get_dml_operation(..., api_options={}, ...)` with the `api_options` dict hardcoded empty. `BulkApiDmlOperation` in `step.py` honors `api_options["bulk_mode"]` for Serial/Parallel selection, but `UpdateData` never sets it. `LoadData` and the snowfakery channel runner DO let users pick `bulk_mode`; `update_data` is the gap. + +The author offered to implement. Fix is small (~10 lines): add `bulk_mode` (or `api_options`) to `UpdateData.task_options` and pipe it into both `get_dml_operation` calls. + +**Recommended pass1: `keep-open`** with `good-first-issue` label. + +### #3663: When clause | Ability to pass in prior task response values + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Method**: code-scan + +**Evidence**: + +- `cumulusci/core/flowrunner.py:510-516` - the `when` Jinja context is hardcoded to `{"project_config": ..., "org_config": ...}`. Prior step results (`self.results`) are not exposed. +- The `^^task.return_value` resolver lives elsewhere (option resolution path) and is not threaded into the `when` evaluator. + +**Recommended action**: + +- pass1: `keep-open` - natural extension of `when`; complements #3506. +- pass2 labels: `enhancement, area:flows` + +### #3692: No parser configuration found for subdirectory digitalExperiences + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug + +**Method**: +Greped `digitalExperiences|digitalExperience` across the codebase - zero +hits. Wrote two tests: a static one asserting `digitalExperiences` is a key +in `metadata_map.yml`, and a runtime one that creates the folder structure +and runs `PackageXmlGenerator`. + +**Evidence**: + +- `cumulusci/tasks/metadata/metadata_map.yml` has no `digitalExperiences` + key. +- `cumulusci/tasks/metadata/package.py:115-118` raises + `MetadataParserMissingError("No parser configuration found for +subdirectory %s")`. +- Test output: `test_issue_3692_digital_experiences_in_metadata_map` fails + on the missing key; runtime test reproduces the exact error message from + the user report. + +**Recommended action**: + +- pass1: keep-open - add `digitalExperiences` (and likely + `digitalExperienceConfigs`) entries to `metadata_map.yml` with + appropriate parser classes (probably a bundle parser). +- pass2 labels: `severity:medium,area:metadata-etl,type:bug` + +--- + +### #3699: Sort of the data during extraction + +**Verdict:** `REPRODUCED-on-v4.10.0` - feature missing, but workaround exists + +`ExtractData._soql_for_mapping` (extract.py:133-147) builds the SOQL with `WHERE` only - no `ORDER BY`. `MappingStep` has no `order_by`/`sort` field. However, `append_filter_clause` strips a leading `WHERE` from `soql_filter` and concatenates the remainder onto the query, which means a user can write `soql_filter: "Active__c = true ORDER BY CreatedDate"` and it produces valid SOQL. So the missing capability is "first-class `order_by` field for parity with `where`", not "ability to sort at all". + +The author hasn't followed up since 2023-11; with a working workaround, this is low-priority. + +**Recommended pass1: `closed:stale-24mo`.** Reopen later if a v5 effort wants explicit `order_by` for declaration ergonomics. + +### #3700: Trying to do an upsert on a master-detail child object gets an error around permission + +**Verdict:** `REPRODUCED-on-v4.10.0` - bug + +`MappingStep._get_required_permission_types(operation)` in `mapping_parser.py:373-377` returns `("updateable", "createable")` for any operation in `(UPSERT, ETL_UPSERT)` (or any mapping action of the same). Master-detail lookup fields in Salesforce are `createable: True` but `updateable: False` (you cannot reparent a master-detail child after creation). `_check_field_permission` therefore returns `False` for the MD lookup field on an upsert mapping, and `_validate_field_dict` errors out with the exact message the user reported: "Field xxx\_\_c does not have the correct permissions ('updateable', 'createable') for this operation." + +Repro test (`_(repro evidence; see narrative)_`) constructs an `UPSERT` mapping for an `Order__c` with an `Account__c` master-detail lookup, simulates a `{createable: True, updateable: False}` describe, and asserts that `_check_field_permission` returns `False` - assertion passes. + +**Recommended pass1: `keep-open`** with `good-first-issue`. Fix: when validating an MD lookup field for an upsert, accept `createable` alone (the lookup never gets updated post-insert anyway). A field-shape detector can use `relationshipName` + `cascadeDelete: True` from describe. + +### #3701: set a mapping to the id instead of it being either a number or the salesforce id + +**Verdict:** `REPRODUCED-on-v4.10.0` - feature gap + +`MappingStep` and the extract/load pipeline special-case the literal field name `"Id"` in many places (`mapping_parser.py:171/190/228/241/422`); it always represents the Salesforce Id and lands in an `sf_id` column. There is no mechanism to make a different field (an external-id like `BCM_Unique_Id__c`) act as the row's primary key in the extracted SQLite. The user's example yaml `Id : BCM_Unique_Id__c` is currently interpreted as "extract the SF Id into the column named `BCM_Unique_Id__c`", not "make `BCM_Unique_Id__c` the row primary key". + +This is closely tied to #3699 (motivated by sortable git diffs of dataset extracts). The deeper PK-replacement feature would touch many places in extract / load / lookup-resolution. + +**Recommended pass1: `closed:stale-24mo`.** Reporter hasn't followed up since 2023-11. Workarounds are available (extract Id into a known column and post-process). Could revisit if a v5 effort tackles dataset diff-ability holistically. + +### #3717: Github automerge feature task not working when running through Github Flow + +**Verdict**: INCONCLUSIVE-needs-cumulus-actions-workflow +**Repro type**: bug +**Method**: Bucket A - code-scan of `cumulusci/core/config/project_config.py` `repo_info` / `repo_branch` and repo-wide grep for GitHub Actions env-var auto-detection. + +**Evidence**: + +The reporter notes that `project_config.repo_branch` and `project_config.project__git__prefix_feature` are `None` when `ci_feature` / `github_automerge_feature` is triggered by a merge event in a GitHub Actions workflow, but populated when triggered manually via the Actions tab. + +`cumulusci/core/config/project_config.py` shows that cci's only CI auto-detection is for Heroku: + +```220:255:cumulusci/core/config/project_config.py + def repo_info(self) -> Dict[str, Any]: + if self._repo_info is not None: + return self._repo_info + + # Detect if we are running in a CI environment and get repo info + # from env vars for the environment instead of .git files + info = {"ci": None} + + # Make sure that the CUMULUSCI_AUTO_DETECT environment variable is + # set before trying to auto-detect anything from the environment + if not os.environ.get("CUMULUSCI_AUTO_DETECT"): + self._repo_info = info + return self._repo_info + + # Heroku CI + heroku_ci = os.environ.get("HEROKU_TEST_RUN_ID") + if heroku_ci: + info = { + "branch": os.environ.get("HEROKU_TEST_RUN_BRANCH"), + "commit": os.environ.get("HEROKU_TEST_RUN_COMMIT_VERSION"), + "ci": "heroku", + "root": "/app", + } + + # Other CI environment implementations can be implemented here... + + self._apply_repo_env_var_overrides(info) +``` + +A repo-wide grep (`Grep` over `cumulusci/`) for `GITHUB_REF|GITHUB_HEAD_REF|GITHUB_SHA|GITHUB_ACTIONS` returns **zero matches** - there is no GitHub Actions environment auto-detection in cci. The fallback path in `repo_branch` (line 394-402) calls `current_branch(self.repo_root)` which reads `.git/HEAD`. On a `push`-triggered GHA workflow the standard `actions/checkout@v4` checkout puts the working copy in a detached HEAD state, so `current_branch` returns `None`. On `workflow_dispatch` triggered manually, the action receives a `ref` input that resolves to a named branch checkout, so `current_branch` works. + +Net: the reporter's symptom is exactly what cci produces unless the workflow sets `CUMULUSCI_REPO_BRANCH` (and friends) explicitly before invoking `cci flow run`. That responsibility lives in the `cumulus-actions/standard-workflows` YAMLs, not in cci. This matches the precedent established in #3418 (also `INCONCLUSIVE-needs-cumulus-actions-workflow`). + +**Recommended action**: + +- pass1: `unchanged` - issue is at the cci ↔ cumulus-actions boundary; needs investigation in `cumulus-actions/standard-workflows` to confirm/fix `CUMULUSCI_REPO_BRANCH` plumbing for `push`-triggered child-feature merges. +- pass2 labels: `external-config, cumulus-actions, needs-info` + +> Optional follow-up enhancement (not part of triage): add a `# GitHub Actions` block in `repo_info` parallel to the Heroku block, reading `GITHUB_HEAD_REF` (PR) / `GITHUB_REF_NAME` (push) and `GITHUB_SHA`. Would close a class of "branch=None in CI" reports including this one, but is a behavior-change to a long-stable contract and merits its own design discussion. + +### #3721: `create_package_version` `version_name` default should be version number, not "Release" + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature + +**Method**: +Searched for `version_name` defaults in `create_package_version.py` and `package_upload.py`, and for any jinja2 templating in PackageUpload. Verified the muselab-d2x commit that implements the requested behavior is not an ancestor of HEAD. + +**Evidence**: + +- `cumulusci/tasks/create_package_version.py:184` - `version_name=self.options.get("version_name") or "Release"`. Default is still the literal string `"Release"`. +- `cumulusci/cumulusci.yml:684-686` - `upload_production` hard-codes `name: Release`. +- `cumulusci/tasks/salesforce/package_upload.py:147-154` - passes `VersionName` straight through; no jinja2/template support. +- `git merge-base --is-ancestor 7aaf348f3 HEAD` returns non-zero. Commit `7aaf348f3` ("Change version naming on PackageUpload task to use the predicted version number and a jinja2 template expression") lives only on the `d2x/*` remotes (muselab-d2x fork), per jlantz's 2024 comment. + +**Recommended action**: + +- pass1: `keep-open` - needs upstream port + design (templating? plain version number? both 1GP and 2GP?). +- pass2 labels: `severity:low,area:packaging,area:1gp,area:2gp` + +--- + +### #3734: upload_production fails with FIELD_INTEGRITY_EXCEPTION when latest is Beta patch + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug +**Org used**: none (would require a 1GP packaging org with both 6.13 Released and 6.13.1 Beta - too large to fabricate) + +**Method**: +Read `cumulusci/tasks/salesforce/package_upload.py` `PackageUpload._validate_versions`. The "latest version" SOQL query orders `MajorVersion DESC, MinorVersion DESC, PatchVersion DESC, ReleaseState DESC LIMIT 1`. When the user has the pattern they describe (release 6.13, then create patch 6.13.1 Beta as a safety net), the query returns the patch row with `ReleaseState='Beta'`. + +Then at line 134-135: + +```python +if version["ReleaseState"] == "Beta": + self.options["minor_version"] = str(version["MinorVersion"]) +``` + +This sets `minor_version=13`, identical to the already-Released minor. The PackageUploadRequest is then created with major=6, minor=13, which Salesforce rejects with `FIELD_INTEGRITY_EXCEPTION: The version number must be greater than the last Managed - Released version number: 6.13`. Exactly the error in the issue. + +The user's own analysis in their last 3 comments is correct and matches the code. The current `cannot-reproduce`/`awaiting-more-details` labels are stale. + +Confirmed via mocked unit test `_(repro evidence; see narrative)_`: + +- With `{Major=6, Minor=13, Patch=1, ReleaseState=Beta}` → `_validate_versions` sets `minor_version='13'` (the bug). +- With `{Major=6, Minor=13, Patch=None, ReleaseState=Released}` → `_validate_versions` sets `minor_version='14'` (the desired behavior). + +git log shows `87b94440e Deploy Major and Minor Version option in upload_production task (#3651)` (2023-09-19) added the major/minor options but did not change the latest-version query or the Beta-branch logic. v4.10.0 still has this code as-is. + +**Evidence**: + +- `cumulusci/tasks/salesforce/package_upload.py:80-98` - SOQL query, `ORDER BY MajorVersion DESC, MinorVersion DESC, PatchVersion DESC, ReleaseState DESC LIMIT 1`. +- `cumulusci/tasks/salesforce/package_upload.py:134-137` - Beta branch sets `minor_version` to the same minor. +- Test passes on v4.10.0 with mocked `_get_one_record`. + +**Recommended action**: + +- pass1: `keep-open` - confirmed real bug; remove the stale `cannot-reproduce`/`awaiting-more-details` labels. +- pass2 labels: `bug` + +--- + +## Summary cross-cutting findings + +1. **Two feature requests (#3587, #3600)** are easy "good-first-issue" candidates whose semantics are clear from the issue body. Both are independently verifiable with no org needed (`update_package_xml` is local; env-var support is a parser-level concern). +2. **Two real bugs (#3446, #3734)** still reproduce on v4.10.0 with stable, well-understood root causes. #3734 is mislabeled `cannot-reproduce` - that label should be removed in pass2. +3. **Two cross-stack issues (#3418, #3542)** point at the boundary between cci and the `cumulus-actions/standard-workflows` repo. They cannot be triaged from cci alone; they need either a workflow-side check or a request to the reporter for the workflow file/SHAs they're using. + +### #3745: ci_beta and install_managed_beta do not use the latest beta + +**Verdict:** `NOT-REPRODUCED-on-v4.10.0` (working as designed) + +**Evidence:** [`_(repro evidence)_`](_(repro evidence)_) + +The `install_managed_beta` task (cumulusci.yml line 408) sets `version: latest_beta`, which `InstallPackageVersion` (lines 96-100 of `cumulusci/tasks/salesforce/install_package_version.py`) resolves via a GitHub Releases lookup using the `include_beta` resolver strategy - NOT a direct DevHub query for the latest `Package2Version`: + +```96:101:cumulusci/tasks/salesforce/install_package_version.py + if version in ["latest", "latest_beta"]: + strategy = "include_beta" if version == "latest_beta" else "production" + dependency = GitHubDynamicDependency(github=github) + dependency.resolve( + self.project_config, get_resolver_stack(self.project_config, strategy) + ) +``` + +The reporter (kayla-hager, 2024-02-07) ran `create_package_version` standalone - bypassing `release_2gp_beta` - so no GitHub release with the beta tag was ever created, hence `ci_beta` correctly fell back to the latest production tag (1.27). Comments resolve this as a documentation/usability issue; the reporter accepted the explanation 2024-02-13 and indicated they would close. v4.10.0 has not changed this behavior. + +**Recommendation:** keep `closed:stale-24mo` (rule 1). No code defect. + +--- + +### #3746: Deleted Versions used for determining next version + +**Verdict:** `REPRODUCED-on-v4.10.0` (code-level confirmation) + +**Evidence:** [`_(repro evidence)_`](_(repro evidence)_) + +The reporter (yippie, 2024-02-09) flagged that `create_package_version._get_base_version_number()` does not filter `IsDeprecated = true` when picking the highest existing `Package2Version` to increment from. The bug is present verbatim in v4.10.0 source at `cumulusci/tasks/create_package_version.py` lines 535-541: + +```529:545:cumulusci/tasks/create_package_version.py + def _get_base_version_number( + self, version_base: Optional[str], package_id: str + ) -> PackageVersionNumber: + """Determine the "base version" of the package (existing version to be incremented)""" + if version_base is None: + # Default: Get the highest existing version of the package + res = self.tooling.query( + "SELECT MajorVersion, MinorVersion, PatchVersion, BuildNumber, IsReleased " + "FROM Package2Version " + f"WHERE Package2Id='{package_id}' " + "ORDER BY MajorVersion DESC, MinorVersion DESC, PatchVersion DESC, BuildNumber DESC " + "LIMIT 1" + ) + if res["size"]: + return PackageVersionNumber( + **res["records"][0], package_type=PackageType.SECOND_GEN + ) +``` + +The same file at line 297 DOES include `IsDeprecated = FALSE` for `Package2` lookups, so the project knows about the column - the omission at line 535 is asymmetric and matches the report exactly. End-to-end repro on a real packaging org would require creating two Package2Version records and deleting one (sf package version delete), which is outside this triage's scope. Code-level confirmation is sufficient. + +**Recommendation:** flip currently-proposed `closed:stale-24mo` → `kept-open` with `severity:medium`, `area:packaging`, `target:v4-patch`. Trivial 1-line fix (add `AND IsDeprecated = false` to the SOQL WHERE clause). + +--- + +### #3754: Enable configuration for cci version update sources + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature +**Method**: code-scan + +**Evidence**: + +- `cumulusci/cli/utils.py:65-79` - `get_latest_final_version` hits `https://pypi.org/pypi/cumulusci/json` literally, no env-var, no kwarg. +- `cumulusci/cli/utils.py:82-101` - `check_latest_version` cannot be disabled via flag/env. Workaround in the comments (touch `~/.cumulusci/cumulus_timestamp` to a far-future epoch) confirmed by inspecting the timestamp logic at lines 38-50, 86-89. + +**Recommended action**: + +- pass1: `keep-open` - easy add (e.g. `CUMULUSCI_DISABLE_VERSION_CHECK` env), helps offline/restricted environments. +- pass2 labels: `enhancement, area:cli` + +### #3758: Flow `push_upgrade_org` is incorrectly defined + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug + +**Method**: +Read the `push_upgrade_org` flow in `cumulusci/cumulusci.yml`. Wrote `cumulusci/tests/triage/test_issue_3758.py` to load the YAML and assert the final step calls `config_managed` (not `config_qa`). + +**Evidence**: + +- `cumulusci/cumulusci.yml:1161-1177` - final step is `flow: config_qa`. The bug report (correctly, in my view) argues this should be `config_managed` because push upgrades target managed-package orgs (UAT sandboxes), not QA scratch orgs. +- Repro test FAILS with `config_qa` != `config_managed`. +- Both flows currently expand to the same steps (`deploy_post`, `update_admin_profile`, `load_sample_data`), so behavior is equivalent today, but semantics drift over time and the docs link customers to the wrong flow page. + +**Recommended action**: + +- pass1: `keep-open` - single-line YAML fix; great `good-first-issue` candidate. Out of scope for this triage pass per task constraints (do not fix bugs). +- pass2 labels: `severity:medium,area:packaging,area:flows,good-first-issue` + +--- + +### #3762: `update_admin_profile` task fails on namespaced org with Person Accounts enabled + +**Verdict**: closed:duplicate-of-#3544 +**Repro type**: bug +**Org used**: none (dup-confirm only per protocol) + +**Method**: +Read both #3762 and (via gh) #3544. The reporter (noahisapilot) explicitly self-identifies the duplicate in their first comment dated 2024-03-06: _"Apologies, this seems like a duplicate of #3544"_. Both report the same root cause: `update_admin_profile` errors when deploying to a namespaced scratch org with `PersonAccounts` enabled, because the retrieved profile contains record types like `Account.Business_Account` (no namespace) but the task injects the namespace prefix onto recordType references during deploy. + +The reporter's own analysis even pinpoints the offending code at `update_profile.py` line 238 (in v3.84.3; corresponds to `rt["record_type"] = rt["record_type"].format(**self.namespace_prefixes)` at line 236 in v4.10.0). + +#3544 is still OPEN at v4.10.0 with `wi-created` label `[internal-ID-redacted]`. + +**Recommended action**: + +- pass1: `close-as-duplicate` - link to #3544. +- pass2 labels: (n/a - duplicate) + +**Notes**: Per dup-confirm protocol, no live repro performed. + +--- + +### #3768: Snowfakery Batch Size and Just Once + +**Verdict:** `REPRODUCED-on-v4.10.0` - bug, structural + +The Snowfakery channel runner architecturally creates a separate working directory per batch via `shutil.copytree(template_path, data_dir)` (`queue_manager.py:322`). Before that copy, `Snowfakery._cleanup_object_tables` (`snowfakery.py:721-730`) drops every non-`sf_ids` table from the template. So when batch 2+ starts, the SQLite database carried in the template only contains `sf_ids` mapping tables - none of the actual `account`-row data created in the initial just_once batch. + +Snowfakery's `random_reference: Account` resolves at generation time against rows in the recipe-local database. Since `just_once: true` means batch 2+ does not regenerate the Accounts, and the carried-over database has no `account` rows (just the `account_sf_ids` map), `random_reference` has nothing to pick from in batch 2+. This matches the user's exact symptom: first 20 contacts (one batch) get the 5 just_once Accounts, the next 430 get nothing (or fall back to NPSP defaults). + +Verifying interactively against an org would require provisioning a scratch and running the recipe at `--batch_size 20 --num_records 450` - code-review evidence is conclusive without it. **Recommended pass1: `keep-open`**, severity major. Fix likely requires preserving rows of just_once-referenced objects (not just `_sf_ids`) in the template DB carried to subsequent batches; coordination with the Snowfakery dev branch is probably required. + +### #3771: find_replace transforms on XPath with predicates does not work + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug + +**Method**: +Read `cumulusci/core/source_transforms/transforms.py:415-485`. The +`transform_xpath` helper splits the XPath on `/`, wraps each tag in +`*[local-name()=...]`, and re-appends the predicate verbatim. The bug: tag +references INSIDE the predicate (e.g. `price` in `[price>40]`) are still +namespace-bound, so on default-`xmlns` documents they don't match. PR #3772 +(referenced in `closedByPullRequestsReferences`) lives only on the `leboff` +fork and was never merged into `main`. + +**Evidence**: + +- `cumulusci/core/source_transforms/transforms.py:420-435` - naive + predicate handling. +- `git log --all --oneline --grep="3772\|3771\|XPath.*predicate"` shows + only `2bf6ce6a3 Improve namespace handling in find_replace` on + `remotes/leboff/feature/improve-find-replace-ns-handling`; not on + origin/main. +- Test output: `test_issue_3771_xpath_predicate_with_xmlns_resolves` + observed 0 matches (or 5 wrong matches) for the user-supplied xpath on + namespaced XML. + +**Recommended action**: + +- pass1: keep-open - the leboff PR is a viable starting point; or implement + the reporter's "strip xmlns then re-add" approach for simplicity. +- pass2 labels: `severity:medium,area:source-transforms,type:bug,has-pr` + +--- + +### #3773: retrieve_profile task seems to be missing some Metadata + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug + +**Method**: +Read `cumulusci/salesforce_api/retrieve_profile_api.py` end to end. The +`_queries_retrieve_permissions` method (lines 164-195) builds queries for +`SetupEntityAccess`, `ObjectPermissions`, `PermissionSetTabSetting`, and a +flow-specific `SetupEntityAccess`. There is no `FieldPermissions` query. +Wrote a test that asserts `fieldpermissions` appears in the joined query +text. + +**Evidence**: + +- `cumulusci/salesforce_api/retrieve_profile_api.py:164-195` - no + `FieldPermissions` query. +- Greped `FieldPermission|field_permission|fieldPermission` - only the + `update_profile.py`, `permissions.py`, and `mapping_parser.py` files + reference field permissions; `retrieve_profile_api.py` does not. +- Test output: `test_issue_3773_retrieve_profile_queries_field_permissions` + shows the four queries built by the function and confirms none include + `FieldPermissions`. + +**Caveat**: This is a code-level repro. Final end-to-end confirmation +(profile XML actually missing AccountContactRelation field perms) would +require a real org with a profile that has only field-level (not +object-level) permissions on AccountContactRelation. Code evidence is +conclusive, however, because objects with no `ObjectPermissions` row never +make it into the package.xml requested for retrieve. + +**Recommended action**: + +- pass1: keep-open - needs additional `FieldPermissions` query plus + inclusion of those parent SObjectTypes in the `CustomObject` retrieve set. +- pass2 labels: `severity:medium,area:retrieve-profile,type:bug` + +--- + +### #3852: CumulusCI 4 refresh token error (sarge Capture.flush) + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug +**Method**: code-scan + runtime probe + +**Evidence**: + +- `pyproject.toml:52` - `"sarge"` pinned with no version constraint. +- `uv run python -c "import sarge; print(sarge.__version__)"` → `0.1.7.post1`. +- `uv run python -c "import sarge; print(hasattr(sarge.Capture, 'flush'))"` → `False`. The upstream fix (`def flush(self): pass`) sits unreleased on master. +- `cumulusci/core/config/sfdx_org_config.py:200-214` - `refresh_oauth_token` still calls `self.sfdx_info` at line 212. Per the maintainer comment in-thread, on Python 3.13 this triggers the `AttributeError: 'Capture' object has no attribute 'flush'` during interpreter shutdown logging path - cosmetic only, no functional regression. + +**Recommended action**: + +- pass1: `keep-open` - kept-open per maintainer label; track until sarge 0.1.8 ships or we vendor/swap the dependency. +- pass2 labels: `bug, upstream:sarge, py313` + +### #3854: Issue while Capturing Data (capture_sample_data lookup_key validation) + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug +**Method**: code-scan + +**Evidence**: + +- `cumulusci/tasks/bulkdata/extract.py:367-374` - the offending validation block is intact: +- `if total_mapping_operations != total_rows: raise ConfigError(f"Total mapping operations ({total_mapping_operations}) do not match total non-empty rows ({total_rows}) for lookup_key: {lookup_key}. Mention all related tables for lookup: {lookup_key}")` +- Error text matches the user's report verbatim. +- Per swirkens' comment, this validation was introduced in PR #3741 / commit `2c5d0056e` and remains unchanged in v4.10.0. +- Workaround in thread: pin to CCI 3.84.1 (pre-validation). + +**Recommended action**: + +- pass1: `keep-open` - kept-open per maintainer label; real bug for polymorphic-lookup users. +- pass2 labels: `bug, area:bulkdata, regression` + +### #3884: Running a Dev_Org flow goes through re-install of the same package version again + +**Verdict:** `INCONCLUSIVE-needs-project-with-managed-deps` + +**Evidence:** [`_(repro evidence)_`](_(repro evidence)_) + +The reporter (dipakparmar, 2025-02-26) describes that re-running `dev_org` reinstalls dependencies that are already installed. CumulusCI itself has no `project__dependencies` block, so end-to-end repro on this very repo is not possible. Source review against v4.10.0 shows that BOTH managed-package install paths in `cumulusci/core/dependencies/dependencies.py` already have a "skip if already at this or newer version" guard: + +```458:465:cumulusci/core/dependencies/dependencies.py + if org.has_minimum_package_version( + self.namespace, + version, + ): + context.logger.info( + f"{self} or a newer version is already installed; skipping." + ) + return +``` + +```513:520:cumulusci/core/dependencies/dependencies.py + if any( + self.version_id == v.id + for v in itertools.chain(*org.installed_packages.values()) + ): + context.logger.info( + f"{self} or a newer version is already installed; skipping." + ) + return +``` + +Likely the reporter conflated "Resolving dependencies..." log noise (which always prints) and the unconditional `deploy_unmanaged` / `config_dev` steps in the dev_org flow with managed-package reinstalls. Without a customer project that has managed deps to reproduce against, we cannot disprove a corner case (e.g., `installed_packages` cache invalidation across a particular path). + +**Recommendation:** keep the proposed `closed:missing-fields` (rule 3) verdict. The original report lacks the customer's `cumulusci.yml` excerpt that would let us see which dependency type they were observing reinstall. + +--- + +### #3886: Required Dependencies? + +**Bucket**: A. **Type**: bug (UX/log-noise). **Verdict**: +REPRODUCED-on-v4.10.0. + +The `[select]` extras (`numpy`, `pandas`, `annoy`, `scikit-learn`) are not in +the default install. `cumulusci/tasks/bulkdata/select_utils.py` L14-30 emits +`logger.warning("Optional dependencies are missing...")` at module-import time +in the `try/except ImportError`. Two transitive imports +(`mapping_parser` and `step`) pull in `select_utils`, and `extract.py` imports +both, so **every** `extract_dataset` invocation triggers the warning even when +no select strategy is configured. + +Behavior introduced in PR #3858 / commit `89a5b5ddb` and +unchanged through v4.10.0. The reporter's quoted text mentioned +`pipx upgrade cumulusci[select]`; v4.10.0 now uses `get_cci_upgrade_command()` +which adapts to the install method (e.g. `pip install --upgrade cumulusci[select]`), +so that part of the message has been polished - but the noise persists. + +Recommendation: keep-open. Mitigations to consider: + +- Move the warning emission out of module-import and into the code path that + actually consumes optional deps (e.g. inside the relevant similarity + strategy or `Annoy`-using step), so it only fires when the user opts into + `select` strategies. +- Or downgrade to `logger.debug` and add a one-line + `logger.warning` only at the point of need. + +Repro: `_(repro evidence; see narrative)_` (2 tests; all pass). + +This issue was double-tagged in `themes.md` (bulkdata + dependencies); +classified under dependencies here per bundle instructions. + +### #3889: Uninstall 2GP task request + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: feature + +**Method**: +Listed all `Uninstall*` task definitions in `cumulusci.yml` and inspected `UninstallPackage.py` and `UninstallPackageZipBuilder` to see whether any task accepts a 04t SubscriberPackageVersionId. + +**Evidence**: + +- `cumulusci/cumulusci.yml:615-642` - Uninstall tasks: `uninstall_managed`, `uninstall_packaged`, `uninstall_packaged_incremental`, `uninstall_src`, `uninstall_pre`, `uninstall_post`. None take a 04t id. +- `cumulusci/tasks/salesforce/UninstallPackage.py:6-32` - `UninstallPackage` accepts only `namespace` (and `purge_on_delete`). Builds an `UninstallPackageZipBuilder` from the namespace. +- `cumulusci/salesforce_api/package_zip.py:290-301` - `UninstallPackageZipBuilder` writes destructiveChanges referencing `InstalledPackage` by namespace; no 04t code path. +- Tooling API `SubscriberPackageVersion` delete or sf cli `package uninstall -p 04t...` is the underlying API the user wants; not wrapped by any CCI task. + +**Recommended action**: + +- pass1: `keep-open` - needs a new `UninstallPackageVersion` task (or extend `UninstallPackage`) that calls Tooling API directly so it doesn't depend on sf cli stability (per the user's note about sf cli breaking changes). +- pass2 labels: `severity:medium,area:packaging,area:2gp,area:unlocked-package` + +### #3899: Exception in task deploy_packaging.unschedule_apex + +**Verdict:** `INCONCLUSIVE-needs-1gp-packaging-org` + +**Evidence:** [`_(repro evidence)_`](_(repro evidence)_) + +The `unschedule_apex` task (cumulusci/cumulusci.yml lines 646-651) runs one line of trivial Apex via the Tooling API: + +```650:650:cumulusci/cumulusci.yml + apex: "for (CronTrigger t : [SELECT Id FROM CronTrigger]) { System.abortJob(t.Id); }" +``` + +Ran cleanly against a scratch org ("Anonymous Apex Executed Successfully!"). The reporter's error references Salesforce platform-internal Java classes (`system.scheduler.cron.JobType`, `common.udd.constants.CronJobTypeEnum`) - this is a Salesforce platform NullPointerException inside the scheduler subsystem when looking up a CronTrigger's job-type implementation. CCI sends correct Apex; the platform fails to execute it on certain 1GP packaging org configurations. Provisioning a 1GP packaging org via OAuth connected app is outside the scope of this triage. + +**Recommendation:** keep `kept-open`. Add a Pass-2 `external/upstream-salesforce` label (or equivalent) so future triage knows the root cause is upstream. Could also be a candidate for "close as not-our-bug" once a Salesforce known-issue reference is found. + +--- + +### #3902: `install_managed` `security_type` not respected with 04t ID + +**Verdict:** `INCONCLUSIVE-needs-managed-package-04t` + +### Original symptom + +User reports that running `install_managed` with `--version '04t…'` and `--security_type NONE` yields a tab visible to non-admin profiles, whereas the same install with `--version '1.11.2'` (namespace+version) honors `NONE`. + +### Code-only evidence on v4.10.0 + +`InstallPackageVersion._init_options` (cumulusci/tasks/salesforce/install_package_version.py) builds `PackageInstallOptions` from task options including `security_type`: + +```148:148:cumulusci/tasks/salesforce/install_package_version.py + self.install_options = PackageInstallOptions.from_task_options(self.options) +``` + +`PackageInstallOptions.from_task_options` (cumulusci/salesforce_api/package_install.py:67-92) parses `security_type` into the `SecurityType` enum where `SecurityType.ADMIN = "NONE"`. + +For 04t versions, `_run_task` routes to `PackageVersionIdDependency.install(...)` which calls `install_package_by_version_id(...)` -> `_install_package_by_version_id(...)`. The latter posts the option to the Tooling API: + +```165:175:cumulusci/salesforce_api/package_install.py + request = PackageInstallRequest.create( + { + "EnableRss": options.activate_remote_site_settings, + "NameConflictResolution": options.name_conflict_resolution, + "Password": options.password, + "SecurityType": options.security_type, + ... + } + ) +``` + +Runtime serialization confirmed in the venv: + +```text +$ uv run python -c "import json; from cumulusci.salesforce_api.package_install import SecurityType; print(json.dumps({'SecurityType': SecurityType.ADMIN}))" +{"SecurityType": "NONE"} +``` + +`SecurityType` is a `StrEnum` (cumulusci/core/enums.py) with `__str__ = str.__str__`, so JSON serializes via the string base - `SecurityType.ADMIN` -> `"NONE"`. This was hardened previously in commit `502290b8d` (Dec 2022) for Python 3.11 enum changes and again in `402b890e0` (StrEnum migration). + +The 04t and namespace+version paths each correctly pass `security_type` to their respective Salesforce APIs (Tooling API `PackageInstallRequest.SecurityType` for 04t, package install zip header for namespace+version). No defect identified in CumulusCI v4.10.0. + +### Why INCONCLUSIVE + +We do not have a known reusable managed package 04t Id installable into a the configured DevHub-derived scratch to verify the user's runtime observation. Per spec, this verdict is allowed when the prerequisite cannot be provisioned within budget. + +### Possible non-CumulusCI explanations (worth recording for the issue) + +- Salesforce Tooling API treatment of `SecurityType=NONE` may differ between fresh installs and upgrades: existing components retain their previously assigned profile permissions on upgrade. If the package was previously installed at a lower version with `SecurityType=FULL`, upgrading with `NONE` may not retroactively restrict access to existing tabs. +- Tab visibility for managed-package tabs can be controlled by App-level visibility (Profile `applicationVisibilities` / `tabVisibilities`) independent of object-level CRUD/FLS, which is what `SecurityType` governs. +- Package metadata may include `Profile` deltas that explicitly grant tab access regardless of the install-time SecurityType. + +### Recommendation + +- **Pass 1:** needs-info - request reporter to (a) confirm whether the package was being upgraded vs freshly installed, and (b) inspect Salesforce setup audit trail to see the actual `PackageInstallRequest.SecurityType` value at install time. +- **Pass 2 label:** `needs-managed-package-fixture` - without an internal reusable managed package 04t fixture, this class of issue can never be deterministically validated by maintainers. + +--- + +### #3929: create_community Loop/Timeout During Community Creation + +**Verdict:** `NOT-REPRODUCED-on-v4.10.0` + +**Evidence:** [`_(repro evidence)_`](_(repro evidence)_) + +Ran the exact reproduction command against a scratch org: + +```bash +uv run cci task run create_community --org [scratch-org] \ + -o name "TestWebsite" -o template "Customer Service" -o url_path_prefix "testwebsite" +``` + +Community `0DBRK000000QtNR4A0` was created in ~117 seconds with normal polling escalation (1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 seconds) and exited the poll loop cleanly. No 300-second timeout, no retry. This matches the OP-thread comment from dipakparmar (2025-10-22): "This issue is no longer happening" - referring to the upstream SF CLI / Communities API fix tracked at forcedotcom/cli#3419. + +**Recommendation:** flip currently-proposed `kept-open` → `closed:not-reproducible-on-v4.10.0` (NEW Pass-1 vocabulary per spec amendment). Confirmed working end-to-end with the very command the reporter said hung. + +### #3931: Specifying a profile in cumulusci.tasks.salesforce.ProfileGrantAllAccess results in an error + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug +**Org used**: none (Python unit repro is sufficient - code path is purely local XML transform) + +**Method**: +Read `cumulusci/tasks/salesforce/update_profile.py`. Spotted the suspect line: + +```290:292:cumulusci/tasks/salesforce/update_profile.py + for elem in tree.findall("layoutAssignments"): + if elem.find("recordType").text == rt["record_type"]: + elem.layout.text = layout_option +``` + +`elem.find("recordType")` returns `None` whenever a `layoutAssignments` element has no `recordType` child (which is valid metadata - a layoutAssignments without recordType applies to records lacking a record-type binding). The subsequent `.text` then raises `AttributeError: 'NoneType' object has no attribute 'text'` - exactly the user's reported error. + +Wrote `_(repro evidence; see narrative)_` that builds an in-memory profile XML matching that shape (one `` with `Account-Account Layout` and no recordType, plus one with both children) and calls `_set_record_types`. Output: + +``` +Traceback (most recent call last): + File "_(repro evidence; see narrative)_", line 64, in main + task._set_record_types(tree, "Admin") + File "...update_profile.py", line 291, in _set_record_types + if elem.find("recordType").text == rt["record_type"]: + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +AttributeError: 'NoneType' object has no attribute 'text' +REPRODUCED: AttributeError raised: 'NoneType' object has no attribute 'text' +``` + +**Recommended action**: + +- pass1: `keep-open` - small, contained fix. +- pass2 labels: `bug` + +**Notes**: Minimal fix at update_profile.py:290-293 - bind `rt_elem = elem.find("recordType")` and check `if rt_elem is not None and rt_elem.text == rt["record_type"]`. Worth a quick scan of sibling code in `_set_record_types` for similar None-deref patterns on optional XML children. + +--- + +### #3936: HTTPSConnectionPool Read timed out (kept-open) + +**Verdict:** `INCONCLUSIVE-needs-flaky-network` - but a structural gap is confirmed + +`get_simple_salesforce_connection` in `cumulusci/salesforce_api/utils.py:13-51` constructs `simple_salesforce.Salesforce(...)` with no timeout kwarg and only retries `502/503/504` via `Retry(total=5, backoff_factor=0.3)`. No CCI-side task option, project setting, or env var exposes connect / read timeout for Salesforce REST or Bulk API calls. `cumulusci.yml` has no `timeout` entry. + +The reported error `Read timed out. (read timeout=None)` with `timeout=None` typically means the proxy / VPN closed the socket while the client was waiting, not that a client-side timeout was hit. CCI cannot mitigate this without (a) exposing a configurable timeout to short-circuit zombie connections, and (b) extending the `Retry` to also cover read-timeout errors and bulk-job-polling failures. + +The structural gap (no exposed timeout) is REPRODUCED. The actual flaky behavior is environment-dependent (corporate VPN) and not reproducible in CI without that environment. + +This issue is already maintainer-labelled `kept-open`. **Recommended pass1: `unchanged`**. v5 candidacy: yes - add timeout option to `get_simple_salesforce_connection` and to bulk-job polling, plus a documented `cci org refresh` retry path for VPN-induced disconnects. + +### #3938: Rest_Deploy ignores errors + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug + +**Method**: +Read `cumulusci/salesforce_api/rest_deploy.py`. Wrote a test that constructs +a `RestDeploy` instance with mocked task/org_config and patches +`requests.get` to return a JSON payload with `deployResult.status == "Failed"` +and a `componentFailures` list. + +**Evidence**: + +- `cumulusci/salesforce_api/rest_deploy.py:101-120` - + `_monitor_deploy_status` logs `componentFailures` then `return`s without + raising. +- `cumulusci/salesforce_api/rest_deploy.py:75-85` - `__call__` only logs + when initial POST is non-201; never raises. +- Test output: `test_issue_3938_rest_deploy_failure_does_not_raise` exits + the patched call with no exception, confirming the silent-success bug. + +**Recommended action**: + +- pass1: keep-open - CRITICAL severity; should raise `MetadataApiError` / + `MetadataComponentFailure` on Failed status, mirroring the SOAP + `ApiDeploy` behavior. +- pass2 labels: `severity:critical,area:rest-deploy,type:bug,silent-failure` + +--- + +### #3939: Deploy task can't parse SOAP Response + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug + +**Method**: +Read `cumulusci/salesforce_api/metadata.py:67-78` (`BaseMetadataApiCall.__call__`) +and `cumulusci/salesforce_api/metadata.py:425-546` +(`ApiDeploy._process_response`). The parser correctly raises +`ApexTestException` on Apex test failures (line 540) and +`MetadataApiError`/`MetadataComponentFailure` for other failure shapes +(lines 509, 520, 544). But every one of those exceptions is caught by the +generic `except Exception as e:` at line 73 and re-raised as +`MetadataParseError("Could not process MDAPI response: ...")`. The wrapping +matches the exact message text in the user report. + +**Evidence**: + +- `cumulusci/salesforce_api/metadata.py:71-76` - wraps every exception + thrown inside `_process_response`. +- `cumulusci/salesforce_api/metadata.py:509,520,540,544` - places where + intentional MDAPI exceptions are raised inside `_process_response`. +- Test output: `test_issue_3939_deploy_apex_test_failure_swallowed` + observed final message + `Could not process MDAPI response: Apex Test Failure: Class.MyTestClass.testIt: line 12, column 1` + which is the exact bug pattern. + +**Recommended action**: + +- pass1: keep-open - the wrapping `except Exception` should re-raise CCI's + own `CumulusCIException` subclasses (`MetadataApiError`, + `MetadataComponentFailure`, `ApexTestException`) untouched and only wrap + truly unexpected errors. +- pass2 labels: `severity:high,area:salesforce-api,type:bug,error-handling` + +### #3951: set_duplicate_rule_status broken + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug (UX/error-message) +**Org used**: `[scratch-org]` + +**Method**: +Live repro using the exact CLI from the bug report: + +``` +uv run cci task run set_duplicate_rule_status --org [scratch-org] \ + -o api_names "Standard_Rule_for_Leads_with_Duplicate_Contacts" \ + -o active False +``` + +Result (saved at `_(repro evidence)_`): + +``` +Error: Cannot find metadata file /var/.../retrieve/duplicateRules/Standard_Rule_for_Leads_with_Duplicate_Contacts.duplicateRule +``` + +Identical error wording as the user's report. Then ran the same command with the canonical Metadata API format `Lead.Standard_Rule_for_Leads_with_Duplicate_Contacts` - the task succeeded end-to-end (extract → transform → deploy → Success). The `SetDuplicateRuleStatus` task itself is functional; the bug is the unhelpful error when the user omits the `.` prefix that DuplicateRule API names require. + +Same root cause as #3613 (api_name format mismatch surfaces as the generic `Cannot find metadata file` from `MetadataSingleEntityTransformTask._transform` base.py:332). + +**Recommended action**: + +- pass1: `improve-error-message` - keep open. +- pass2 labels: `bug`, `good-first-issue`, `documentation` + +**Notes**: Two improvements: + +1. Update the `set_duplicate_rule_status` task option help to call out the `.` format requirement. +2. Same as #3613 - improve the base.py:332 error to list the files actually retrieved. + +The `Cannot find metadata file` error is shared across most `MetadataSingleEntityTransformTask` subclasses, so a single base-class fix would benefit several issues at once. + +--- + +### #3953: add_picklist_entries never works through CLI + +**Verdict**: REPRODUCED-on-v4.10.0 +**Repro type**: bug +**Org used**: `[scratch-org]` + +**Method**: +Live repro using the exact CLI from the bug report: + +``` +uv run cci task run add_picklist_entries --org [scratch-org] \ + -o picklists "Account.Status__c" \ + -o entries '[{"fullName": "TestValue", "label": "Test Value"}]' +``` + +Result (saved at `_(repro evidence)_`): + +``` +Error: The 'fullName' key is required on all picklist values. +``` + +Identical to user's report. + +Root cause confirmed by reading `cumulusci/tasks/metadata_etl/picklists.py`: + +- Line 51: `process_list_arg(self.options["picklists"])` - runs through the list-arg parser. +- Line 68: `if not all(["fullName" in entry for entry in self.options["entries"]])` - iterates `entries` directly without parsing. +- The CLI passes `entries` through as a JSON string. Iteration walks characters of `'[{"fullName": "TestValue", ...}]'`; none of the characters contain the substring `"fullName"`; `all(...)` returns False; error raised. + +**Recommended action**: + +- pass1: `keep-open` - single-line fix. +- pass2 labels: `bug`, `good-first-issue` + +**Notes**: Minimal fix in `AddPicklistEntries._init_options`: `if isinstance(self.options.get("entries"), str): self.options["entries"] = json.loads(self.options["entries"])`. Apply same pattern to `record_types` for symmetry. The same class of bug exists in `AddFieldsToPageLayout` (encountered while investigating #3613): `cci task run add_page_layout_fields ... -o fields '[...]'` -> `pydantic.ValidationError: value is not a valid list`. A more general fix would be a helper in the CLI/task-base that auto-parses JSON strings for list-typed options, or schema-driven coercion via the new task_options Pydantic models. + +--- + +Themes: robotframework (the triage), scratch-org-config (the triage), auth (the triage), keychain (the triage), docs (the triage), python-modernization (the triage). +Scope: 17 issues across 6 themes, all against `origin/dev` @ `1925a3083` (NOT v4.10.0 like Rounds 1+2). +Verdicts: 12 REPRODUCED-on-dev, 5 NOT-REPRODUCED-on-dev. 10 `xfail` tests staged at `_(repro evidence)_`. + +Worktree: the triage worktree (the triage worktree @ `1925a3083` on `origin/dev`). + +5 issues processed: #3955, #3873, #675, #987, #3602. + +--- + +### #3955: Open Test Browser - SalesforcePlaywright.robot + +**Verdict**: REPRODUCED-on-dev +**Repro type**: bug +**Method**: code-scan + pytest (mocked Browser library) + +**Evidence**: + +- `cumulusci/robotframework/SalesforcePlaywright.py:106` - `width, height = size.split("x", 1)` returns two `str` values. +- `cumulusci/robotframework/SalesforcePlaywright.py:109-111` - the strings are forwarded directly: + +```python +context_id = self.browser.new_context( +viewport={"width": width, "height": height}, recordVideo=record_video +) +``` + +- Playwright contract requires `viewport.width` / `viewport.height` to be `int`, hence the runtime error `viewport.width: expected integer, got string` reported by the user (and confirmed by commenter @rasjani's pointer to lines 106-111). + +**Proposed fix sketch**: + +- Approach: cast both fragments to `int` immediately after splitting. +- Target: `cumulusci/robotframework/SalesforcePlaywright.py:106` +- Size: small (~1 line) - e.g. `width, height = (int(v) for v in size.split("x", 1))` +- Risk: low - preserves all existing call sites; users were already passing the documented `WxH` string format. +- API break: no. + +**Recommended action**: + +- pass1: `keep-open` - clear, low-risk, single-line bug fix; great good-first-issue candidate. +- pass2 labels: `bug, robotframework, playwright, good-first-issue` +- triage test: `cumulusci/tests/triage/test_issue_3955.py` + +--- + +### #3873: Standalone Robot Framework Library for Selenium-Based Salesforce Automation (Inspired by Copado QForce) + +**Verdict**: REPRODUCED-on-dev (feature still missing) +**Repro type**: feature +**Method**: code-scan + +**Evidence**: + +- `cumulusci/robotframework/base_library.py:1-39` - `BaseLibrary` lazily resolves `cumulusci.robotframework.CumulusCI`, `cumulusci.robotframework.Salesforce`, `cumulusci.robotframework.SalesforceAPI` libraries through Robot's `BuiltIn`, which require a CumulusCI project context. +- `cumulusci/robotframework/Salesforce.py:20-28` - imports `cumulusci.robotframework.locator_manager`, `faker_mixin`, `form_handlers`, plus the rest of the cci core that ships in the same package. +- There is no standalone distribution; the Salesforce/SalesforcePlaywright libraries cannot be installed without the full `cumulusci` wheel and project layout. + +**Proposed fix sketch**: + +- Approach: factor a UI-only subset out of `cumulusci.robotframework.*` into a sibling distribution (e.g. `salesforce-robot-library`) that depends only on Selenium/Playwright + the locator dictionaries; have CumulusCI's Robot task consume that subset. +- Target: package layout change spanning `cumulusci/robotframework/` and `pyproject.toml`. +- Size: large (>100 lines, design change + new distribution). +- Risk: medium - must not break existing tasks/keywords. +- API break: no (additive new distribution). + +**Recommended action**: + +- pass1: `keep-open` - reasonable architectural request, age <24mo, no community PR yet; worth retaining as roadmap signal. +- pass2 labels: `enhancement, robotframework, scope-large` +- triage test: n/a - no concrete API to xfail against. + +--- + +### #675: Show full traceback for Python exceptions in robot keywords + +**Verdict**: REPRODUCED-on-dev (feature still missing) +**Repro type**: feature (cli-usability) +**Method**: code-scan + +**Evidence**: + +- `cumulusci/tasks/robotframework/robotframework.py` configures listeners (`KeywordLogger`, `DebugListener`) but never sets `loglevel`/`logtitle`/`pythonpath` to surface Python tracebacks. `rg 'traceback|format_exc|format_tb' cumulusci/robotframework/` returns 0 matches; same for `cumulusci/tasks/robotframework/`. +- Robot Framework's default behaviour: when a Python keyword raises, only `str(exc)` is shown in the report; the full traceback requires either `--loglevel TRACE`, `--listener` overrides, or `error.ROBOT_CONTINUE_ON_FAILURE`/`error.ROBOT_EXIT_ON_FAILURE` plumbing. CumulusCI does none of this by default. + +**Proposed fix sketch**: + +- Approach: in `Robot._init_options`, default `options.loglevel` to include `TRACE`, OR install a small listener that captures `sys.exc_info()` in keyword-end events and emits a formatted traceback through `robot.api.logger.error`. +- Target: `cumulusci/tasks/robotframework/robotframework.py:104` (`_init_options`). +- Size: small (~10 lines). +- Risk: low - additive listener; opt-out via options if needed. +- API break: no. + +**Recommended action**: + +- pass1: `closed:stale-24mo` - issue opened 2018-07, last activity 2018-09, ~8 years inactivity. Two simple workarounds exist (`-o loglevel:TRACE`, `traceback.format_exc()` in keywords). Surface as a tip in docs rather than keep the bug open. +- pass2 labels: `cli-usability, robotframework, stale` +- triage test: n/a - observable only in a robot run. + +--- + +### #987: Last week this test worked, now I get a javascriptexception message. + +**Verdict**: NOT-REPRODUCED-on-dev +**Repro type**: bug (legacy) +**Method**: code-scan + history review + +**Evidence**: + +- Issue specifies Salesforce Spring 19, chromedriver 2.46.628411, chrome 72.0.3626.109 - all extinct. +- Reporter posted on 2019-02-18: _"@davisagli I figured out a work-around. Use firefox with geckodriver. Everything works as intended now."_ - i.e. accepted a working alternative. +- `cumulusci/robotframework/Salesforce.py:154` `click_object_button` still calls `_jsclick`, which has been the workaround pattern for shadow-DOM-aware clicks for years; current Selenium 4 / Salesforce Lightning UI no longer surfaces the original `Cannot read property 'defaultView' of undefined` error in the same form. +- Last comment 2022-06 (davidmreed asking if still impacted; no response). + +**Proposed fix sketch**: n/a - environment-specific historical bug. + +**Recommended action**: + +- pass1: `closed:stale-24mo` - reporter has a workaround, root cause was Spring 19 specific, 7+ years stale, last activity 2022-06 (still 4 years ago). +- pass2 labels: `stale, user-error` +- triage test: n/a - would require a live 2019 org snapshot. + +--- + +### #3602: Need Chrome/Firefox options(browser options/capabilities) in 'Open Test Browser' Keyword + +**Verdict**: REPRODUCED-on-dev (feature still missing) +**Repro type**: feature +**Method**: code-scan + pytest (signature assertion) + +**Evidence**: + +- `cumulusci/robotframework/SalesforcePlaywright.py:60-62` - `def open_test_browser(self, size=None, useralias=None, wait=True, record_video=None):` has no `browser_options`/`extra_options`/`**kwargs` hook. +- `cumulusci/robotframework/Salesforce.robot:103` - Selenium keyword signature `[Arguments] ${size}=... ${alias}=${NONE} ${wait}=True ${useralias}=${NONE}` - same gap. +- `cumulusci/robotframework/Salesforce.robot:157-168` `Get Chrome Options` hard-codes a single `--disable-notifications` argument with no extension hook. +- Users cannot load Chrome extensions, switch to incognito, change download directory or accept self-signed certs without a fork of `Salesforce.robot`. + +**Proposed fix sketch**: + +- Approach (Playwright): add a `browser_options: dict | None = None` kwarg; pass through to `new_browser` (browser-launch options) and a separate `context_options` dict merged into the `new_context` call. +- Approach (Selenium): expose a `${EXTRA_CHROME_OPTIONS}` variable / list argument honoured by `Get Chrome Options`; alternatively add an `extra_options` argument to `Open Test Browser` and pipe through to `Create Webdriver With Retry`. +- Target: `cumulusci/robotframework/SalesforcePlaywright.py:60-117`; `cumulusci/robotframework/Salesforce.robot:77-168`. +- Size: medium (~30-60 lines across both implementations + tests + docs). +- Risk: low - additive parameter with safe default. +- API break: no (default `None` preserves current behaviour). + +**Recommended action**: + +- pass1: `keep-open` - reasonable, scoped feature ask; age <36mo; no PR yet; tractable medium-sized contribution. +- pass2 labels: `enhancement, robotframework, playwright, good-second-issue` +- triage test: `cumulusci/tests/triage/test_issue_3602.py` + +Working tree: the triage worktree on the triage worktree off `origin/dev` at `1925a3083`. + +Triaged 3 issues (#3910, #3306, #710). All three remain actionable on dev: #3910 has an open fix PR; #3306 and #710 are never-implemented enhancements. + +### #3910: JSON Schema incorrectly defines namespaced field as string instead of boolean for scratch org configuration + +**Verdict:** `REPRODUCED-on-dev`. Bug. + +**Evidence:** + +- `cumulusci/utils/yaml/cumulusci_yml.py:150` declares `namespaced: str = None` on the `ScratchOrg` Pydantic v1 model. +- The auto-generated schema at `cumulusci/schema/cumulusci.jsonschema.json:424` reads `"type": "string"` (the test `cumulusci/tests/test_schema.py:test_schema_is_current` enforces that the saved schema matches the model output, so the two are locked together). +- `ScratchOrg.parse_obj({"namespaced": True})` silently coerces to the literal string `"True"`; `{"namespaced": False}` to `"False"`. Either value is non-empty, so a downstream `if not self.namespaced` check would flip its logic if the model output were used directly. +- The actual runtime path (`BaseProjectKeychain.create_scratch_org` at `cumulusci/core/keychain/base_project_keychain.py:74`, then `ScratchOrgConfig._build_org_create_args` at `cumulusci/core/config/scratch_org_config.py:143`) bypasses the Pydantic model via `project_config.lookup`, so booleans set in YAML survive at runtime. The user-visible impact is therefore: editors that consume the JSON schema reject `namespaced: true`/`false` (boolean) in `cumulusci.yml`, and `make schema` regenerates the same incorrect schema until the model is fixed. +- Open PR [#3911](https://github.com/SFDO-Tooling/CumulusCI/pull/3911) (base `main`, branch `fix/namespaced-config-value-type-in-schema`) correctly fixes both files. State on 2026-05-14: OPEN, not merged. + +**Repro test:** `cumulusci/tests/triage/test_issue_3910.py` - 4 XFAILs against the schema JSON, the live Pydantic schema, and the model's coercion of booleans. + +**Proposed fix sketch** + +- Approach: land PR #3911 essentially as-is (one-line Pydantic field change in `cumulusci_yml.py` + `make schema` regenerated `cumulusci.jsonschema.json`). Both edits must ship together because `test_schema_is_current` would otherwise fail. +- Target file:line: `cumulusci/utils/yaml/cumulusci_yml.py:150` (`namespaced: str = None` → `namespaced: bool = None`); regenerate `cumulusci/schema/cumulusci.jsonschema.json`. +- Size: small. +- Risk: low. The only Pydantic consumers of `ScratchOrg.namespaced` are YAML-validation pathways; the runtime keychain path already uses booleans. +- API break: no (silent coercion was incidental and arguably already broken for users). + +### #3306: Preview Toggle for Scratch org def file + +**Verdict:** `NOT-REPRODUCED-on-dev`. Feature. + +**Evidence:** + +- `cci org scratch` already supports `--release preview|previous` (`cumulusci/cli/org.py:567-579`) and threads it through `BaseProjectKeychain.create_scratch_org` (`cumulusci/core/keychain/base_project_keychain.py:57-84`) to `ScratchOrgConfig._build_org_create_args` (`cumulusci/core/config/scratch_org_config.py:149-150`). +- `cci flow run` (`cumulusci/cli/flow.py:119-200`) has no `--release` / `--preview` flag. The flow path also auto-recreates expired scratch orgs in `cumulusci/cli/runtime.py:106-114` without forwarding any release argument. +- Internally tracked as [internal-ID-redacted]; no PR opened in 4 years. + +No test written - this is a clean enhancement request whose API surface (`--preview` vs `--release` flag, behaviour with non-scratch orgs, behaviour when `release` is already set in YAML) is not yet specced. + +### #710: Allow disabling default scratch org configs + +**Verdict:** `REPRODUCED-on-dev`. Feature. + +**Evidence:** + +- The universal config at `cumulusci/cumulusci.yml:1559` defines five default scratch orgs (`dev`, `qa`, `feature`, `beta`, `release`). +- `BaseProjectKeychain._load_scratch_orgs` (`cumulusci/core/keychain/base_project_keychain.py:149-159`) iterates every key under `orgs.scratch` and calls `create_scratch_org` unconditionally. +- `merge_config` (`cumulusci/core/utils.py:158`) calls `dictmerge`, which drops a `None` value from the project-side override (`{"orgs": {"scratch": {"dev": {"config_file": None}}}}` merged with the universal config still resolves to the universal's `config_file: orgs/dev.json`). The exact syntax proposed by the issue therefore has no effect. + +**Repro test:** `cumulusci/tests/triage/test_issue_710.py` - 2 XFAILs. One asserts that the project-side `config_file: None` override survives `merge_config`; the other asserts the resulting keychain does not load `dev`. + +**Proposed fix sketch** + +- Approach: introduce a sentinel for "disabled" (recommend an explicit `disabled: true` flag on each scratch org config rather than relying on `config_file: None`, which has overloaded meaning) and skip disabled entries in `_load_scratch_orgs`. Optional: a stricter `merge_config` mode that preserves `None` overrides under `orgs.scratch.*`. +- Target file:line: `cumulusci/utils/yaml/cumulusci_yml.py:147` (add `disabled: bool = None` to `ScratchOrg`); `cumulusci/core/keychain/base_project_keychain.py:155` (skip when `config.get("disabled")`); regenerate `cumulusci/schema/cumulusci.jsonschema.json`. +- Size: small/medium. +- Risk: low. Existing keys remain compatible; only adds new opt-in behaviour. Needs a docs note (`docs/orgs/scratch.md`) on how to disable inherited defaults. +- API break: no (additive). The issue's literal `config_file: None` syntax would NOT be honoured under this proposal; if the team prefers that exact syntax instead, add a `dictmerge` exception so `None` overrides under `orgs.scratch.*` are preserved, then have `_load_scratch_orgs` skip entries with `config_file is None`. Either implementation satisfies the XFAIL test (`dev not in keychain.orgs`). + +### #2667 - `cci org connect` should output the name of the connected app it is using + +**Theme**: auth · **Bucket**: enhancement (cli-output) · **Verdict**: `NOT-REPRODUCED-on-dev` + +**Original ask (prescod, 2021-06-08)**: When connecting an org, print which connected app is being used, e.g. `"Using connected_app 'xyzzy'"`, so users get a hint when a connected-app mismatch is the source of a confusing Salesforce error. + +**Status on `origin/dev` @ `1925a3083`**: Implemented. `cumulusci/cli/org.py` `org_connect` resolves the connected-app name (CLI flag → keychain default) and emits, before initiating OAuth: + +```204:209:cumulusci/cli/org.py + click.echo(f"Connecting org using the {connected_app_name} connected app...") + connected_app = runtime.keychain.get_service("connected_app", connected_app_name) + sf_client = setup_client(connected_app, login_url, sandbox) + connect_org_to_keychain( + sf_client, runtime, global_org, org_name, connected_app_name + ) +``` + +The connected_app name is also persisted onto the new `OrgConfig` (`org_config.config["connected_app"] = connected_app`, line 155), addressing davisagli's follow-up that orgs should remember which connected app authorized them. + +**Implementation history** (via `git log -L`): + +- `8fe1910b1` "refactor into separate methods" - split out `connect_org_to_keychain` and `setup_client`. +- `40520bee4` "checkpoint" (2022-01-31) - introduced the `connected_app_name` resolution and the `click.echo` line. This is the commit that closes the loop on this issue. Matches davidmreed's 2022-01-28 comment "Covering in [internal-ID-redacted]". +- `719d40260` "more tests, use the stored connected_app" - added test coverage and passed `connected_app_name` through to `connect_org_to_keychain` so it is stored on the org. + +**Test coverage already present**: + +- `cumulusci/cli/tests/test_org.py::TestOrgCommands::test_org_connect` asserts `"Connecting org using the built-in connected app..." in result.output` (line 135). +- `cumulusci/cli/tests/test_org.py::TestOrgCommands::test_org_connect__non_default_connected_app` asserts `"Connecting org using the other connected app..." in result.output` (line 191), explicitly covering the non-default case the issue called out as most important. + +Both pass on dev (verified locally; 10/10 `test_org_connect*` tests pass). + +**Recommendation**: `close-stale` with labels `resolved,implemented`. The exact phrasing differs from the issue's mock-up (`"Connecting org using the X connected app..."` vs `"Using connected_app 'X'"`), but the substantive ask - surfacing the connected-app identity at connection time - is satisfied. davisagli's deeper suggestions (configurable production/sandbox login URLs per connected app; auto-pick connected_app from login URL) are out of scope for this ticket and can be filed separately if still wanted. + +Worktree: the triage worktree @ `1925a3083` (off `origin/dev`). +Issues processed: 3/3 (#2126, #3407, #3541). +Verdict tally: 1 NOT-REPRODUCED (feature/stale), 2 REPRODUCED-on-dev. + +### #2126 - Probabilistic-encryption task + +**Classification**: feature request, theme-mismatched (Shield Platform +Encryption, not keychain). + +**What dev shows**: no `encrypt_all_encryptable_fields` task exists. +`rg -i 'encryptionScheme|encrypt_all_encryptable|probabilistic' cumulusci/` +returns zero hits. The issue is labelled `blocked` since 2022-01-28 when +davidmreed wrote "this feature has been developed but is blocked by bugs in +the underlying platform functionality." No platform follow-up has surfaced +in the comments; the issue is 5+ years stale. + +**Verdict**: `NOT-REPRODUCED-on-dev` (feature scope; no bug to reproduce). + +**Pass-1 recommendation**: `closed:stale-24mo`. Pass-2 labels: `area/metadata-etl`, +`blocked` (theme correction). No fix sketch - close or hand off to the +metadata-ETL theme owner with a stale-feature ping. + +### #3407 - `set_service(service_config)` annotation lies + +**Classification**: typing/API-consistency bug, still present on dev. + +**Reproduction path**: + +- `cumulusci/core/keychain/base_project_keychain.py` lines 202-209 declare + `service_config: ServiceConfig` on `set_service`. +- `cumulusci/core/keychain/encrypted_file_project_keychain.py` line 717 + calls `set_service(service_type, name, config, save=False, config_encrypted=True)` + where `config` is the raw text body of a `.service` file read at line 712-713. +- `_set_service` (encrypted_file_project_keychain.py:583-605) explicitly + short-circuits when `self.key and config_encrypted` and treats + `service_config` as the already-serialised payload, so the call is + semantically correct - the annotation is what's wrong. + +The two-test repro (`cumulusci/tests/triage/test_issue_3407.py`) introspects the +annotation, confirms a string call site exists in `_load_service_files`, and +exercises the runtime path with a string payload via a `BaseProjectKeychain` +subclass. Both xfail on dev. + +**Proposed fix sketch** + +- **Approach**: Widen the annotation. `set_service`'s `service_config` + parameter should be `Union[ServiceConfig, str]` (or `ServiceConfig | str | +bytes`) and the docstring updated to say "raw encrypted payload when + `config_encrypted=True`". The deeper refactor - splitting the API into + `set_service` (validated `ServiceConfig`) and `set_encrypted_service` + (raw blob) - is cleaner but breaks the public API and is out of scope + for this triage pass. +- **Target**: `cumulusci/core/keychain/base_project_keychain.py:202-219`. + Also add the same widened annotation on the abstract `_set_service` + (line 360) and propagate to `EncryptedFileProjectKeychain._set_service` + (line 583). +- **Size**: ~10 LOC plus a typing-import bump. Single-file change feasible. +- **Risk**: very low. Pure type-hint widening; no runtime behaviour change. + Downstream callers that already pass `ServiceConfig` keep working; + pyright/mypy users gain an accurate hint. +- **API break**: no. Widening a parameter type is non-breaking for + callers. + +### #3541 - `None__dev` SFDX alias + +**Classification**: real bug, mis-labelled `cannot-reproduce`. + +**Reproduction path**: + +- `cumulusci/core/keychain/base_project_keychain.py` lines 77-79: + +```python +scratch_config["sfdx_alias"] = ( +f"{self.project_config.project__name}__{org_name}" +) +``` + +- When `project_config.project__name` is None (cumulusci.yml without a + `project.name`, or partially-loaded project_config), this f-string + serialises the singleton `None` to the literal text `"None"`. The alias + becomes `"None__dev"`. +- Eager `_load_scratch_orgs` (line 45 → line 149-159) runs on keychain + construction and creates these mis-aliased configs before the user ever + runs `cci org scratch`. Reporter's claim that `cci org scratch dev dev` + fixes it is consistent: the explicit call re-runs `create_scratch_org` + after the project has fully loaded. +- The poisoned alias is then passed to `sfdx force config set +target-org={alias}` at `base_project_keychain.py:99`, producing the + reporter's symptom. + +Two xfail tests in `cumulusci/tests/triage/test_issue_3541.py` build a +`BaseProjectConfig` without `project.name`, drive both the direct +`create_scratch_org` path and the eager-init `_load_scratch_orgs` path, and +assert the resulting alias contains no literal `'None'` token. Both fail on +dev. + +**Proposed fix sketch** + +- **Approach**: Guard the alias construction. Two reasonable options: + (1) raise `CumulusCIException("Cannot build sfdx_alias: project.name is not set in cumulusci.yml")` - surfaces the misconfiguration at the earliest possible moment. + (2) Fall back to `f"{org_name}"` (no prefix) when `project__name` is None, with a `logger.warning`. Less disruptive for existing setups. + Option 2 is the safer change; option 1 is the more correct one. Pick based on willingness to break first-run UX. + +- **Target**: `cumulusci/core/keychain/base_project_keychain.py:77-79`. + Migration helper recommended to scan the keychain for `None__*` aliases + and rewrite them once a real `project.name` is available. + +- **Size**: ~10 LOC for the guard, ~30 LOC including a one-shot migration + pass on keychain load. + +- **Risk**: medium. Existing keychains in the wild may already contain + `None__dev` rows; option 2 silently writes a new alias on next run, + option 1 forces the user to fix cumulusci.yml. Document either way in + CHANGELOG. + +- **API break**: no public API change. The `OrgConfig.sfdx_alias` field + shape is preserved; only its derivation logic shifts. + +- **Bonus**: remove the `cannot-reproduce` label - the repro here is + deterministic. + +Worktree: the triage worktree @ `1925a3083` (off `origin/dev`). +Issues processed: 3/3 (#773, #2500, #3464). +Verdict tally: 3 REPRODUCED-on-dev (all pure doc-gaps with code-level +testable assertions). + +### #773 - Document task return values and results + +**Classification**: feature-with-doc-component (needs a small framework +hook + matching renderer + the docs themselves). + +**What dev shows**: + +- `BaseTask` (`cumulusci/core/tasks.py:51`) declares `return_values: dict` + as an instance attribute (line 64), initialised to `{}` in `__init__` + (line 92), populated at runtime by each task's `_run_task()`. There is + no declarative class attribute that would describe the _shape_ of those + return values. +- `doc_task()` (`cumulusci/utils/__init__.py:354`) walks `task_options` + and a free-form `task_docs` string and emits "Description", "Class", + "Command Syntax", and "Options" sections. There is no "Return Values" + or "Returns" section - and no plumbing to add one. +- `cci task info ` is implemented by `cumulusci/cli/task.py:99`, + which delegates straight to `doc_task`, so it has the same gap. +- The docs themselves admit this. `docs/config.md:740-744` includes an + `attention` admonition that reads: _"Current task return values are + not documented, so finding return values set by a specific task (if + any) requires you to read the source code for the given task."_ + +So 8 years after the issue was filed, the documented workaround in the +docs is still "read the source". The repro test (`cumulusci/tests/triage/test_issue_773.py`) +runs `doc_task` against a real shipping task (`PackageUpload`, whose +`_set_return_values()` populates `version_number` / `version_id` / +`package_id`) and asserts the rendered RST mentions any of those keys. +It xfails today. + +**Verdict**: `REPRODUCED-on-dev`. + +**Proposed fix sketch**: + +1. Add a class attribute on `BaseTask`: + +```python +return_values_schema: ClassVar[Dict[str, str]] = {} +``` + +where each key is the return-values dict key and each value is a one-line +description (parallel to how `task_options` already works). + +2. Extend `doc_task()` in `cumulusci/utils/__init__.py` to render a + "Return Values" RST section (heading + bullet list) when + `task_class.return_values_schema` is non-empty. Update the matching + `get_task_*` helpers to surface the schema for web-doc generation. + +3. Backfill the schema on tasks that already emit return values - search + for `self.return_values\[` across `cumulusci/tasks/`: at minimum + `PackageUpload`, `PromotePackageVersion`, `GithubRelease`, + `CreatePackageVersion`, dependency-resolution tasks. Each gets a few + lines. + +4. Lift the `attention` admonition in `docs/config.md:740-744` once + coverage is broad enough. + +Estimated effort: 1-2 day PR for the framework + first wave of tasks; an +ongoing "every new task documents its return values" contributor +expectation thereafter (enforce in `requesting-code-review` skill). + +### #2500 - `ignore_failure` is not documented + +**Classification**: pure doc-gap (the feature exists and works; only the +docs are missing). + +**What dev shows**: + +- The option exists in three independent places, so it is a stable, + public, supported feature: +- `cumulusci/utils/yaml/cumulusci_yml.py:45` - `Step` Pydantic model + declares `ignore_failure: bool = False`. +- `cumulusci/core/flowrunner.py:667` - uses `step_config.get("ignore_failure", False)` + to drive the `allow_failure` flag passed into the step runner. +- `cumulusci/schema/cumulusci.jsonschema.json:149` - exported in the + JSON schema for IDE autocomplete. +- In `docs/`, total mentions are exactly two: a single-line example use + inside a release-flow snippet at `docs/config.md:800` + (`ignore_failure: True`) and a one-line changelog blurb at + `docs/history.md:5993` from when the feature shipped. +- The `## Flow Configurations` chapter (line 294) has subsections for + "Add a Custom Flow" (303), "Add a Flow Step" (330), "Skip a Flow Step" + (388), "Replace a Flow Step" (414), "Configure Options on Tasks in + Flows" (432) - and there is documentation prose for `when:` at line + 474+ - but no parallel subsection for `ignore_failure`. +- Davidmreed cut GUS [internal-ID-redacted] on 2022-03-28 to track it; nothing has + landed in the 4+ years since. + +The repro test (`cumulusci/tests/triage/test_issue_2500.py`) confirms the +option exists in the `Step` model and asserts `docs/config.md` has a +heading-level section matching `ignore[ _]failure|ignore a failed|continue.*failure`. +The heading assertion xfails today; the existence assertion passes. + +**Verdict**: `REPRODUCED-on-dev`. + +**Proposed fix sketch**: + +Add `### Ignore a Failed Step` (or `### Continue After a Failed Step`) +to `docs/config.md` immediately after the existing `### Skip a Flow Step` +section. Body should cover: + +- What it does: when `ignore_failure: True` is set on a step, an + exception raised by that step does not abort the flow; subsequent + steps still run. +- A minimal YAML example (the existing `release_unlocked_production` + snippet around line 783 already demonstrates it - link to it instead + of duplicating). +- Interaction with `^^step.return_value` references: downstream steps + that reference a failed step's return values must defend against + missing keys. +- When NOT to use it: in CI, silently ignoring a failure hides + regressions; prefer a `when:` clause for intentional conditional + branching. +- One sentence on the difference between `ignore_failure` (post hoc: + swallow the exception) and `when:` (a priori: skip the step entirely). + +This is a tiny, high-leverage docs PR - a strong candidate for a +`good-first-issue` label. + +### #3464 - Concise project-config documentation + +**Classification**: doc-gap with structural-fix implications (the most +sustainable fix changes how docs are generated, not just what they say). + +**What dev shows**: + +- The authoritative shape of `project:` is the `Project` Pydantic model + at `cumulusci/utils/yaml/cumulusci_yml.py:135`, with nine fields: + `name`, `package`, `test`, `git`, `dependencies`, + `dependency_resolutions`, `dependency_pins`, `source_format`, `custom`. + Each of those (except scalars) points at a further sub-model with its + own fields - `Package` has 7, `Git` has 10, `DependencyResolutions` + has 3, `Test` has 1. +- `docs/config.md` shows exactly one project-level YAML example at line + 281 (NPSP's `project` block) covering `name` + a partial `package`. + There is no reference subsection enumerating every `project:` key. +- Substring scans of `docs/config.md`: +- `dependency_resolutions` → 0 occurrences. +- `dependency_pins` → 0 occurrences. +- `source_format` → 6 occurrences, all inside flow-step `when:` + clauses, not as a project-level reference. +- `custom` → 29 occurrences but none are about `project.custom`. +- The keys that _are_ documented live in a separate page - `docs/dev.md` + has `dependency_resolutions` at lines 596, 727 and `dependency_pins` + at lines 499, 509, 535 - which is exactly the _"scattered to the + wind"_ complaint in the issue body. jstvz acknowledged the gap in the + one comment on the issue ("We've made improvements to the + documentation over time, but there is still work to be done to make + it easier for users to find what they need"). + +The repro test (`cumulusci/tests/triage/test_issue_3464.py`) enumerates +every field of the `Project` Pydantic model and asserts each name +appears at least once in `docs/config.md`. Today it lists the missing +keys; xfails. + +**Verdict**: `REPRODUCED-on-dev`. + +**Proposed fix sketch**: + +Two-step path: + +1. **Tactical**: expand the `### Project Configurations` heading at + `docs/config.md:673` into a real reference subsection that lists + every `project:` top-level key with a one-sentence description and + one example value. Cross-link to `docs/dev.md` for in-depth treatment + of `dependencies` / `dependency_resolutions` / `dependency_pins` so + we are not duplicating the longer narratives, just providing a + central index. This alone closes the issue per the user's literal + ask. + +2. **Strategic** (separate, follow-up PR): backfill `Field(description=...)` + on every Pydantic attribute in `cumulusci_yml.py` and add a Sphinx + directive (or a `conf.py` autodoc hook) that emits the reference + table directly from the model at docs-build time. This keeps the + reference and the schema in lockstep - the same mechanism powers the + JSON schema (`cumulusci/schema/cumulusci.jsonschema.json`), so the + plumbing is straightforward. + +Step 1 is a single-day effort; step 2 is multi-day but pays back every +time a new field is added to `cumulusci.yml`. Recommend keep-open until +at least step 1 ships. + +Worktree: the triage worktree @ the triage worktree based on `origin/dev` (`1925a3083`). + +### #3849 - urllib3 v2 breaks Robot tests on a fresh pip install + +**Verdict**: `REPRODUCED-on-dev` → `keep-open`. + +**Code scan**: + +- `pyproject.toml` `[project].dependencies` (lines 26-59) declares no `urllib3` constraint. It still pins `selenium<4` (line 54) and `robotframework-seleniumlibrary<6` (line 50). Both of those pin chains force `selenium==3.141.0`, whose `urllib3` import-time use of the pre-2.0 `Timeout` sentinel object is what produces the user-reported `ValueError: Timeout value connect was `. +- `requests` (installed as a transitive runtime dep) declares `urllib3<3,>=1.26`, so a fresh `pip install cumulusci` resolves to whatever `urllib3` 2.x is current on PyPI. The local worktree happens to have `urllib3==1.26.20` because `uv sync` consumed `uv.lock`, but the lock is not what pip sees. Confirmed via `uv run python -c "import importlib.metadata as md; print(md.metadata('selenium').get_all('Requires-Dist'))"` → `selenium` declares `urllib3` with no version constraint. +- `git log --all --grep urllib3` shows only dependabot lock bumps (e6423311e, 05a904856) that never landed on `dev`. The original 2018 fix referenced in `docs/history.md` (`#832`) is not reflected in the modern dependency list. +- 6+ users have reported reblocking on this in 2024-2025 (Szandor72, dipakparmar, atran-agentsync, JonnyPower, elizabethrichardson-uw, kg345); the documented workaround is still `pip install urllib3==1.26.20` after installing cumulusci. + +**Repro test**: `cumulusci/tests/triage/test_issue_3849.py` parses the worktree's `pyproject.toml` (located via `cumulusci.__file__`) and asserts the modernized state - either an explicit `urllib3<…` constraint OR removal of both the `selenium<4` and `robotframework-seleniumlibrary<6` pins. Verified `XFAIL` under `uv run pytest -v -rx` (reason surfaces the docs path). + +**Proposed fix sketch**: Two acceptable shapes. + +1. **Cheap, restores Robot reliability**: add `"urllib3<2"` to `[project].dependencies` in `pyproject.toml`. This makes pip honor what `uv.lock` already encodes. Drop the matching workaround note from #3849. +2. **Right long-term**: bump to `robotframework-seleniumlibrary>=6` and `selenium>=4`, which removes the Timeout-sentinel incompatibility entirely. Audit `cumulusci/robotframework/*` for Selenium 4 API drift (the comment on the original PR notes integration tests caught urllib3 issues last time). The two pin entries on lines 50 and 54 of `pyproject.toml` are the visible footgun. + +Either way, add a smoke test that imports `SeleniumLibrary` after a fresh resolve to keep the regression caught. + +### #3610 - `run_tests` crashes when `ApexTestResult.MethodName` is null + +**Verdict**: `NOT-REPRODUCED-on-dev` → `closed:fixed-by-pr-#3681`. + +**Code scan**: + +- The original issue points at `cumulusci/tasks/apex/testrunner.py` L417 (sort that explodes on `None`) and L346-348 (dict keyed by `MethodName`). On `origin/dev` today the same module's `_process_test_results` (lines 491-510) explicitly handles `None`: + +```500:510:cumulusci/tasks/apex/testrunner.py + if None in method_names: + class_id = self.classes_by_name[class_name] + self.retry_details.setdefault(class_id, []).append( + self._get_test_methods_for_class(class_name) + ) + del self.results_by_class_name[class_name][None] + self.logger.info( + f"Retrying class with id: {class_id} name:{class_name} due to `None` methodname" + ) + self.counts["Retriable"] += len(self.retry_details[class_id]) + self._attempt_retries() +``` + +- This landed in PR #3681 / commit `84389d998b4783ddd2ff062f486a2366709cac27` (“Handling exception when the Tooling API returns a test result with a null method name”), with regression coverage in `cumulusci/tasks/apex/tests/test_apex_tasks.py::test_run_task_None_methodname_fail` and `…_pass`. Both pass on this worktree (`uv run pytest cumulusci/tasks/apex/tests/test_apex_tasks.py -k None_methodname -v` → `2 passed`). + +No repro test added - the fix is in place and already has direct unit coverage. Closing the issue is a paperwork action. + +**Proposed fix sketch**: none required. Recommend closing #3610 with a comment that points at PR #3681 and the two `None_methodname` tests. diff --git a/docs/triage/v5/themes.md b/docs/triage/v5/themes.md new file mode 100644 index 0000000000..bfff0de43d --- /dev/null +++ b/docs/triage/v5/themes.md @@ -0,0 +1,530 @@ +# CCI Open-Issue Themes and Duplicate Clusters + +Generated by the reproducibility pass. Read-only analysis over the 142-issue snapshot +captured by Task 1. The current proposed-pass1 decisions live in +`proposals.md`; this document surfaces information the controller and +user can use to refine those decisions before Phase 2 execution. + +## How to read this document + +- A "theme" is a topical bucket: a coherent group of issues that share + a subject area, even if they are not duplicates of each other. A + theme may overlap with other themes; an issue can appear in multiple. +- A "duplicate cluster" is a set of two or more issues that report the + same underlying problem or request the same feature. Each cluster has + a "canonical" issue (the one to keep open if any) and one or more + "subsumed" issues (the ones to close as `duplicate-of-#NNNN` rather + than `stale-24mo`, `pre-v4.0.0`, or `missing-fields`). +- A "tranche" is a release-and-effort grouping: a recommendation for + how the canonical surviving issues should be sequenced if the user + wants to act on them in v5. + +## Theme summary + +| theme | issues | dup clusters | net unique signals | suggested-v5 | +| -------------------- | -----: | -----------: | -----------------: | ------------ | +| packaging | 34 | 0 | 34 | yes | +| metadata-etl | 28 | 1 | 27 | yes | +| cli | 37 | 0 | 37 | maybe | +| bulkdata | 23 | 0 | 23 | yes | +| dependencies | 8 | 0 | 8 | yes | +| robotframework | 6 | 0 | 6 | maybe | +| scratch-org-config | 6 | 0 | 6 | maybe | +| ci-integration | 5 | 0 | 5 | maybe | +| auth | 5 | 0 | 5 | yes | +| keychain | 5 | 0 | 5 | no | +| docs | 4 | 0 | 4 | no | +| python-modernization | 3 | 0 | 3 | yes | + +Note: "issues" counts every issue that mentions theme-defining keywords +strongly enough; an issue can appear in multiple theme rows because +primary and secondary theme membership are both included. The total of +the issues column therefore exceeds 142. The "net unique signals" +column subtracts only the issues subsumed by clusters anchored in this +theme. + +## Cross-theme duplicate cluster index + +Issues that duplicate across themes (rare but important). + +None. The single confirmed duplicate cluster (Cluster A) is fully +contained in the metadata-etl theme. No cluster has 5+ members. + +## Themes + +### Theme: packaging + +Description: Issues touching 1GP and 2GP package creation, version management, install and uninstall flows, beta promotion, and community creation. + +Issues (34): + +#2973, #2979, #3089, #3347, #3418, #3429, #3440, #3441, #3446, #3466, #3512, #3542, #3563, #3587, #3592, #3593, #3600, #3605, #3721, #3734, #3745, #3746, #3758, #3763, #3781, #3796, #3810, #3884, #3885, #3889, #3899, #3902, #3919, #3929 + +Singletons in theme (no dup): + +- #3899: Getting following Exception in task deploy_packaging.unschedule_apex; current proposed-pass1: `kept-open`. +- #3902: Task install_managed security_type not being respected with 04t ID; current proposed-pass1: `kept-open`. +- #3929: cci task run create_community (and sf community create) Appears to Loop/Timeout ; current proposed-pass1: `kept-open`. +- #3466: Ability to specify a test suite to run instead of just test_name_match in the ru; current proposed-pass1: `closed:missing-fields`. +- #3884: Running a Dev_Org flow goes through re-install of the the same package version a; current proposed-pass1: `closed:missing-fields`. +- #3889: Uninstall 2GP task request; current proposed-pass1: `closed:missing-fields`. +- #2973: cumulusci.tasks.salesforce.CreateCommunity has no option to set authentication m; current proposed-pass1: `closed:pre-v4.0.0`. +- #3089: task run_tests not including namespace in query; current proposed-pass1: `closed:pre-v4.0.0`. +- #3512: `namespace` does not display when running `cci org info` on a packaging org (doe; current proposed-pass1: `closed:pre-v4.0.0`. +- #3563: Unmanaged Metadata not deploying during 2GP Package Create; current proposed-pass1: `closed:pre-v4.0.0`. +- #3592: release_unlocked_beta doesn't work when an install key is specified; current proposed-pass1: `closed:pre-v4.0.0`. +- #3763: Namespace not being added into Flow references in minority of cases; current proposed-pass1: `closed:pre-v4.0.0`. +- #3781: Package Versions Starting with 0 (0.1,0.2,0.3, etc.) Cause "does not exist" erro; current proposed-pass1: `closed:pre-v4.0.0`. +- #3796: cci task run get_installed_packages/uninstall_managed do not see 2GP beta; current proposed-pass1: `closed:pre-v4.0.0`. +- #3810: Github tasks fail when using github enterprise; current proposed-pass1: `closed:pre-v4.0.0`. +- #3885: Versioning in CumulusCI Releases Does Not Support Full Versioning with Build Num; current proposed-pass1: `closed:pre-v4.0.0`. +- #3919: uninstall_packaged_incremental was purged even though it was set to "not"; current proposed-pass1: `closed:pre-v4.0.0`. +- #2979: deploy task should deploy from the default entry in packageDirectories in sfdx-p; current proposed-pass1: `closed:stale-24mo`. +- #3347: Cannot release an unlocked beta package with release_unlocked_beta flow; current proposed-pass1: `closed:stale-24mo`. +- #3418: Error creating 1gp release; current proposed-pass1: `closed:stale-24mo`. +- #3429: Support overriding `cumulusci.yml` to be used for configuration; current proposed-pass1: `closed:stale-24mo`. +- #3440: Enhance default_package_path to serve multi package projects better; current proposed-pass1: `closed:stale-24mo`. +- #3441: "cci task run create_package_version" should allow option "version_base: default; current proposed-pass1: `closed:stale-24mo`. +- #3446: CCI task "push_qa" crashes for Unlocked package with no namespace; current proposed-pass1: `closed:stale-24mo`. +- #3542: 2GP flows fail in the local environment to test: `ci_feature_2gp` or `qa_org_2gp; current proposed-pass1: `closed:stale-24mo`. +- #3587: Warning message when attempting to set `--install_class` or `--uninstall_class` ; current proposed-pass1: `closed:stale-24mo`. +- #3593: `dx` task doesn't work for some commands like `project convert source`; current proposed-pass1: `closed:stale-24mo`. +- #3600: Allow install_managed to use environment variables; current proposed-pass1: `closed:stale-24mo`. +- #3605: Ability to Increment Major Versions when running `upload_production`; current proposed-pass1: `closed:stale-24mo`. +- #3721: create_package_version version_name default should be version number, not "Relea; current proposed-pass1: `closed:stale-24mo`. +- #3734: Exception in cci task upload_production - Malformed Request Error: The version n; current proposed-pass1: `closed:stale-24mo`. +- #3745: cci flow run ci_beta AND cci task install_managed_beta DO not use the latest bet; current proposed-pass1: `closed:stale-24mo`. +- #3746: Deleted Versions used for determining next version; current proposed-pass1: `closed:stale-24mo`. +- #3758: Flow "push_upgrade_org" is incorrectly defined; current proposed-pass1: `closed:stale-24mo`. + +Recommendation: + +- Leave all other issues in this theme on their current proposed-pass1 plan unless flagged elsewhere in this document. + +### Theme: metadata-etl + +Description: Issues in the metadata ETL task family: update_admin_profile, update_package_xml, find_replace, retrieve and deploy variants, and individual ETL transforms. + +Issues (28): + +#808, #2440, #2826, #3100, #3137, #3165, #3167, #3320, #3331, #3518, #3543, #3544, #3561, #3585, #3589, #3593, #3613, #3692, #3762, #3770, #3771, #3773, #3919, #3931, #3938, #3939, #3951, #3953 + +Duplicate clusters within theme: + +- Cluster A: canonical #3544 (last activity: 2025-05-07; comments: 4). Subsumed: #3762 (current proposed-pass1 was `closed:stale-24mo`; recommend converting to `closed:duplicate-of-#3544`; last activity 2024-03-06). + Shared signal: update_admin_profile cannot reconcile the Admin profile when a namespaced scratch org has Person Accounts enabled. + Rationale: Both report the same root cause: running update_admin_profile against a namespaced scratch org with the PersonAccounts feature enabled triggers the same MDAPI failure (no RecordType named Account.Business_Account in the retrieved Admin profile). Reproduction steps converge on the same cumulusci.yml + scratch org config combination. #3544 is more recent (2025-05-07), has 4 comments, and includes a linked external thread; #3762 (2024-03-06, 1 comment) repeats the same diagnosis. + +Singletons in theme (no dup): + +- #3931: Specifying a profile in cumulusci.tasks.salesforce.ProfileGrantAllAccess results; current proposed-pass1: `kept-open`. +- #3938: Rest_Deploy ignores errors; current proposed-pass1: `kept-open`. +- #3939: Deploy task can't parse SOAP Response; current proposed-pass1: `kept-open`. +- #3951: set_duplicate_rule_status broken; current proposed-pass1: `kept-open`. +- #3953: add_picklist_entries never works through CLI; current proposed-pass1: `kept-open`. +- #3137: cci task run update_package_xml and Salesforce Case Custom Object; current proposed-pass1: `closed:missing-fields`. +- #3320: Metadata ETL task to Deactivate a Flow; current proposed-pass1: `closed:missing-fields`. +- #2440: add_permission_set_perms throws 'string indices must be integers' error; current proposed-pass1: `closed:pre-v4.0.0`. +- #3100: uninstall_post is not substituting %%%NAMESPACE%%%; current proposed-pass1: `closed:pre-v4.0.0`. +- #3589: "Workflow" and "MatchingRules" are removed from package.xml after running "updat; current proposed-pass1: `closed:pre-v4.0.0`. +- #3770: Error during cci task run retrieve_changes; current proposed-pass1: `closed:pre-v4.0.0`. +- #3919: uninstall_packaged_incremental was purged even though it was set to "not"; current proposed-pass1: `closed:pre-v4.0.0`. +- #808: deploy_packaging flow runs uninstall_packaged_incremental with wrong package nam; current proposed-pass1: `closed:stale-24mo`. +- #2826: The deploy_unmanaged flow is supposed to silently do nothing if there's not actu; current proposed-pass1: `closed:stale-24mo`. +- #3165: Update Admin Profile task fails when specifying records types without custom pac; current proposed-pass1: `closed:stale-24mo`. +- #3167: Add ability to define page layout assignments with record types using the update; current proposed-pass1: `closed:stale-24mo`. +- #3331: Task update_package_xml does not write correct package.xml for AssignmentRules; current proposed-pass1: `closed:stale-24mo`. +- #3518: Task add_picklist_entries always sets a default value for record types; current proposed-pass1: `closed:stale-24mo`. +- #3543: New Option `load_sfdx_project_paths` for dx_convert_from Task ; current proposed-pass1: `closed:stale-24mo`. +- #3561: Retrieve_unpackaged unusable in MetaDeploy; current proposed-pass1: `closed:stale-24mo`. +- #3585: Error Occurs when Using `update_package_xml` on object with `xsi:nil="true"`; current proposed-pass1: `closed:stale-24mo`. +- #3593: `dx` task doesn't work for some commands like `project convert source`; current proposed-pass1: `closed:stale-24mo`. +- #3613: AddFieldsToPageLayout; current proposed-pass1: `closed:stale-24mo`. +- #3692: No parser configuration found for subdirectory digitalExperiences; current proposed-pass1: `closed:stale-24mo`. +- #3771: find_replace transforms on XPath with predicates does not work; current proposed-pass1: `closed:stale-24mo`. +- #3773: retrieve_profile task seems to be missing some Metadata; current proposed-pass1: `closed:stale-24mo`. + +Recommendation: + +- Promote canonical #3544 to v5-candidate:maybe (and hold from current closure plan). +- Convert these to `closed:duplicate-of-#3544`: #3762. +- Hold the canonical #3544 from current Pass-1 closure (was `closed:pre-v4.0.0`). +- Leave all other issues in this theme on their current proposed-pass1 plan unless flagged elsewhere in this document. + +### Theme: cli + +Description: Issues about CLI mechanics: argument validation, command output, prompts, the VSCode extension, run-time behavior of cci task and cci flow, and flow definition control (when clauses, ci_master rename). + +Issues (37): + +#733, #1348, #1350, #1432, #2140, #2402, #2507, #2697, #3015, #3024, #3089, #3134, #3161, #3307, #3347, #3470, #3479, #3485, #3492, #3506, #3549, #3570, #3585, #3607, #3609, #3612, #3618, #3663, #3733, #3754, #3770, #3784, #3852, #3854, #3899, #3929, #3953 + +Singletons in theme (no dup): + +- #3852: CumulusCI 4 refresh token error; current proposed-pass1: `kept-open`. +- #3854: Issue while Capturing Data; current proposed-pass1: `kept-open`. +- #3899: Getting following Exception in task deploy_packaging.unschedule_apex; current proposed-pass1: `kept-open`. +- #3929: cci task run create_community (and sf community create) Appears to Loop/Timeout ; current proposed-pass1: `kept-open`. +- #3953: add_picklist_entries never works through CLI; current proposed-pass1: `kept-open`. +- #2402: Create a --rebuild-org parameter for cci flow run; current proposed-pass1: `closed:missing-fields`. +- #3015: Request to have the ability to remove an imported dx org from cci org list witho; current proposed-pass1: `closed:missing-fields`. +- #3307: Project Template Support for initialisation; current proposed-pass1: `closed:missing-fields`. +- #3754: Enable configuration for cci version update sources; current proposed-pass1: `closed:missing-fields`. +- #3089: task run_tests not including namespace in query; current proposed-pass1: `closed:pre-v4.0.0`. +- #3134: Running flow always needs a default org or org defined; current proposed-pass1: `closed:pre-v4.0.0`. +- #3733: `cci org remove` doesn't remove org; current proposed-pass1: `closed:pre-v4.0.0`. +- #3770: Error during cci task run retrieve_changes; current proposed-pass1: `closed:pre-v4.0.0`. +- #3784: Can't run test suites if project**test**name_match is defined; current proposed-pass1: `closed:pre-v4.0.0`. +- #733: Prompt to delete scratch org when creating one that already exists; current proposed-pass1: `closed:stale-24mo`. +- #1348: Multiple Git Provider Support; current proposed-pass1: `closed:stale-24mo`. +- #1350: Unable to run tasks in remote projects; current proposed-pass1: `closed:stale-24mo`. +- #1432: CCI Inconsistencies in validating arguments; current proposed-pass1: `closed:stale-24mo`. +- #2140: Prompt Org Configs when Org Does Not Exist and Command Runs Against It; current proposed-pass1: `closed:stale-24mo`. +- #2507: Undo mode for CumulusCI Insert; current proposed-pass1: `closed:stale-24mo`. +- #2697: Initialization issue with 'namespaced' field in 'cci org info' after updating fr; current proposed-pass1: `closed:stale-24mo`. +- #3024: Order of flow groups in cumulusci/cumulusci.yml reflects in the Flows box in CCI; current proposed-pass1: `closed:stale-24mo`. +- #3161: Ability to Hide Option Values When Using Task Options In a Task or Flow Run; current proposed-pass1: `closed:stale-24mo`. +- #3347: Cannot release an unlocked beta package with release_unlocked_beta flow; current proposed-pass1: `closed:stale-24mo`. +- #3470: Rename `ci_master` flow to a `ci_main` or at least create a new duplicate `ci_ma; current proposed-pass1: `closed:stale-24mo`. +- #3479: Error with "cci org import" in github action; current proposed-pass1: `closed:stale-24mo`. +- #3485: "cci task run run_tests" generates incorrect test_results.xml format output; current proposed-pass1: `closed:stale-24mo`. +- #3492: Enhance the "-o" option of "cci flow run" to accept "project\_\_custom" attribute ; current proposed-pass1: `closed:stale-24mo`. +- #3506: when clause support for flow steps which call other flows; current proposed-pass1: `closed:stale-24mo`. +- #3549: Deploy to Salesforce does not create a test output; current proposed-pass1: `closed:stale-24mo`. +- #3570: Feature Request: Flow "finally" or "error" path; current proposed-pass1: `closed:stale-24mo`. +- #3585: Error Occurs when Using `update_package_xml` on object with `xsi:nil="true"`; current proposed-pass1: `closed:stale-24mo`. +- #3607: The `retry_failures` from the task `run_tests` is not working for me. ; current proposed-pass1: `closed:stale-24mo`. +- #3609: Command 'cci task run dx --command "plugins:install sfdx-browserforce-plugin"' f; current proposed-pass1: `closed:stale-24mo`. +- #3612: Maintain the CumulusCI for VSCode Extension; current proposed-pass1: `closed:stale-24mo`. +- #3618: Allow for list when using deleting/removing CumulusCI orgs (`cci org remove` and; current proposed-pass1: `closed:stale-24mo`. +- #3663: When clause | Ability to pass in prior task response values; current proposed-pass1: `closed:stale-24mo`. + +Recommendation: + +- Leave all other issues in this theme on their current proposed-pass1 plan unless flagged elsewhere in this document. + +### Theme: bulkdata + +Description: Issues about extract_data, load_data, generate_and_load_from_yaml, Snowfakery integration, mapping files, dataset record-type handling, and bulk operations. + +Issues (23): + +#1769, #2013, #2096, #2325, #2505, #2506, #2508, #2930, #2951, #3045, #3203, #3283, #3349, #3353, #3360, #3376, #3649, #3699, #3700, #3701, #3768, #3886, #3936 + +Singletons in theme (no dup): + +- #3886: Required Dependencies?; current proposed-pass1: `kept-open`. +- #3936: Error: HTTPSConnectionPool(host='yoursite.scratch.my.salesforce.com', port=443):; current proposed-pass1: `kept-open`. +- #3349: Make generated dataset recordType tables unique based on table instead of sf_obj; current proposed-pass1: `closed:missing-fields`. +- #3353: Enable Snowfakery task to use recipes from other repositories; current proposed-pass1: `closed:missing-fields`. +- #2930: Exception in task load_dataset; current proposed-pass1: `closed:pre-v4.0.0`. +- #3045: create_bulk_data_permission_set errors on multiple runs; current proposed-pass1: `closed:pre-v4.0.0`. +- #3203: generate_and_load_from_yaml task record type extraction fails for namespaced cus; current proposed-pass1: `closed:pre-v4.0.0`. +- #3376: extract_dataset sql error; current proposed-pass1: `closed:pre-v4.0.0`. +- #1769: Unusual case in test_load; current proposed-pass1: `closed:stale-24mo`. +- #2013: Mapping files with steps that are not 1-1 with SObjects are unreliable for extra; current proposed-pass1: `closed:stale-24mo`. +- #2096: REST dataloads throw errors that Bulk loads do not.; current proposed-pass1: `closed:stale-24mo`. +- #2325: Task to turn off validation rules to allow data insert; current proposed-pass1: `closed:stale-24mo`. +- #2505: Filtering records to be extracted; current proposed-pass1: `closed:stale-24mo`. +- #2506: Bulk Operations should have a --debug mode which maintains logs and tempfiles; current proposed-pass1: `closed:stale-24mo`. +- #2508: Manual load retries; current proposed-pass1: `closed:stale-24mo`. +- #2951: Error in task load_dataset - Standard_price_not_defined; current proposed-pass1: `closed:stale-24mo`. +- #3283: json parser error when empty string passed for date field during upsert or updat; current proposed-pass1: `closed:stale-24mo`. +- #3360: Read Only Object Lookup for Load_Dataset; current proposed-pass1: `closed:stale-24mo`. +- #3649: Support serial loads with update_data task; current proposed-pass1: `closed:stale-24mo`. +- #3699: Sort of the data during extraction; current proposed-pass1: `closed:stale-24mo`. +- #3700: Trying to do an upsert on a master-detail child object gets an error around perm; current proposed-pass1: `closed:stale-24mo`. +- #3701: set a mapping to the id instead of it being either a number or the salesforce id; current proposed-pass1: `closed:stale-24mo`. +- #3768: Snowfakery Batch Size and Just Once; current proposed-pass1: `closed:stale-24mo`. + +Recommendation: + +- Leave all other issues in this theme on their current proposed-pass1 plan unless flagged elsewhere in this document. + +### Theme: dependencies + +Description: Issues about cumulusci.yml dependencies, dependency_pins, update_dependencies, resolution strategy, and version pinning. + +Issues (8): + +#3603, #3604, #3615, #3619, #3641, #3781, #3884, #3886 + +Singletons in theme (no dup): + +- #3886: Required Dependencies?; current proposed-pass1: `kept-open`. +- #3604: Task request: Update sfdx-project.json dependencies based off of computed cumulu; current proposed-pass1: `closed:missing-fields`. +- #3884: Running a Dev_Org flow goes through re-install of the the same package version a; current proposed-pass1: `closed:missing-fields`. +- #3641: Prevent Downgrade of dependencies; current proposed-pass1: `closed:pre-v4.0.0`. +- #3781: Package Versions Starting with 0 (0.1,0.2,0.3, etc.) Cause "does not exist" erro; current proposed-pass1: `closed:pre-v4.0.0`. +- #3603: Any issue with git results in the unhelpful "404 not found" error; current proposed-pass1: `closed:stale-24mo`. +- #3615: update_dependencies does not honor resolution strategy; current proposed-pass1: `closed:stale-24mo`. +- #3619: Dependency_pins does not honor passwords; current proposed-pass1: `closed:stale-24mo`. + +Recommendation: + +- Leave all other issues in this theme on their current proposed-pass1 plan unless flagged elsewhere in this document. + +### Theme: robotframework + +Description: Issues about the Robot Framework integration: Open Test Browser, Selenium and Playwright wrappers, browser options, and standalone library asks. Per task constraints, no internal RF code was inspected; classification is from issue text only. + +Issues (6): + +#675, #987, #3047, #3602, #3873, #3955 + +Singletons in theme (no dup): + +- #3955: Open Test Browser - SalesforcePlaywright.robot; current proposed-pass1: `kept-open`. +- #3873: Standalone Robot Framework Library for Selenium-Based Salesforce Automation (Ins; current proposed-pass1: `closed:missing-fields`. +- #3047: Open Test Browser TypeError: 'NoneType' object is not callable when using useral; current proposed-pass1: `closed:pre-v4.0.0`. +- #675: Show full traceback for Python exceptions in robot keywords; current proposed-pass1: `closed:stale-24mo`. +- #987: Last week this test worked, now I get a javascriptexception message.; current proposed-pass1: `closed:stale-24mo`. +- #3602: Need Chrome/Firefox options(browser options/capabilities) in 'Open Test Browser; current proposed-pass1: `closed:stale-24mo`. + +Recommendation: + +- Leave all other issues in this theme on their current proposed-pass1 plan unless flagged elsewhere in this document. + +### Theme: scratch-org-config + +Description: Issues about scratch org definition files, dev_org and qa_org configuration, namespaced toggles, and JSON schema for scratch_def files. + +Issues (6): + +#710, #2140, #2697, #3306, #3884, #3910 + +Singletons in theme (no dup): + +- #3910: JSON Schema incorrectly defines namespaced field as string instead of boolean fo; current proposed-pass1: `kept-open`. +- #3306: Preview Toggle for Scratch org def file; current proposed-pass1: `closed:missing-fields`. +- #3884: Running a Dev_Org flow goes through re-install of the the same package version a; current proposed-pass1: `closed:missing-fields`. +- #710: Allow disabling default scratch org configs; current proposed-pass1: `closed:stale-24mo`. +- #2140: Prompt Org Configs when Org Does Not Exist and Command Runs Against It; current proposed-pass1: `closed:stale-24mo`. +- #2697: Initialization issue with 'namespaced' field in 'cci org info' after updating fr; current proposed-pass1: `closed:stale-24mo`. + +Recommendation: + +- Leave all other issues in this theme on their current proposed-pass1 plan unless flagged elsewhere in this document. + +### Theme: ci-integration + +Description: Issues about MetaCI, GitHub Actions integration, github_automerge_feature, and merge-conflict tooling. + +Issues (5): + +#2153, #3471, #3479, #3717, #3887 + +Singletons in theme (no dup): + +- #3887: Version comparison bug: 0.10.0.1 incorrectly compared as less than 0.9.0.1; current proposed-pass1: `closed:pre-v4.0.0`. +- #2153: Add comment to original PR which tags all branch subscribers when a merge confli; current proposed-pass1: `closed:stale-24mo`. +- #3471: `Merged 0 commits into branch:` message displays when a non-Source Code change i; current proposed-pass1: `closed:stale-24mo`. +- #3479: Error with "cci org import" in github action; current proposed-pass1: `closed:stale-24mo`. +- #3717: Github automerge feature task not working when running through Github Flows; current proposed-pass1: `closed:stale-24mo`. + +Recommendation: + +- Leave all other issues in this theme on their current proposed-pass1 plan unless flagged elsewhere in this document. + +### Theme: auth + +Description: Issues about OAuth refresh, JWT, connected app handling, GitHub Enterprise auth, and the github service connection. + +Issues (5): + +#2667, #2973, #3182, #3810, #3852 + +Singletons in theme (no dup): + +- #3852: CumulusCI 4 refresh token error; current proposed-pass1: `kept-open`. +- #2667: `cci org connect` should output the name of the connected app it is using, espec; current proposed-pass1: `closed:missing-fields`. +- #2973: cumulusci.tasks.salesforce.CreateCommunity has no option to set authentication m; current proposed-pass1: `closed:pre-v4.0.0`. +- #3182: "Error: 'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte" ; current proposed-pass1: `closed:pre-v4.0.0`. +- #3810: Github tasks fail when using github enterprise; current proposed-pass1: `closed:pre-v4.0.0`. + +Recommendation: + +- Leave all other issues in this theme on their current proposed-pass1 plan unless flagged elsewhere in this document. + +### Theme: keychain + +Description: Issues about the encrypted keychain, service config, set_service typing, password_env_name handling, and SFDX alias integration. + +Issues (5): + +#2126, #3407, #3541, #3546, #3619 + +Singletons in theme (no dup): + +- #2126: Support task to encrypt all possible fields with probabilistic encryption; current proposed-pass1: `closed:missing-fields`. +- #3546: password_env_name bleeding over to next package; current proposed-pass1: `closed:pre-v4.0.0`. +- #3407: `set_service(service_config)` type signature is wrong; current proposed-pass1: `closed:stale-24mo`. +- #3541: Getting None\_\_dev as SFDX Alias ; current proposed-pass1: `closed:stale-24mo`. +- #3619: Dependency_pins does not honor passwords; current proposed-pass1: `closed:stale-24mo`. + +Recommendation: + +- Leave all other issues in this theme on their current proposed-pass1 plan unless flagged elsewhere in this document. + +### Theme: docs + +Description: Issues that ask for documentation improvements: undocumented options, project config docs, README updates. + +Issues (4): + +#773, #2500, #3464, #3471 + +Singletons in theme (no dup): + +- #2500: ignore_failure is not documented; current proposed-pass1: `closed:missing-fields`. +- #773: Document task return values and results; current proposed-pass1: `closed:stale-24mo`. +- #3464: Provide concise documentation of cumulusci.yml `project` configuration options; current proposed-pass1: `closed:stale-24mo`. +- #3471: `Merged 0 commits into branch:` message displays when a non-Source Code change i; current proposed-pass1: `closed:stale-24mo`. + +Recommendation: + +- Leave all other issues in this theme on their current proposed-pass1 plan unless flagged elsewhere in this document. + +### Theme: python-modernization + +Description: Issues caused by upstream Python ecosystem changes: urllib3, cryptography, OpenSSL, Python version compatibility. + +Issues (3): + +#3609, #3610, #3849 + +Singletons in theme (no dup): + +- #3849: Installing CumulusCI v 4 installs the latest version of urllib3 which seems to b; current proposed-pass1: `kept-open`. +- #3609: Command 'cci task run dx --command "plugins:install sfdx-browserforce-plugin"' f; current proposed-pass1: `closed:stale-24mo`. +- #3610: Error running `run_tests` task; current proposed-pass1: `closed:stale-24mo`. + +Recommendation: + +- Leave all other issues in this theme on their current proposed-pass1 plan unless flagged elsewhere in this document. + +## Tranche recommendations + +A **tranche** is a release-and-effort grouping for surviving canonical issues - a recommendation for which release window an issue's fix should target. The narrower [Tranche 1 candidate list in `README.md`](./README.md#tranche-1-candidate-list) defines the immediate fix-PR slate against `dev`; the four-tier taxonomy below is the broader recommendation for the 16-issue surviving set. + +Surviving canonical set (16 issues): the 15 issues already proposed as +`kept-open` plus #3544, promoted by Cluster A. The 126 closure-bound +issues are not assigned to a tranche; they remain on their proposed-pass1 +plan unless the user re-categorizes them. + +### Tranche 1: v5.0 must-fix (effort:small) + +#3852, #3938, #3939, #3849, #3936 + +Rationale: each issue is a recently active bug (within 12 months) in a +core workflow with a clean reproduction. + +- #3852: CumulusCI 4 refresh token error; auth, broken since v4 upgrade, + 5 comments, clear traceback in body. +- #3938: Rest_Deploy ignores errors; deploy correctness, December 2025, + identified offending code path in body. +- #3939: Deploy task can't parse SOAP Response; companion to #3938 from + the same author, separate code path (rest_deploy: False), kept as a + distinct fix because the failing parser code is different. +- #3849: urllib3 incompatibility with Robot tests after CCI v4 install; + 12 comments, well-scoped pin workaround documented. +- #3936: HTTPSConnectionPool read timeout in Snowfakery + bulkdata flow; + recent (2025-12-03), reproducible against any large dataset run. + +### Tranche 2: v5.0 nice-to-have (effort:small/medium) + +#3910, #3953, #3951, #3899, #3902, #3955, #3854, #3886, #3929 + +Rationale: well-scoped fixes or UX improvements with a clean repro and +limited blast radius. None block a major workflow on their own. + +- #3910: JSON Schema namespaced field type wrong; one-line schema fix. +- #3953: add_picklist_entries fails through CLI because entries arrive + as a string instead of a list; small parsing fix. +- #3951: set_duplicate_rule_status broken; small ETL fix. +- #3899: deploy_packaging.unschedule_apex exception in Trailhead module; + small fix, but the trail through Trailhead means user-visible. +- #3902: install_managed security_type not respected with 04t ID. +- #3955: Open Test Browser viewport.width type error in + SalesforcePlaywright; small Playwright integration fix. +- #3854: capture_sample_data total mapping operations error; bulkdata + UX with reproducible failure. +- #3886: extract_dataset "Optional dependencies missing" message; minor + installer hygiene fix. +- #3929: cci task run create_community appears to loop / timeout; + marked needs-repro in proposals.md but recent activity (2025-10-22). + +### Tranche 3: v5.x post-GA (effort:medium/large) + +#3931, #3544 + +Rationale: deeper investigation needed; affects edge configurations. + +- #3931: ProfileGrantAllAccess error when specifying a profile; + needs-investigation, deeper metadata-etl rework. +- #3544: update_admin_profile fails on namespaced + Person Accounts. + Promoted from `closed:pre-v4.0.0` by Cluster A. Root cause may sit + partially in SFDX behavior, so the fix likely requires investigation + of how CCI requests record types from the org. + +### Tranche 4: deferred or won't-fix + +Empty in this analysis. The 126 closure-bound issues remain on their +proposals.md plan; treat them as Tranche 4 if a future controller +decides to re-open any. None of the surviving canonicals belong here. + +## Dedup statistics + +- Total issues analyzed: 142 +- Duplicate clusters found: 1 +- Issues subsumed by clusters: 1 +- Net unique signals after dedup: 142 - 1 = 141 +- Cross-theme dup clusters: 0 + +## Borderline classification calls + +These are pairs that scored high on title and body similarity but were +rejected as dups because the underlying problems are distinct. They may +still benefit from joint resolution and are surfaced here for the +controller's review. + +- #3938 + #3939 (theme: metadata-etl). Same author, same day, same task + family (deploy). #3938 hits the rest_deploy=True code path; #3939 + hits the rest_deploy=False (SOAP) code path. Different parsers, kept + as separate [Tranche 1](../../docs/triage/v5/README.md#tranche-1-candidate-list) items. +- #3347 + #3441 (theme: packaging). Both touch + release_unlocked_beta + version_base. #3347 (2022-08-29) is a 1-line + bug report against the default; #3441 (2023-12-15) is a feature ask + to permit overriding the default. Same root area, different framings. +- #3546 + #3619 (theme: dependencies / keychain). Both involve install + keys. #3546 is `password_env_name` bleeding across consecutive + dependencies; #3619 is `dependency_pins` not honoring passwords at + all. Distinct code paths. +- #3015 + #3618 + #3733 (theme: cli). All touch `cci org remove`. #3015 + is a feature ask (decouple from underlying scratch org); #3618 asks + for batch removal; #3733 is a bug where the command silently fails + after the org was deleted via the sf CLI. +- #3047 + #3955 + #3602 (theme: robotframework). All involve Open Test + Browser. Each is a distinct concern (useralias param, viewport type, + browser options request). +- #3137 + #3331 + #3585 + #3589 + #3692 (theme: metadata-etl). All are + distinct bugs in update_package_xml: Case object decomposition, + AssignmentRule pluralization, xsi:nil handling, Workflow and + MatchingRules removal, digitalExperiences subdirectory parser. A + metadata_map.yml refresh might address several at once but each + reproduces independently. +- #3506 + #3663 + #3570 (theme: cli). All are flow-control feature + requests (when clauses for sub-flows; when clauses with task + response references; finally / error path). Distinct enhancements. + +## Anomalies + +- All 142 issues classified into at least one theme (no `unclassified` + residue after refining keyword rules). All issue bodies are in + English. +- No theme exceeded 8 duplicate clusters (the maximum is 1, in + metadata-etl). +- No duplicate cluster has 5+ members; the only confirmed cluster has 2. +- Per task constraints, no file under `cumulusci/robotframework/` was + read. Robot Framework theme classification used title and body text + only.