Skip to content

fix(bundler): normalize the recorded integration key like the canonical reader - #4541

Open
jawwad-ali wants to merge 2 commits into
github:mainfrom
jawwad-ali:fix/bundler-active-integration-clean-key
Open

jawwad-ali wants to merge 2 commits into
github:mainfrom
jawwad-ali:fix/bundler-active-integration-clean-key

Conversation

@jawwad-ali

Copy link
Copy Markdown
Contributor

Problem

active_integration carries a comment saying it matches the canonical reader in integration_state:

default_integration first, matching the canonical reader in integration_state

But that reader normalizes every value through clean_integration_key:

def clean_integration_key(key: Any) -> str | None:
    """Return a stripped integration key, or None for empty/non-string values."""
    if not isinstance(key, str) or not key.strip():
        return None
    return key.strip()

while active_integration only checked:

if isinstance(value, str) and value:
    return value

A whitespace-only key is truthy, so it was returned as a real integration — and, being non-None, it also suppressed the "Returns None when it cannot be determined" fallback the docstring promises. A padded key was returned verbatim and matches no registered integration.

Reproduction on current main (c173bf1)

recorded='copilot'      -> 'copilot'       canonical='copilot'
recorded='  copilot  '  -> '  copilot  '   canonical='copilot'    <-- DIVERGES
recorded='   '          -> '   '           canonical=None         <-- DIVERGES
recorded='\t\n'         -> '\t\n'          canonical=None         <-- DIVERGES
recorded=''             -> None            canonical=None

.specify/integration.json is a plain JSON file, so a hand-edited or externally-written value reaches this directly.

Fix

Delegate to the existing helper instead of re-implementing the check, so the two readers cannot drift apart again:

return clean_integration_key(value)

After the fix all eight shapes — including None, an int and a list — agree with the canonical reader.

Verification

  • Fail-before / pass-after: 3 new-vs-baseline failures with the source reverted to upstream/main → 51 passed with the fix.
  • Parametrized over seven recorded shapes, each asserted against the value the canonical reader produces, so the test pins agreement rather than a hand-copied expectation.
  • Import checked for cycles (bundler.lib.project → integration_state) by importing both directly.
  • uvx ruff@0.15.0 check src tests → clean

Behaviour change, disclosed: a padded key now resolves to the stripped key (which is what actually matches a registered integration), and a whitespace-only key now resolves to None — i.e. "not determinable", letting the documented fallback run instead of proceeding with an unusable key. A plain recorded key is unchanged.


Written with assistance from Claude Code. Bug found, reproduced, and verified by me on current main.

🤖 Generated with Claude Code

…al reader

`active_integration`'s own comment says it matches the canonical reader in
`integration_state`, but that reader runs every value through
`clean_integration_key` while this one only checked:

    if isinstance(value, str) and value:
        return value

A whitespace-only key is truthy, so it was returned as a real integration --
and, being non-None, it also suppressed the "not determinable" fallback the
docstring promises. A padded key was returned verbatim and matches no
registered integration.

Reproduced on main:

    recorded='copilot'      -> 'copilot'       canonical='copilot'
    recorded='  copilot  '  -> '  copilot  '   canonical='copilot'    DIVERGES
    recorded='   '          -> '   '           canonical=None         DIVERGES
    recorded='\t\n'         -> '\t\n'          canonical=None         DIVERGES

Now delegates to `clean_integration_key` rather than re-implementing the
check, so the two readers cannot drift again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jawwad-ali
jawwad-ali requested a review from mnriem as a code owner September 11, 2026 17:53
@mnriem mnriem added the triage-nice-to-have Verdict: evidence-backed fix or greenlit feature — land after review label Sep 12, 2026
@mnriem
mnriem requested a balanced review from Copilot September 18, 2026 12:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Invalid preferred fields still suppress valid fallback integration keys.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Normalizes recorded integration keys to align bundler behavior with canonical integration-state handling.

Changes:

  • Reuses clean_integration_key.
  • Adds regression tests for malformed keys.
File summaries
File Description
src/specify_cli/bundler/lib/project.py Normalizes the selected integration key.
tests/contract/test_bundle_cli.py Tests key normalization cases.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/specify_cli/bundler/lib/project.py Outdated
Addresses review feedback: normalizing only the first truthy raw field still
disagreed with `normalize_integration_state`. A raw `or` chain selects a
whitespace-only `default_integration` (truthy) and then cleans it to None,
losing the valid legacy key behind it, whereas the canonical reader does
`clean_integration_key(data.get("default_integration")) or legacy_key` and
falls through:

    {"default_integration": "   ", "integration": "copilot"}
        before -> None        canonical -> 'copilot'

Each candidate is now cleaned before selection, so `default_integration`,
`integration`, `id` and `active` all fall through correctly while precedence
is unchanged. Verified across nine shapes, all agreeing with the canonical
reader.

Mutation-verified: restoring the or-chain fails the four new mixed-field
parametrizations.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Active integration resolution still diverges from the canonical reader for installed-only modern state.

Review effort: Balanced
Findings: None

Resolved since last review (1)

@mnriem

mnriem commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator

Please resolve conflicts

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

triage-nice-to-have Verdict: evidence-backed fix or greenlit feature — land after review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants