Skip to content

setup-project.py: the preflight's credential-helper [??] and its gh auth setup-git offer stay silent where an insteadOf rule sends github.com over ssh or a helper under another spelling of the github.com key answers, and store's files are the ones git expands ~ to, so with HOME unset it no longer names files git never reads (#1013) - #1022

Merged
brettheap merged 4 commits into
mainfrom
l3-1013-credential-warnings-over-ssh
Oct 20, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1013.

Follows #1008 (2d6f928, closed #992) and RV1008's review of it. Head 35da321e, one commit on origin/main dbf12e05. Only setup-project.py and tests/test_setup_project_py.py change; no flag, no --help line, no code outside those two.

What changed, by section

SHOULD-1, both warnings (Direction 1). New _git_asks_a_helper(opts), which was _github_goes_over_https before fix round 1. It runs git ls-remote --get-url -- <url>, which is local and contacts nothing. _check_credential_helper now returns before either warning when the answer starts with neither https:// nor http://. Fix round 1 added http://, because git asks a helper over http too.

  • The url is https://github.com/<org>/ with --org, and https://github.com/ without it.
  • A pushInsteadOf rule alone leaves the answer https://, so the warnings stay. A row pins that.
  • Decision: the url stops at the organisation. Every remote the run makes (https://github.com/<org>/<Project>[-spec|-code].git) starts with it, so a rule that rewrites it rewrites all three. A rule keyed below the organisation, to one repository, is not asked, and that machine still gets the [??] it gets today.
  • Decision: where git gives no answer (non-zero exit, empty output), the check keeps today's answer, HTTPS, and warns as before.

NIT-1, a URL-scoped helper (Direction 2). I chose the first option: read every key and count its helpers. _credential_helpers keeps its two --get-all reads and adds one more:

  • git config -z --get-regexp '^credential\..*\.helper$' reads every credential.<url>.helper key.
  • The helpers of each key whose url _names_github reads as possibly github.com's are counted with the other two keys'. The exact key credential.https://github.com.helper is skipped there, because the first read already counts it.
  • _names_github reads a superset of git's matching. It is pinned row by row against git's own credential fill (HELPER_URLS, 21 urls).
    • Measured, git asks the helper for: an https scheme in any case, or no scheme; the host github.com in any case, after %XX decoding; an empty host; *.com; port 443.
    • Measured, git does not ask it for: http, ssh, another host, port 8443.
    • The reader also counts a path, a user, https://* and *.com with no scheme, which git does not. So the cost of a miss is silence, never a false warning.
  • Measured: git asks every matching credential.<url>.helper, not only the best match. With store under https://github.com/ and a made-up helper under github.com, GIT_TRACE shows credential-store get run, and then the second helper gives the login.
  • -z because a url may hold a blank. .* and not .+ because [credential ""] applies to every url (measured).
  • I kept the two --get-all calls so that the recorders in tests/test_setup_project_dry_run_offers.py (config --get-all) still answer them.
  • _check_credential_helper's docstring now says which spellings of the key it reads.

NIT-5, ~ as git expands it (Direction 3). New _home_path(path) runs git -c openreposhape.storefile=<path> config --type=path --get openreposhape.storefile.

  • _store_files asks it for ~/.git-credentials, and for ~/.config/git/credentials where XDG_CONFIG_HOME is unset or empty.

  • With HOME unset, git exits 128 (failed to expand user dir), and no ~ file is named.

  • The case with no file at all (HOME unset and XDG_CONFIG_HOME unset or empty) gets a new sentence, which a row pins byte for byte. Measured, store dies unable to set up default path; use --file and fill exits 128:

    [??] git's credential helper for github.com is `store`, and with `HOME` not set git gives it no file to read, so the first push over HTTPS will fail. Fix it once: gh auth setup-git
    
  • Decision: a failure with HOME set is not the measured case (it could be a git too old for --type). _home_path returns None there, and store's files are then left alone, as a file this cannot read is.

  • Git for Windows choosing its own HOME is covered by asking git, but it was not measured here: this host has no Windows.

NIT-2 (Direction 4). _why_no_login no longer says "a file this cannot read, may answer". _holds_a_github_login now gives the reason, as measured: git's store reads a file it may not open as empty, and dies over a folder, so a push fails over either. But what such a file holds is not something this check can say, so it says nothing. The silence is kept.

NIT-3 (Direction 5). The 106-character docstring line is rewrapped. git grep "a file this cannot read, may answer" -- setup-project.py finds nothing (exit 1). No line from URL_HELPER_KEYS to the end of _check_credential_helper is longer than 79 columns (count: 0).

NIT-4 (Direction 6), and the test helpers.

  • A real-git row: GIT_CONFIG_SYSTEM holds credential.helper=store, HOME is an empty scratch folder, and the global config is scratch without the helper. It gives the [??], and silence once ~/.git-credentials holds a made-up login. RV1008's mutant R1 (--global only) fails it.
  • New _a_real_git(...): real git through the module's own run, gh answered in-process and never run.
    • Scratch GIT_CONFIG_SYSTEM and GIT_CONFIG_GLOBAL, with GIT_CONFIG_NOSYSTEM, GIT_CONFIG_COUNT and GIT_CONFIG_PARAMETERS dropped.
    • HOME and XDG_CONFIG_HOME in scratch, and the cwd in a scratch folder under GIT_CEILING_DIRECTORIES.
    • Nothing there makes a repository, so dropping command scope turns no maintenance on. No count is set, so test_no_auto_maintenance.py's scan passes.
  • _a_credential_store keeps answering the two keys itself, and runs every other git argv through _a_real_git's git, so its ~ is git's.
  • _SetsUpGit now takes the recorder.

Docs (Direction 7). I re-read README :231, :406 and :470 and the handbook's matching lines. They still read true: an ssh machine has nothing missing and is offered nothing, and none of them quotes a printed line. So AGENTS.md, docs/cli.md, the README and the handbook are unchanged. setup-project.py is not a pinned copy.

Fix round 1 (RV1022)

Committed locally as f68f4b4d, on top of 35da321e. It will be pushed once #1026 has fixed main.

  • SHOULD-1, three rows.

    • test_a_url_git_gives_no_answer_for_keeps_the_warning: --get-url answering 128 still warns. It kills RV-M4.
    • test_an_empty_helper_under_a_url_of_its_own_is_no_helper: [credential "https://github.com/"] holding only helper = gives the no-helper [??]. It kills RV-M2b.
    • RULES[this-org-in-capitals-*]: a DemoOrg/ rule with --org DemoOrg stays silent. It kills RV-M3.
  • NIT-1 (setup-project.py:1356). _names_github reads a port as git does: none, or 443 with zeros before it.

    • Measured with credential fill: :0443, :00443 and an empty : give the login; :0, :000 and :+443 do not.
    • RV1022's port.lstrip("0") in ("", "443") would also count :0, so I wrote port == "" or port.lstrip("0") == "443".
    • New HELPER_URLS rows: :0443 (True, True) and :0 (False, False).
  • NIT-2 (:1501, :1534). An http:// answer keeps both warnings, because git asks a helper over http too (measured). New rows: RULES[plain-http-*]. The docstring says the checks behind the warnings still read only github.com's https keys and logins.

  • NIT-3 (:1522). The docstring now names a known limit: --get-url reads the starting folder's .git/config, and the scaffold pushes from its own repositories. So a rule kept only there silences both warnings for pushes it does not reach.

  • NIT-B and NIT-C (RV1022d; commit 3df8c515, pushed in the merge c7adebc5 of origin/main 051176ce, so the "locally" and "will be pushed" above are as of f68f4b4d). The rule comments in tests/test_setup_project_py.py list DEMOORG_IN_CAPITALS_OVER_SSH and EVERY_ORG_OVER_HTTP and say git sends the urls "over HTTP or HTTPS"; HELPER_URLS gains ("https://github.com:000", False, False) beside :0, which git's credential fill does not treat as 443. The mutant port.lstrip("0") in ("", "443") and port != "0" fails that row alone (1 failed, 100 passed in the -k subset), and setup-project.py was restored byte for byte. At the merge: test_setup_project_py.py 340 passed, test_setup_project_dry_run_offers.py 13 passed, test_no_auto_maintenance.py 60 passed.

  • Tests at f68f4b4d, one file at a time, under the same stub gh as below:

    File Result
    test_setup_project_py.py 339 passed in 88.64s
    test_setup_project_dry_run_offers.py 13 passed in 1.36s
    test_setup_sh.py 129 passed in 60.96s
    test_readme_windows_quoting_sentence.py 44 passed in 13.29s
    test_agents_md_codes.py 47 passed in 1.55s
    test_cli_reference.py 23 passed in 24.53s
    test_update_add_codes_documented.py 6 passed in 0.39s
    test_repo_hygiene.py 100 passed in 2.17s
    test_no_auto_maintenance.py 60 passed in 6.67s

    No shell_quoted call was added (count 0).

  • Mutants, each re-made against the -k "credential or rule or helper or home or store or url" subset. All are killed:

    Mutant Killed by
    RV-M4 test_a_url_git_gives_no_answer_for_keeps_the_warning
    RV-M2b test_an_empty_helper_under_a_url_of_its_own_is_no_helper
    RV-M3 both RULES[this-org-in-capitals-*] rows
    N1a (no leading zeros) HELPER_URLS[https://github.com:0443-True-True]
    N1b (:0 counted) HELPER_URLS[https://github.com:0-False-False]
    N2 (http:// silences) both RULES[plain-http-*] rows

Left out of scope

Verify: --preflight at a real git

Run from a folder that is not the checkout, inside env -i. The stub gh answers only --version and auth status and exits 97 on everything else. GH_TOKEN=bogus, an empty GH_CONFIG_DIR, GIT_SSH_COMMAND=false, and the system config, global config and HOME all in scratch. Git 2.43.0.

main is setup-project.py blob f4a71ad7, the same at 2d6f928 and at dbf12e0. head is 35da321e. The exit code is 0 in every row.

machine main head
insteadOf to git@github.com:, no helper no-helper [??] silent
the same without the rule no-helper [??] the same line, byte for byte
that rule + store over nothing store [??] silent
store over nothing, no rule store [??] the same line, byte for byte
rule for https://github.com/demoorg/, --org demoorg [??] silent
that rule, --org other [??] [??] (unchanged)
that rule, no --org [??] [??] (unchanged)
pushInsteadOf alone [??] [??] (unchanged)
store + made-up helper under credential.https://github.com/.helper store [??] silent
HOME unset, XDG_CONFIG_HOME unset, store [??] naming <passwd home>/.git-credentials and <passwd home>/.config/git/credentials the new no-file sentence
HOME unset, XDG_CONFIG_HOME set, store [??] naming <passwd home>/.git-credentials and the XDG file [??] naming the XDG file alone

Tests

One file at a time. Each run has the stub gh first on PATH (exit 97), GH_TOKEN=bogus, an empty GH_CONFIG_DIR and EMAIL=tests@openreposhape.invalid, and runs /usr/bin/python3 -m pytest -q -p no:cacheprovider --basetemp=<scratch>/bt-<name> -o tmp_path_retention_policy=failed.

file result
tests/test_setup_project_py.py (at head) 331 passed in 116.07s
tests/test_setup_project_py.py -k "credential or rule or helper or home or store" 88 passed, 243 deselected in 6.78s
tests/test_setup_project_dry_run_offers.py (at head) 13 passed in 1.55s
tests/test_setup_sh.py 129 passed in 72.67s
tests/test_doctor_machine_row.py 8 passed in 4.36s
tests/test_readme_windows_quoting_sentence.py 44 passed in 16.50s
tests/test_agents_md_codes.py 47 passed in 1.98s
tests/test_update_add_codes_documented.py 6 passed in 0.44s
tests/test_repo_hygiene.py 100 passed in 2.44s
tests/test_cli_reference.py 23 passed in 29.51s
tests/test_no_auto_maintenance.py 60 passed in 8.71s
tests/test_setup_project_name_edges.py (drives the preflight in a subprocess; not in the brief's list) 97 passed in 17.52s
tests/test_github_name_limits.py (the same) 64 passed in 26.97s
tests/test_setup_project_py.py on the three-way tree (main + #1011 + head) 331 passed in 130.38s

The rows marked "at head", test_setup_project_name_edges.py, test_github_name_limits.py and the three-way row ran at the pushed head. The -k row ran after the rewrap below and before the rebase. The other eight ran before two later steps: a rewrap of the NO_HELPER literal in the test file, and the rebase onto dbf12e0, which touched neither file. None of those eight imports a helper this PR changed. 45 rows are new: 14 RULES rows, 5 BESIDE_STORE, 21 HELPER_URLS, 2 system-scope, 2 HOME-unset and 1 _home_path-failure.

Mutants

Each was applied alone to setup-project.py and run against the -k subset above. All are killed.

mutant killed by
M1 no _github_goes_over_https check 6 RULES rows (every-org-*, this-org-*)
M2 probe ignores --org RULES[this-org-none], [this-org-an-empty-store]
M3 probe url without the trailing slash the same two rows
M4 no credential.<url>.helper keys read 16 rows (BESIDE_STORE, HELPER_URLS)
M5 .+ for .* in URL_HELPER_KEYS HELPER_URLS[-True-True] ([credential ""])
M6 any port HELPER_URLS[https://github.com:8443-False-False]
M7 any scheme BESIDE_STORE[http://github.com/], HELPER_URLS[http://...], [ssh://...]
M8 host case kept BESIDE_STORE[https://GitHub.com], HELPER_URLS[https://GitHub.com/...]
M9 no %XX decoding HELPER_URLS[https://github%2Ecom-True-True]
M10 scoped key not skipped in the regexp read (counted twice) HELPER_URLS[https://github.com-True-True] (the helper is counted once)
M11 Python's ~ again (HOME or expanduser) both test_with_home_unset_... rows and test_a_home_git_will_not_expand_...
M12 any _home_path failure means "no file" test_a_home_git_will_not_expand_with_home_set_is_left_alone
M13 any _home_path failure means "cannot read" both test_with_home_unset_... rows
M14 no-file sentence removed test_with_home_unset_...[unset]
M15 RV1008's R1, --global on both --get-all both test_a_store_in_the_system_config_... rows
M16 None in read check removed test_a_home_git_will_not_expand_with_home_set_is_left_alone
M17 XDG_CONFIG_HOME ignored 14 rows, starting with test_a_credential_store_with_no_github_login_is_warned_about[no-file]

Merges

git merge-tree --write-tree, all clean:

Lane: openreposhape-3 (openRepoShape-3)
🤖 Generated with Claude Code

Summary by Sourcery

Make credential preflight warnings reflect Git's actual transport, helper matching, and credential-store path behavior.

Bug Fixes:

  • Prevent preflight credential warnings and gh auth setup-git offers when Git rewrites GitHub remotes to SSH or when matching URL-scoped credential helpers already provide authentication.
  • Avoid reporting credential-store paths that Git cannot expand when HOME is unset, including a dedicated warning when no store file is available.

Enhancements:

  • Recognize Git's URL-scoped credential-helper key matching across supported GitHub URL spellings and accurately account for helpers in system, global, and scoped configuration.
  • Use Git itself to resolve credential-store paths and determine whether remotes remain on HTTP(S) before issuing preflight warnings.
  • Expand coverage with real-Git tests for URL rewriting, credential-helper matching, store-file resolution, configuration scopes, and failure cases.

Tests:

  • Add comprehensive regression tests covering SSH and HTTP URL rewriting, alternate credential-helper keys, Git URL matching behavior, system configuration, unset HOME, and Git command failures.

…h auth setup-git` offer stay silent where an `insteadOf` rule sends github.com over ssh or a helper under another spelling of the github.com key answers, and `store`'s files are the ones git expands `~` to, so with `HOME` unset it no longer names files git never reads (#1013)

- `_github_goes_over_https` asks `git ls-remote --get-url` (local, no
  network) for `https://github.com/<org>/`, or `https://github.com/`
  without `--org`, and `_check_credential_helper` says nothing where the
  answer is not `https://`. A `pushInsteadOf` rule alone changes nothing.
- `_credential_helpers` also reads every `credential.<url>.helper`
  (`git config -z --get-regexp`), counting those whose url
  `_names_github` reads as possibly github.com's, a superset of git's
  matching, pinned against `git credential fill` row by row.
- `_store_files` asks git for each `~/` path (`_home_path`, `git -c
  <k>=~/... config --type=path`): with `HOME` unset git expands none,
  and the `[??]` says `store` has no file to read.
- NIT-2 docstrings reworded, NIT-3 line rewrapped, NIT-4 real-git rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 19, 2026

Copy link
Copy Markdown

Reviewer's Guide

Updates preflight credential diagnostics to reflect Git’s effective transport and credential resolution: SSH-rewritten GitHub remotes no longer trigger HTTPS warnings, URL-scoped helpers are detected across Git-compatible spellings, and store paths are expanded exactly as Git expands them, with extensive isolated real-Git regression coverage.

Sequence diagram for GitHub credential preflight transport detection

sequenceDiagram
    participant Preflight
    participant Git
    participant CredentialCheck
    Preflight->>Git: ls-remote --get-url -- https://github.com/<org>/
    Git-->>Preflight: Effective URL
    alt Effective URL is not HTTPS
        Preflight->>CredentialCheck: return without warning or setup-git offer
    else Git fails or returns no URL
        Preflight->>CredentialCheck: continue with HTTPS warning behavior
    else Effective URL starts with https://
        Preflight->>CredentialCheck: check credential helpers
    end
Loading

Sequence diagram for Git credential helper and store-file resolution

sequenceDiagram
    participant Preflight
    participant Git
    participant CredentialConfig
    participant StoreFiles
    Preflight->>Git: config --get-all credential.https://github.com.helper
    Preflight->>Git: config --get-all credential.helper
    Preflight->>Git: config -z --get-regexp ^credential\..*\.helper$
    Git-->>CredentialConfig: Matching helper entries
    CredentialConfig->>StoreFiles: _store_files(store)
    StoreFiles->>Git: config --type=path --get openreposhape.storefile
    Git-->>StoreFiles: Git-expanded credential path or failure
    StoreFiles-->>Preflight: Files Git can read
    Preflight->>Preflight: _holds_a_github_login(path)
Loading

Flow diagram for HTTPS credential warning decision

flowchart TD
    A[_check_credential_helper] --> B{local_mode?}
    B -->|yes| Z[Return silently]
    B -->|no| C[_github_goes_over_https]
    C --> D{Effective URL starts with https://?}
    D -->|no| Z
    D -->|yes or Git gives no answer| E[_why_no_login]
    E --> F{Credential login detected?}
    F -->|yes| Z
    F -->|no| G[Warn and offer gh auth setup-git]
Loading

File-Level Changes

Change Details Files
Suppress credential warnings and the gh auth setup-git offer when the run's GitHub remotes are rewritten away from HTTPS.
  • Probe the effective URL with git ls-remote --get-url using the selected organization scope.
  • Preserve warnings for HTTPS, failed, empty, organization-mismatched, and pushInsteadOf cases.
  • Add real-Git coverage for URL rewrite behavior and warning/offer preservation.
setup-project.py
tests/test_setup_project_py.py
Recognize URL-scoped credential helpers using Git-compatible matching semantics.
  • Read all credential.<url>.helper keys with a NUL-delimited regexp query.
  • Match schemes, hosts, ports, escapes, wildcards, empty URLs, and case variants conservatively; avoid double-counting GitHub’s exact scoped key.
  • Add coverage against measured git credential fill behavior and helper spellings.
setup-project.py
tests/test_setup_project_py.py
Resolve store credential paths through Git instead of Python’s home-directory expansion.
  • Use Git config --type=path to expand ~ and omit paths Git cannot expand when HOME is unset.
  • Retain XDG-configured paths and leave paths unresolved when Git expansion fails with HOME set.
  • Report a dedicated no-file warning when store has no readable default path, and update unreadable-file reasoning.
setup-project.py
tests/test_setup_project_py.py
Strengthen integration test infrastructure to exercise real Git configuration scopes and path behavior.
  • Run unmatched Git commands against real Git with isolated system/global configuration, environment, home, and working directories.
  • Verify system-scoped helpers, credential files, setup-git recorder behavior, and HOME/XDG edge cases.
  • Add regression coverage for all rewrite, helper-key, and store-path cases.
tests/test_setup_project_py.py

Assessment against linked issues

Issue Objective Addressed Explanation
#992 Make the credential-helper check accurately distinguish a configured helper from a stored GitHub login, and warn before scaffolding when store has no usable GitHub login. ✅
#992 Determine store credential files using Git's path expansion, and avoid claiming that a file contains no login when the check cannot reliably determine what Git will read. ✅
#1013 Keep both credential-helper warnings and the gh auth setup-git offer silent when Git rewrites the run’s GitHub URL away from HTTPS through an insteadOf rule, while retaining the warnings when the run’s URL remains HTTPS. ✅
#1013 Account for URL-scoped credential-helper keys that Git may use, so a helper that can answer for GitHub prevents an incorrect store warning. ✅
#1013 Determine store’s ~ file paths using Git’s own path expansion, and update the related diagnostic wording and tests, including the case where Git expands no home-relative file. ✅

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-3, 2026-10-20T00:33:55Z) RV1022 review of #1022 (closes #1013) at head 35da321: READY. No MUST. One SHOULD (does not block), four NITs.

Measured at main e076e3e and the head, git 2.43.0. --preflight ran from a scratch folder inside env -i, with a stub gh that answers only --version and auth status. GH_TOKEN=bogus, empty GH_CONFIG_DIR, GIT_SSH_COMMAND=false, all config and HOME in scratch. Each row was also checked against git's own ls-remote --get-url and credential fill. Exit 0 at both in every row; the only output lines that differ are the credential lines.

  • Silent at the head, warned at main: every-org insteadOf (no helper / store / with --org); the demoorg/ rule with --org demoorg; a DemoOrg/ rule with --org DemoOrg; ssh:// targets; rules with no trailing slash; rules in the system, XDG or ~/.gitconfig file.
  • Unchanged, byte for byte: the demoorg/ rule with --org other or no --org; pushInsteadOf alone; a gitlab rule; rules keyed GitHub.com, :443 or %2E (git's prefix match does not apply them); a target carrying a token.
  • Helpers: scoped keys https://github.com/, https://GitHub.com, github.com, :443, github%2ecom, and the org path: silent at the head, and git fill gets the login. gitlab and http keys: unchanged. HOME unset: the new no-file sentence, or the XDG file alone; main named the passwd home.
  • Pty, typing no: the rule machine gets no [??] and no offer. A dry run exits 2 at both at the same remote-unreadable refusal, with steps (1)-(5) identical.

SHOULD-1 (this PR's fix round if it gets one, else a follow-up): two new branches have no row. RV-M4, setup-project.py:1520 made "returncode == 0 and (...)", survives the -k subset, the whole file (331 passed) and the dry-run offers file (13 passed). RV-M2b, :1328 counting an empty scoped value, survives the same; on [credential "https://github.com/"] helper = alone it would silence a true no-helper [??]. Add two rows: --get-url answering (128, "") still warns, and that config alone gives NO_HELPER. Optional: a DemoOrg/ rule with --org DemoOrg stays silent (RV-M3, org lowered, survives).
NIT-1: :1355. https://github.com:0443 as a scoped key: git fill gets the login, the head warns (main did too), so "True of every url git asks" is false there. Use port.lstrip("0") in ("", "443") and add a HELPER_URLS row.
NIT-2: :1500/:1520. A rule rewriting to http:// silences both warnings, but git asks helpers over http too. Accept http:// or reword the docstring.
NIT-3: :1518. A rule only in the starting folder's local .git/config silences both warnings (measured), but the scaffold pushes from its own repositories, which do not carry it. Name it as a known limit.
NIT-4: the declared tradeoff, measured: credential.https://github.com/otherorg.helper with --org demoorg, fill exits 128, the head is silent where main warned. Add it to "Left out of scope".

Tests at the head, one at a time:

Mutants: W1013's M2, M9, M14, M15 and M12 re-made, each killed by the rows the writer named. RV-M1 (no answer means ssh) killed by test_the_credential_helper_is_a_warning_not_a_refusal. RV-M2b, RV-M4 and RV-M3 survive (SHOULD-1).

Merges: clean with origin/main e076e3e, clean with #1011 f5749b7, clean three-way. No other open PR touches the two files.

CI tests: 2 failed, 17646 passed, 318 skipped. The 2 are the known main-red rows in test_family_add_gitlink_and_null_members.py (members: null and members: []). Land once #1026 fixes main and tests is green on a re-run.

Lane: openreposhape-3 (openRepoShape-3)

…elper =` and an org rule in capitals are pinned by rows, a `:0443` port reads as git reads it, a rule to `http://` keeps the `[??]` since git asks a helper there too, and the docstring names the local-config limit (#1013)

Fix round 1 for RV1022's review of #1022 (SHOULD-1 and NIT-1 to NIT-3):
- SHOULD-1: rows for `git ls-remote --get-url` failing (128), for
  `[credential "https://github.com/"]` holding only `helper =`, and for
  a `DemoOrg/` rule with `--org DemoOrg`.
- NIT-1: `_names_github` takes a port of 443 with zeros before it, and
  not 0 (measured with `credential fill`); two `HELPER_URLS` rows.
- NIT-2: `_github_goes_over_https` is `_git_asks_a_helper`, and an
  `http://` answer keeps the warnings; a `RULES` row.
- NIT-3: its docstring names the starting folder's `.git/config` as a
  known limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brett Heap and others added 2 commits October 20, 2026 01:49
…les and say git sends a url over HTTP or HTTPS, and `HELPER_URLS` pins `https://github.com:000`, which git's `credential fill` does not treat as 443 (#1013)

RV1022d's NIT-B: the comment over the rules lacked `DEMOORG_IN_CAPITALS_OVER_SSH` and `EVERY_ORG_OVER_HTTP`, and the one over `RULES` said git sends the urls over HTTPS where its third field is true for plain http too. NIT-C: an all-zeros port was pinned by no row, so the mutant `port.lstrip("0") in ("", "443") and port != "0"` survived the file; the new row kills it.

Lane: openreposhape-3 (openRepoShape-3)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rnings-over-ssh

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-3, 2026-10-20T01:54:09Z) #1022 is pushed at c7adebc5 (c7adebc5d4ab58de134feff22eb62164bb25e766): fix round 1 (f68f4b4d, below), RV1022d's NIT-B and NIT-C (3df8c515), and a clean merge of main 051176ce. The merge tree is 7256a476, the same as git merge-tree predicts. RV1022d: READY.

  • 3df8c515 touches only tests/test_setup_project_py.py (+6/-3):
    • the rule comments list DEMOORG_IN_CAPITALS_OVER_SSH and EVERY_ORG_OVER_HTTP, and say "over HTTP or HTTPS";
    • HELPER_URLS gains ("https://github.com:000", False, False). RV1022d's all-zeros mutant now fails that row alone.
  • RV1022d's NIT-A was fixed in the body earlier.
  • Re-run at the merge: test_setup_project_py 340, test_setup_project_dry_run_offers 13 and test_no_auto_maintenance 60, all passed.
  • Landing: after main's push run on 051176ce is green and this head's tests and SonarCloud are green.

W1013, fix round 1:
W1013 fix round 1 for RV1022 on #1022: committed locally as f68f4b4 on 35da321, NOT pushed. It waits for #1026 to fix main; then main is merged and pushed once.

SHOULD-1, three rows (tests/test_setup_project_py.py):

  • test_a_url_git_gives_no_answer_for_keeps_the_warning: --get-url answering (128, "") still gives the [??]. Kills RV-M4.
  • test_an_empty_helper_under_a_url_of_its_own_is_no_helper: [credential "https://github.com/"] holding only "helper =" gives the no-helper [??]. Kills RV-M2b.
  • RULES[this-org-in-capitals-]: a DemoOrg/ rule with --org DemoOrg stays silent. Kills RV-M3.
    NIT-1, setup-project.py:1356: the port check is port == "" or port.lstrip("0") == "443". Measured with credential fill: :0443, :00443 and an empty ":" give the login; :0, :000 and :+443 do not. The suggested lstrip(...) in ("", "443") would also count :0, so I did not use it. New HELPER_URLS rows: :0443 (True, True) and :0 (False, False).
    NIT-2, :1501/:1534: _github_goes_over_https is now _git_asks_a_helper. An answer starting https:// or http:// keeps both warnings, since git asks a helper over http too (measured). The docstring says the checks behind them still read only github.com's https keys and logins. New row: RULES[plain-http-
    ].
    NIT-3, :1522: the docstring names the known limit: --get-url reads the starting folder's .git/config, and the scaffold pushes from its own repositories.
    NIT-4: the otherorg case (credential.https://github.com/otherorg.helper, --org demoorg; fill exits 128, the head is silent where main warned) is in the PR body's "Left out of scope". The body was edited with gh pr edit and read back byte for byte. Sourcery's appended summary was dropped, because it held non-ASCII quotes.

Mutants, each against the -k "credential or rule or helper or home or store or url" subset, all killed:

  • RV-M4 by test_a_url_git_gives_no_answer_for_keeps_the_warning
  • RV-M2b by test_an_empty_helper_under_a_url_of_its_own_is_no_helper
  • RV-M3 by RULES[this-org-in-capitals-none] and [this-org-in-capitals-an-empty-store]
  • N1a (no leading zeros) by HELPER_URLS[https://github.com:0443-True-True]
  • N1b (the lstrip-in form) by HELPER_URLS[https://github.com:0-False-False]
  • N2 (https:// only) by RULES[plain-http-none] and [plain-http-an-empty-store]

Tests at f68f4b4, one at a time under the stub gh:

  • test_setup_project_py.py: 339 passed in 88.64s
  • test_setup_project_dry_run_offers.py: 13 passed in 1.36s
  • test_setup_sh.py: 129 passed in 60.96s
  • test_readme_windows_quoting_sentence.py: 44 passed in 13.29s
  • test_agents_md_codes.py: 47 passed in 1.55s
  • test_cli_reference.py: 23 passed in 24.53s
  • test_update_add_codes_documented.py: 6 passed in 0.39s
  • test_repo_hygiene.py: 100 passed in 2.17s
  • test_no_auto_maintenance.py: 60 passed in 6.67s
    No shell_quoted call added (count 0).

Diff of the fix round: setup-project.py +26/-15, tests/test_setup_project_py.py +40/-4.

RV1022d, delta review:
RV1022d delta review of #1022 (closes #1013), fix round 1 at local commit f68f4b4 on 35da321 (not pushed): READY. No MUST, no SHOULD, three NITs.

Each fix against RV1022:

  • SHOULD-1: three rows added; RV-M4 and RV-M2b are now killed (below).
  • NIT-1: re-measured with git 2.43.0 credential fill: :0443, :000443 and ":" give the login; :0, :+443 and a scheme-less github.com:443 do not. The new port check matches; the scheme-less :443 over-count costs silence only, as the docstring says.
  • NIT-2: no reference to _github_goes_over_https remains; _git_asks_a_helper has one caller (setup-project.py:1586). Measured: git asks a plain credential.helper for protocol=http.
  • NIT-3: the known limit is in the docstring and reads true.
  • NIT-4: the otherorg case is in the PR body (read back with gh pr view, ASCII but the robot).

Probes: --preflight at 35da321 and f68f4b4, real git, stub gh (97 except --version and auth status), GH_TOKEN=bogus, empty GH_CONFIG_DIR, GIT_SSH_COMMAND=false, all config and HOME in scratch. Full stdout+stderr compared, rc 0 at both everywhere.

  • Same at both (0-line diff): every-org insteadOf rule (no helper / store / store with --org demoorg); a plain empty store; HOME unset with XDG unset; HOME unset with XDG set.
  • Changed only where a fix says so (one line each): a rule to http:// now gives the store [??] (was silent); a :0443 scoped helper is now silent (git fill gets the login); a :0 scoped helper still warns at both.

New NITs (none blocks):

  • NIT-A, PR body "Left out of scope": "because a key with a path applies only under credential.useHttpPath" is not what git does. Measured with useHttpPath unset: credential.https://github.com/otherorg.helper gives the login for path=otherorg/x.git and rc128 for path=demoorg/x.git. Git skips it because its path names another organisation; reword the reason.
  • NIT-B, tests/test_setup_project_py.py:1877-1878 lists the rules without the two new constants, and :1890-1892 says the third field is "over HTTPS" while plain-http is True over HTTP. Say "over HTTP or HTTPS" and list the constants.
  • NIT-C (optional): an all-zeros port such as :000 is pinned by no row. My mutant "port.lstrip("0") in ("", "443") and port != "0"" survives the whole file (339 passed) and the dry-run offers file (13 passed). Add ("https://github.com:000", False, False). It can only cost silence.

Mutants, each against the -k "credential or rule or helper or home or store or url" subset (100 selected):

  • RV-M4: killed by test_a_url_git_gives_no_answer_for_keeps_the_warning
  • RV-M2b: killed by test_an_empty_helper_under_a_url_of_its_own_is_no_helper
  • N2 (https:// only): killed by test_a_rule_sending_github_over_ssh_silences_both_warnings[plain-http-none] and [plain-http-an-empty-store]
  • OM1, port.endswith("443"): killed by test_a_credential_helper_key_is_read_as_git_reads_it[https://github.com:8443-False-False]
  • OM2, all-zeros port counted: survives (NIT-C)

Tests at f68f4b4, one at a time under the 97 stub gh:

  • test_setup_project_py.py: 339 passed in 78.10s
  • test_setup_project_dry_run_offers.py: 13 passed in 1.72s
  • test_readme_windows_quoting_sentence.py: 44 passed in 14.00s
  • test_no_auto_maintenance.py: 58 passed, 2 skipped in 8.48s; with GIT_CONFIG_GLOBAL unset, 59 passed, 1 skipped (XDG_CONFIG_HOME set) in 6.21s

Merges (git merge-tree --write-tree, exit 0, no conflicts): with origin/main e076e3e, tree d2c13b91; with #1011 f5749b7, tree 220a6241.

Skipped to save usage: the other five gate files the writer ran, the three-way merge, and SonarCloud (the commit is not pushed). #1026 is still open, so land after it fixes main and tests is green.

Lane: openreposhape-3 (openRepoShape-3)

@brettheap
brettheap marked this pull request as ready for review October 20, 2026 04:07
@sourcery-ai

sourcery-ai Bot commented Oct 20, 2026

Copy link
Copy Markdown

Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 1 day and 15 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@brettheap
brettheap merged commit ee6f553 into main Oct 20, 2026
5 checks passed
@brettheap
brettheap deleted the l3-1013-credential-warnings-over-ssh branch October 20, 2026 04:07
brettheap pushed a commit that referenced this pull request Oct 20, 2026
…lict, so #1025 is tested against main as it stands, with #983's change to validate-family.py and #1026's fix of main's two red rows (#936)

Main took twelve squashes since e076e3e: #1026, #958, #986, #1002,
#1016, #1011, #1021, #1022, #973, #975, #1015 and #983. Only #983
touches validate-family.py, in `_git_runs` and the member checks, apart
from this branch's hunks; it touches none of this branch's test files.

Lane: openreposhape-4 (openRepoShape-4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment