Skip to content

DEVOPS-735 fix: sf cli not accessible in windows for clariti commands#9

Merged
dipakparmar merged 4 commits intomainfrom
fix/DEVOPS-735
Nov 12, 2025
Merged

DEVOPS-735 fix: sf cli not accessible in windows for clariti commands#9
dipakparmar merged 4 commits intomainfrom
fix/DEVOPS-735

Conversation

@dipakparmar
Copy link
Copy Markdown

@dipakparmar dipakparmar commented Nov 10, 2025

This pull request refactors the way Salesforce CLI commands are executed in cumulusci/utils/clariti.py by replacing direct subprocess calls with the sfdx helper from cumulusci.core.sfdx. This change improves error handling and streamlines command execution, making the codebase more consistent and maintainable.

Refactoring command execution

  • Replaced all direct subprocess.run calls with the sfdx helper for running Salesforce CLI commands in both checkout_org_from_pool and set_sf_alias functions, improving consistency and error handling. [1] [2] [3]

Improved error handling

  • Added handling for OSError exceptions when executing Salesforce CLI commands, providing clearer error messages in both functions. [1] [2]

Output processing updates

  • Updated output extraction to use stdout_text and stderr_text streams from the sfdx helper, ensuring output is read and stripped correctly. [1] [2]

Minor formatting and code cleanup

  • Simplified multi-line calls to _extract_string by removing unnecessary line breaks for improved readability.

Summary by CodeRabbit

  • Bug Fixes

    • Clearer, more informative error messages for CLI operations and failures, including explicit handling when the CLI is missing or cannot be executed.
  • Chores / Refactor

    • Internal command invocation and output processing reworked for more robust decoding, trimming, and summarized error reporting while preserving public interfaces.
  • Tests

    • Test suite updated to exercise the new invocation path with consistent mocked CLI outputs and adjusted assertions.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 10, 2025

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'profile', 'request_changes_workflow', 'high_level_summary', 'high_level_summary_placeholder', 'high_level_summary_in_walkthrough', 'auto_title_placeholder', 'auto_title_instructions', 'review_status', 'commit_status', 'fail_commit_status', 'collapse_walkthrough', 'changed_files_summary', 'sequence_diagrams', 'estimate_code_review_effort', 'assess_linked_issues', 'related_issues', 'related_prs', 'suggested_labels', 'auto_apply_labels', 'suggested_reviewers', 'auto_assign_reviewers', 'in_progress_fortune', 'poem', 'labeling_instructions', 'path_filters', 'path_instructions', 'abort_on_close', 'disable_cache', 'auto_review', 'finishing_touches', 'pre_merge_checks', 'tools', 'art', 'auto_reply', 'integrations', 'opt_out', 'web_search', 'code_guidelines', 'learnings', 'issues', 'jira', 'linear', 'pull_requests', 'mcp', 'docstrings', 'unit_tests'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

Replaces direct subprocess invocations with a dedicated sfdx wrapper in the Clariti utility, switches command construction to args lists, adds helpers for robust stdout/stderr extraction and decoding, introduces explicit OSError handling for CLI invocation failures, and refines non-zero-exit handling while preserving public function signatures.

Changes

Cohort / File(s) Summary
Clariti CLI integration
cumulusci/utils/clariti.py
Replace subprocess.run usage with sfdx(..., args=command_args, capture_output=True, check_return=False); add _read_process_stream and _coerce_stream_value to normalize stdout/stderr; handle OSError explicitly for missing/unexecutable CLI; refine non-zero return-code handling to raise ClaritiError with summarized or raw output depending on debug mode; inline JSON field extraction and preserve public return types.
Tests — sfdx wrapper mocking
cumulusci/tests/test_utils_clariti.py
Replace subprocess-based test stubs with a test sfdx wrapper; add _make_proc helper to simulate returncode/stdout_text/stderr_text; update tests to assert wrapper call signature (args list) and to return mocked proc results; adjust expected error messages/branches to match new wrapper behavior.

Sequence Diagram(s)

