refactor(skills)!: remove the GitLab die() helper in favor of GitLabError - #2779
refactor(skills)!: remove the GitLab die() helper in favor of GitLabError#2779Jamie Kim (jkim323) wants to merge 6 commits into
Conversation
- convert all 61 die() sites to raise GitLabError, preserving message and exit code - delete die(); main() already emits once and returns exc.exit_code - migrate 42 SystemExit test refs, splitting direct-call from main() boundary sites - add a source contract forbidding SystemExit, sys.exit, and die outside __main__ - close the last open acceptance criterion of #1555 🧹 - Generated by Copilot
Eval Execution✅ Status: Passed
No changed AI artifacts required evaluation. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2779 +/- ##
==========================================
+ Coverage 82.98% 84.26% +1.27%
==========================================
Files 183 83 -100
Lines 33787 11240 -22547
Branches 25 0 -25
==========================================
- Hits 28038 9471 -18567
+ Misses 5746 1769 -3977
+ Partials 3 0 -3
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Katrien De Graeve (katriendg)
left a comment
There was a problem hiding this comment.
Thank you. The failure-mechanism migration is consistent, the GitLab suite and repository checks pass, and the breaking API removal is disclosed. I found three medium follow-ups: tighten the AST contract so non-equality and else exits cannot bypass it; prevent provider-controlled OAuth causes from escaping redaction for direct helper consumers; and reconcile issue #1555's GitLabAPIError wording with the GitLabError base type used here before closing the issue.
Approving, as comments need to be closed before merge, and I know you do that!
| assert invocations | ||
| assert invocations[0][1] == expected | ||
|
|
||
| def test_gitlab_error_is_the_only_failure_mechanism(self) -> None: |
There was a problem hiding this comment.
The suite passes 283 tests, but the PR evidence does not report the measured line or branch coverage for this GitLab skill. The apparent coverage level of roughly 70% is too low for a security-sensitive CLI boundary that owns OAuth, credential handling, redaction, audit fail-closed behavior, and transport validation. Please run the skill suite with coverage, publish the missing-line report, and add tests for the highest-risk uncovered branches. If the current value is near 70%, please raise it materially (at least above 80%, or to the repository-defined threshold if higher) and enforce the agreed floor in CI so it cannot regress. Branch coverage for the newly changed error and exception paths would be especially useful.
🧪 - Generated by Copilot
🔒 - Generated by Copilot
Pull Request
Description
Removes the
die()helper from the GitLab skill soGitLabErrorbecomes the module's single failure mechanism, and adds a source contract so the boundary cannot drift again.GitLabErroris the module-wide typed failure base.GitLabAPIError(GitLabError)is the API-specific subclass used for transport failures; non-transport validation, configuration, and command errors remainGitLabError.This closes the last open acceptance criterion of #1555. The other seven criteria —
LOGGER, the_emitoutput sink,_emit_debug_traceback, typed API error classes, the source-contract tests, and thegitlab_tokenglobal removal — already landed in #2718 for both the Jira and GitLab skills. No Jira changes are needed.Why this is not cosmetic
die()was already redaction-safe: it routed through_emit(), so the raw-stderr leak described in the issue was fixed by #2718. The remaining problem was an internal contract violation. TheGitLabErrordocstring stated thatdie()survives only for "the argument-parsing and command-dispatch layer, where no value is promised" — yetdie()was being called from four helpers that do promise return values:_auth_headersdict[str, str]_required_oauth_client_idstrprojectstr_read_cappedbytesThat is exactly the "silent
Nonereturn" hazard the docstring warns against. The documented contract and the code disagreed, and nothing enforced the split.What changed
die()call sites nowraise GitLabError(message, exit_code), preserving each original message and exit-code constant. Sites insideexceptblocks chain withfrom exc.die()is deleted; the now-unusedNoReturnimport is dropped.GitLabError's docstring is rewritten to describe the single-mechanism contract.SystemExittest references are migrated across 7 files.raise SystemExit, nosys.exit(...), and nodiedefinition outside the__main__guard — with one negative case per predicate.Behavior
Exit codes are unchanged.
main()already caughtGitLabError, emitted once via_emit(), and returnedexc.exit_code, so no new error handling was introduced.Two deliberate consequences, both improvements:
main()boundary, removing a double-emission path.SystemExitis aBaseExceptionand previously bypassedmain()entirely. These failures now flow throughmain(), soGITLAB_DEBUG=1shows a redacted traceback for argument-validation failures that previously printed only a one-line error.Related Issue(s)
Closes #1555
Type of Change
Code & Documentation:
Other:
.ps1,.sh,.py)die()was a public module attribute, so its removal is technically breaking for any external caller importinggitlab.die. The CLI surface, exit codes, and output format are unchanged.Testing
Test migration
The migration was not a uniform substitution. Sites fell into three kinds:
pytest.raises(gitlab.GitLabError),.codeto.exit_code, and stderr assertions moved tostr(exc_info.value)because converted helpers no longer emitmain()boundarypytest.raisesremoved entirely; assertgitlab.main() == <constant>plus emitted stderrNo assertion was weakened to a bare
pytest.raises(Exception). Every migrated site that checked an exit code still checks the same constant.TestDiewas removed. Its exacterror: boomsingle-line assertion was not already covered elsewhere —test_emit_writes_exactly_one_stderr_lineexercises_emitdirectly, and no test previously reachedmain()'s handler becauseSystemExitbypassed it. A replacement assertion was added to a convertedmain()test:Validation status
npm run lint:pynpm run validate:skillsnpm run test:pynpm run test:pycould not execute locally.uv syncfails withos error 32inside the OneDrive-synced workspace andos error 396outside it, PyPI is unreachable with a TLSHandshakeFailure, and the existing skill virtualenvs hold a partially installedpytest. This predates the change and affects unrelated skills identically.Because the suite could not run, acceptance criteria were verified directly against system Python:
dieis absent from the module, the source contract rejects all three synthetic violations and accepts the guardedsys.exit(main()), and noSystemExitreference remains outside the contract's own test fixtures.Reviewers: please confirm the GitLab suite passes in CI before merging. That is the one gate not satisfied locally.
Checklist
Required Checks
Required Local Checks
npm run validate:localnpm run spell-checknpm run lint:md-linksSecurity Considerations
No new dependencies. No change to
_redact,_REDACT_KEYS,_REDACT_PATTERNS, the emit helpers,AuthContext, the OAuth modules, or the audit sink. The existing print-ownership,LOGGER.exception, and two-owner credentialed-egress contracts pass unmodified.Additional Notes
The
path:linereferences in #1555 are stale — the skills moved from.github/skills/{jira,gitlab}/{jira,gitlab}/to.github/skills/project-planning/{jira,gitlab}/and both files roughly tripled in size. Worth correcting the issue body on close.The PR validation failure on
Asset Docs Validationwas due to the shared PowerShell module installer assumingPSGallerywas already registered on the runner. The installer now self-registersPSGalleryon demand, and the fix is covered by focused tests inscripts/tests/security/Install-PSModules.Tests.ps1.