Skip to content

Commit 3025ed3

Browse files
AriESQclaude
andcommitted
Add resolved repo identity to fix --debug output
When multiple repos share the same name under different owners (e.g. user/quest and org/quest), the existing debug output only showed API paths with no way to confirm which concrete repo was targeted. Now fix --debug emits the repo's id, full_name, and owner_type immediately after fetching repo data. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3996535 commit 3025ed3

7 files changed

Lines changed: 52 additions & 2 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ API and tool quirks that affect future work. For full context on any of these, s
5252
**Key design invariants:**
5353
- CLI uses subcommands: `create`, `fix`, `scan` — all GitHub-targeting commands require `owner/repo` format
5454
- `create` validates `owner` case-insensitively against the authenticated user (UX guard for multi-account systems)
55-
- `fix` skips the owner check and instead verifies admin permissions on the target repo (supports org repos and collaborator access)
55+
- `fix` skips the owner check and instead verifies admin permissions on the target repo (supports org repos and collaborator access); `--debug` emits the resolved repo identity (id, full_name, owner_type) to help confirm the correct repo is targeted
5656
- `fix` has no secret scanning (settings-only); `create --local/--from` has automatic pre-flight scan
5757
- `create` and `fix` both prompt for confirmation before applying; `--yes`/`-y` skips the prompt for scripted/batch use
5858
- `enforce_admins = false` is intentional (owner bypass for tooling)

MANUAL_TESTING.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,33 @@ gh-safe-repo create YOUR_USERNAME/gsr-test-debug-01 --dry-run --debug
665665

666666
No tokens or credentials appear in debug output (sanitized URLs).
667667

668+
### 8.2 Debug output shows resolved repo identity (fix)
669+
670+
```bash
671+
gh-safe-repo fix YOUR_USERNAME/some-repo --dry-run --debug
672+
```
673+
674+
**Expected:** After the `GET /repos` line, a repo identity line:
675+
676+
```
677+
[debug] GET /repos/YOUR_USERNAME/some-repo
678+
[debug] repo: YOUR_USERNAME/some-repo (id=123456789, owner_type=User)
679+
```
680+
681+
For an org repo:
682+
683+
```bash
684+
gh-safe-repo fix some-org/some-repo --dry-run --debug
685+
```
686+
687+
**Expected:**
688+
689+
```
690+
[debug] repo: some-org/some-repo (id=987654321, owner_type=Organization)
691+
```
692+
693+
This helps confirm you are targeting the correct repo when multiple repos share the same name under different owners.
694+
668695
---
669696

670697
## 9. Config File Customisation
@@ -873,6 +900,8 @@ Run each test doc's full suite before considering these scripts production-ready
873900
| 7.1 --json output | Y | | | | | Y | | Y | Y |
874901
| 7.2 --json pipeable | Y | | | | | Y | | Y | Y |
875902
| 7.3 --json fix | | | | Y | | Y | | Y | Y |
903+
| 8.1 Debug API calls | Y | | | Y | | | | Y | Y |
904+
| 8.2 Debug repo identity (fix) | | | | Y | | | | Y | Y |
876905
| 9.1 Custom config | Y | | | | | | | Y | Y |
877906
| 11.1 Abort on findings | Y | | Y | | Y | | | Y | Y |
878907
| 12.1 Rulesets API | Y | | | | | | | | Y |

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ All commands that interact with GitHub require the `owner/repo` format (e.g. `my
219219
| `--dry-run` | Show settings diff without applying changes |
220220
| `--json` | Emit the plan as JSON to stdout instead of the ANSI table |
221221
| `--config [PATH]` | Path to config file; bare `--config` uses built-in defaults only |
222-
| `--debug` | Print every API call and response |
222+
| `--debug` | Print every API call and response, plus resolved repo identity (id, full name, owner type) |
223223

224224
### `scan` — Local secret scanning
225225

docs/LEARNINGS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,9 @@ Technical notes accumulated during development. Moved from CLAUDE.md to keep the
183183
## Config Consistency
184184

185185
**`SAFE_DEFAULTS` must stay in sync with `gh-safe-repo.ini.example` — enforced by `test_config_consistency.py`.** Before this test existed, several keys consumed by `security_scanner.py` (`scan_email_history`, `exclude_emails`, `warn_ai_context_files`, `scan_exclude_paths`) relied on hardcoded `fallback=` values in the code but had no entry in `SAFE_DEFAULTS`. The `actions.enabled` key was in `SAFE_DEFAULTS` but missing from the example file. The test suite catches three classes of drift: missing/extra sections or keys, value mismatches, and config reads with neither a `SAFE_DEFAULTS` entry nor an explicit `fallback=`.
186+
187+
## `fix` Non-Owner Repo Support
188+
189+
**`fix` skips the `build_context()` owner check and verifies admin permissions from the repo API response instead.** The original owner check (`actual_owner.lower() != expected_owner.lower()`) was a UX guard for multi-account systems, not a security mechanism. For `fix`, requiring ownership is too strict — users need to fix org repos and repos where they are collaborators with admin access. The `permissions.admin` field from `GET /repos/{owner}/{repo}` is the correct check because admin access is what's actually needed to modify repo settings.
190+
191+
**`fix --debug` emits the resolved repo identity (id, full_name, owner_type) after fetching repo data.** When multiple repos share the same name under different owners (e.g. `user/quest` and `org/quest`), the standard debug output (`[debug] GET /repos/{owner}/{repo}`) doesn't confirm which concrete repo was resolved. The identity line (`[debug] repo: owner/repo (id=N, owner_type=User|Organization)`) makes this immediately visible.

gh_safe_repo/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,5 +77,8 @@ cli.main()
7777
`gh api` directly from a plugin or from `cli.py`.
7878
- **Tokens are never logged.** Debug output uses sanitised URLs; `GH_TOKEN` is
7979
injected into the child-process environment, not into logged command strings.
80+
- **`fix` emits repo identity in debug mode.** After `get_repo_data()`, `fix`
81+
prints the repo's `id`, `full_name`, and `owner.type` to stderr when `--debug`
82+
is set, so users can confirm they are targeting the correct repo.
8083
- **`GET /user` and `GET /repos/{owner}/{repo}` are cached.** `GitHubClient` caches
8184
both; every plugin hits the cache instead of making a fresh HTTP call.

gh_safe_repo/commands/fix.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ def run(args):
7575
error(f"Failed to fetch repository info: {e}")
7676
sys.exit(1)
7777

78+
if args.debug:
79+
repo_id = repo_data.get("id", "unknown")
80+
full_name = repo_data.get("full_name", f"{owner}/{repo_name}")
81+
owner_type = repo_data.get("owner", {}).get("type", "unknown")
82+
print(f"[debug] repo: {full_name} (id={repo_id}, owner_type={owner_type})", file=sys.stderr)
83+
7884
# Verify the authenticated user has admin access (required to change settings)
7985
if not repo_data.get("permissions", {}).get("admin", False):
8086
error(

tests/test_cli.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@ def test_no_admin_permission_exits(self):
194194
with patch("gh_safe_repo.commands.fix.build_context") as mock_ctx:
195195
mock_client = MagicMock()
196196
mock_client.get_repo_data.return_value = {
197+
"id": 123,
198+
"full_name": "some-org/some-repo",
199+
"owner": {"login": "some-org", "type": "Organization"},
197200
"private": False,
198201
"default_branch": "main",
199202
"permissions": {"admin": False, "push": True, "pull": True},
@@ -212,6 +215,9 @@ def test_admin_permission_proceeds(self):
212215
with patch("gh_safe_repo.commands.fix.build_context") as mock_ctx:
213216
mock_client = MagicMock()
214217
mock_client.get_repo_data.return_value = {
218+
"id": 123,
219+
"full_name": "some-org/some-repo",
220+
"owner": {"login": "some-org", "type": "Organization"},
215221
"private": False,
216222
"default_branch": "main",
217223
"permissions": {"admin": True, "push": True, "pull": True},

0 commit comments

Comments
 (0)