sequenceDiagram
    participant Caller as CCI Caller
    participant Clariti as clariti.py
    participant SfdxWrapper as sfdx Wrapper
    participant CLI as Salesforce CLI

    rect rgb(245,250,255)
    Note over Clariti,SfdxWrapper: New flow — args list + wrapper + normalized streams
    Caller->>Clariti: checkout_org_from_pool(...)
    Clariti->>SfdxWrapper: sfdx(command, args=command_args, capture_output=True, check_return=False)
    alt CLI executes successfully
        SfdxWrapper->>CLI: Run command
        CLI-->>SfdxWrapper: proc (stdout_text/stderr_text, returncode=0)
        SfdxWrapper-->>Clariti: proc
        Clariti->>Clariti: parse stdout_text → JSON → fields
        Clariti-->>Caller: ClaritiCheckoutResult
    else CLI missing / exec error
        SfdxWrapper-->>Clariti: raises OSError
        Clariti-->>Caller: ClaritiError (CLI not found / cannot execute)
    else non-zero return
        SfdxWrapper-->>Clariti: proc with returncode != 0
        Clariti->>Clariti: normalize streams, summarize or include raw output
        Clariti-->>Caller: ClaritiError
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~35 minutes

  • Pay extra attention to:
    • cumulusci/utils/clariti.py — OSError handling, debug vs non-debug error content, and correct JSON field extraction.
    • set_sf_alias behavior and its (bool, Optional[str]) return semantics.
    • Test updates in cumulusci/tests/test_utils_clariti.py — ensure mocked sfdx returns and assertions mirror real wrapper kwargs and fallback behavior.

Possibly related PRs

Suggested reviewers

  • jsun-clariti

Poem

🐇 I hopped through args and stdout lines,

swapped shells for tidy wrapper signs.
I catch the errors, trim the fluff,
parse JSON fields and pack them tough.
A little rabbit fixed the CLI stuff.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.74% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly references DEVOPS-735 and clearly summarizes the main fix: improving SF CLI accessibility on Windows for Clariti commands. The change is entirely focused on replacing subprocess calls with the sfdx helper to address this issue.
Linked Issues check ✅ Passed The pull request implements the coding requirements from DEVOPS-735: replacing direct subprocess.run calls with the sfdx helper from cumulusci.core.sfdx, adding OSError handling for CLI invocation failures, and improving error messages. These changes address the root cause of SF CLI not being accessible.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the SF CLI accessibility issue: refactoring clariti.py and updating corresponding tests. No unrelated modifications or scope creep detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/DEVOPS-735

📜 Recent review details

Configuration used: Repository: ClaritiSoftware/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f075441 and 8f8e885.

📒 Files selected for processing (1)
  • cumulusci/utils/clariti.py (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • cumulusci/utils/clariti.py

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Comment @coderabbitai help to get the list of available commands and usage tips.

@dipakparmar dipakparmar marked this pull request as ready for review November 11, 2025 00:52
@dipakparmar dipakparmar self-assigned this Nov 11, 2025
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: Repository: ClaritiSoftware/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 77acbd9 and 2119484.

📒 Files selected for processing (1)
  • cumulusci/utils/clariti.py (4 hunks)

Comment thread cumulusci/utils/clariti.py Outdated
Comment thread cumulusci/utils/clariti.py Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
cumulusci/tests/test_utils_clariti.py (1)

1-205: Consider adding tests for CLI not found scenario.

The PR addresses the issue where "sf CLI not accessible in windows" by adding OSError exception handling to the production code. However, I don't see tests covering the case where the Salesforce CLI is not found or not accessible (FileNotFoundError/OSError).

Consider adding a test case like:

def test_checkout_org_from_pool_handles_cli_not_found(monkeypatch):
    def fake_sfdx(command, **kwargs):
        raise FileNotFoundError("sf command not found")
    
    monkeypatch.setattr("cumulusci.utils.clariti.sfdx", fake_sfdx)
    
    with pytest.raises(ClaritiError) as exc:
        checkout_org_from_pool("Pool42")
    
    message = str(exc.value)
    assert "Salesforce CLI" in message or "sf" in message

This would verify that the OSError handling mentioned in the PR summary is working correctly. Should I generate a complete test suite for the CLI not found scenario?

📜 Review details

Configuration used: Repository: ClaritiSoftware/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2119484 and df8aa8d.

📒 Files selected for processing (1)
  • cumulusci/tests/test_utils_clariti.py (9 hunks)
🔇 Additional comments (2)
cumulusci/tests/test_utils_clariti.py (2)

17-22: Well-structured test helper.

The _make_proc helper correctly creates a mock process result with io.StringIO objects for stdout/stderr. This aligns with the production code's expectation of file-like objects with .read() methods.

Note: If the production code reads from these streams multiple times, it must call .seek(0) between reads, as StringIO position advances after each read.


47-72: LGTM: Comprehensive test coverage for checkout with alias.

The test correctly validates the command string, argument structure, and result parsing. The assertions cover all expected fields in the ClaritiCheckoutResult.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
cumulusci/utils/clariti.py (4)

122-125: Consider preserving original exception details in OSError handling.

The OSError handler provides a generic message but discards the original exception details that might help diagnose specific Windows path or permission issues.

Apply this diff to preserve exception context:

     except OSError as err:
-        raise ClaritiError("Failed to execute Salesforce CLI 'sf'.") from err
+        raise ClaritiError(f"Failed to execute Salesforce CLI 'sf': {err}") from err

156-161: Minor: Extraction calls could be slightly more concise.

While the current code is functional, the single-tuple wrapping in extraction calls is a bit verbose.

Consider this minor simplification:

-    org_id_value = _extract_string(payload, (("orgId",),), allow_missing=True)
+    org_id_value = _extract_string(payload, [["orgId"]], allow_missing=True)
     instance_url_value = _extract_string(
-        payload, (("instanceUrl",),), allow_missing=True
+        payload, [["instanceUrl"]], allow_missing=True
     )
-    org_type_value = _extract_string(payload, (("orgType",),), allow_missing=True)
-    pool_id_value = _extract_string(payload, (("poolId",),), allow_missing=True)
+    org_type_value = _extract_string(payload, [["orgType"]], allow_missing=True)
+    pool_id_value = _extract_string(payload, [["poolId"]], allow_missing=True)

This maintains the Sequence[Sequence[str]] type contract while being slightly easier to read.


189-199: LGTM with same OSError note as checkout_org_from_pool.

The sfdx integration is correct, and the exception handling pattern matches the other function. The same suggestion about preserving original exception details in the OSError handler applies here as well.

For consistency, consider including the original exception message:

     except OSError:
-        return False, "Failed to execute Salesforce CLI 'sf'."
+        return False, f"Failed to execute Salesforce CLI 'sf': {err}"

Note: You'll need to capture the exception as except OSError as err: for this change.


127-134: Extract duplicated stdout/stderr logic into a helper function.

The stdout/stderr extraction logic (lines 127-134 and 201-208) is identical in both functions. This duplication increases maintenance burden and the risk of inconsistent updates.

Consider extracting to a helper function:

def _extract_sfdx_output(proc) -> Tuple[str, str]:
    """Extract and decode stdout/stderr from an sfdx process result.
    
    :param proc: Process result from sfdx() call.
    :returns: Tuple of (stdout, stderr) as stripped strings.
    """
    stdout = getattr(proc, "stdout_text", None)
    stderr = getattr(proc, "stderr_text", None)
    if stdout is None and getattr(proc, "stdout", None) is not None:
        stdout = proc.stdout.decode() if isinstance(proc.stdout, bytes) else proc.stdout
    if stderr is None and getattr(proc, "stderr", None) is not None:
        stderr = proc.stderr.decode() if isinstance(proc.stderr, bytes) else proc.stderr
    stdout = stdout.strip() if stdout else ""
    stderr = stderr.strip() if stderr else ""
    return stdout, stderr

Then replace both occurrences with:

stdout, stderr = _extract_sfdx_output(proc)

Also applies to: 201-208

📜 Review details

Configuration used: Repository: ClaritiSoftware/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between df8aa8d and f075441.

📒 Files selected for processing (1)
  • cumulusci/utils/clariti.py (4 hunks)
🔇 Additional comments (3)
cumulusci/utils/clariti.py (3)

11-11: LGTM: Import added for the sfdx helper.

This import is necessary to replace direct subprocess calls with the sfdx wrapper, which should improve cross-platform compatibility (especially Windows).


108-121: LGTM: sfdx helper properly integrated.

The command args are correctly constructed as a list, and the sfdx() call uses appropriate parameters (capture_output=True, check_return=False) to allow custom error handling downstream.


127-134: LGTM: stdout/stderr extraction correctly addresses past review feedback.

The extraction logic properly treats stdout_text/stderr_text as strings (not streams), with appropriate fallback to stdout/stderr and handling of bytes vs. string types. This resolves the previous .read() AttributeError issue.

@dipakparmar
Copy link
Copy Markdown
Author

dipakparmar commented Nov 12, 2025

Note

Following test failures are unrelated to changes in this PR:

=========================== short test summary info ============================
FAILED cumulusci/tasks/bulkdata/tests/test_select_utils.py::test_annoy_post_process - AssertionError
FAILED cumulusci/tasks/bulkdata/tests/test_select_utils.py::test_annoy_post_process__insert_records - TypeError: 'NoneType' object is not subscriptable
FAILED > cumulusci/tasks/bulkdata/tests/test_select_utils.py::test_annoy_post_process__insert_records_with_polymorphic_fields - > TypeError: 'NoneType' object is not subscriptable
===== 3 failed, 3197 passed, 27 skipped, 50 warnings in 171.13s (0:02:51) ======
Error: Process completed with exit code 1.

@dipakparmar dipakparmar merged commit 44e613e into main Nov 12, 2025
6 of 26 checks passed
@dipakparmar dipakparmar deleted the fix/DEVOPS-735 branch November 12, 2025 22:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants