orbi-cli 0.7.6__tar.gz → 0.7.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {orbi_cli-0.7.6/src/orbi_cli.egg-info → orbi_cli-0.7.7}/PKG-INFO +1 -1
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/pyproject.toml +1 -1
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/__init__.py +1 -1
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/failure.py +26 -7
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/release.py +14 -8
- {orbi_cli-0.7.6 → orbi_cli-0.7.7/src/orbi_cli.egg-info}/PKG-INFO +1 -1
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_bootstrap_release.py +16 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_failure.py +40 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/LICENSE +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/README.md +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/docs/licenses/sustainable-use-license.md +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/setup.cfg +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/blocked_comment.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/branch_reclaim.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/checks.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/claim.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/clarify.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/cli.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/cli_source.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/comment_kind.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/config.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/delivery_comment.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/delivery_labels.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/delivery_scene.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/engine_source.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/example_config.toml +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/failure_report.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/gh_response.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/git_identity.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/git_transport.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/github.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/gitops.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/human_review.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/journal.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/language.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/language_terms.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/launchd_deploy.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/merge_gate.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/merge_handoff.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/milestone.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/milestone_command.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/notify.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/pi_activity.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/pi_command.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/pi_process.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/pi_recovery.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/pi_session.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/pilot_setup.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/pilot_slots.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/progress.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/progress_display.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/progress_outcome.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/progress_summary.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/release_comments.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/release_git.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/release_notes.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/repo_config.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/resume_scan.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/review_merge.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/review_rounds.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/run_state.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/runner.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/runner_health.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/scene.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/scheduler.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/scheduler_units.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/session_command.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/source_pool.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/status_observe.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/suggest.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/systemd_deploy.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/ticket_command.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi_cli.egg-info/SOURCES.txt +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi_cli.egg-info/dependency_links.txt +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi_cli.egg-info/entry_points.txt +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi_cli.egg-info/top_level.txt +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_agents_md.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_apply_label_patch.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_base_branch_failure.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_blocked_comment.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_bootstrap_adapters.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_bootstrap_delivery.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_bootstrap_pi.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_bootstrap_preflight.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_bootstrap_runner.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_branch_reclaim.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_ci_failure_issue_workflow.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_ci_failure_triage.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_ci_macos_workflow.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_ci_workflow.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_clarify.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_claude_plugin.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_cli_install.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_cli_milestone.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_cli_packaging.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_cli_source.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_comment_identity.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_comment_readers_contract.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_concurrency_e2e.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_constitution.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_coverage_gate.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_deliver_pr_fakes.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_delivery_branch_delete.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_delivery_labels.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_delivery_scene.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docker_docs.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docker_entrypoint.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docker_image_workflow.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docs_i18n.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docs_issue_shapes.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docs_labels.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docs_mermaid.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docs_releases.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docs_single_source.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docs_site.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_engine_source.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_failure_report.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_fix_needed_loop.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_gh_read_retry.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_gh_response.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_git_base_smoke.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_git_merge_smoke.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_git_network_retry.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_git_transport.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_github.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_github_fakes.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_gitops.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_gitops_fakes.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_human_review.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_import_graph.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_install_sh.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_issue_1367.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_issue_461.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_issue_template_config.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_journal.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_journal_events.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_launchd_deploy.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_license.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_message_language.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_milestone_command.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_notify.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_ops_e2e.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_orbi.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_orbi_dashboard.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_orbi_exporter.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_patch_ratchet.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_pi_activity.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_pi_command.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_pi_extensions.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_pi_provider_templates.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_pi_recovery.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_pi_session.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_pilot_setup.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_pilot_slots.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_pr_issue_link_workflow.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_prerequisite_check.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_progress.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_progress_wiring.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_prompt_contract.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_public_contracts.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_publish_workflow.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_release_docs_workflow.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_release_ticket_templates.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_repo_config.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_resume_continue_e2e.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_resume_e2e.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_resume_pr.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_review_merge.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_run_artifacts.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_run_id_e2e.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_runner_config.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_runner_health.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_runner_source_freshness.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_scene.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_scheduler.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_size_ratchet.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_src_layout.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_status_observe.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_steering_review.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_suggest.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_suite_hygiene.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_systemd_deploy.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_systemd_timer.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_ticket_command.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_user_acceptance_contract.py +0 -0
- {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_worktree_reclaim.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: orbi-cli
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.7
|
|
4
4
|
Summary: Orbi — local AI development worker: claims GitHub Issues, runs Pi in isolated worktrees, and delivers PRs (GitHub Issues + labels are the only state store).
|
|
5
5
|
Author: xqliu
|
|
6
6
|
License-Expression: AGPL-3.0-only OR LicenseRef-Sustainable-Use-1.0
|
|
@@ -44,7 +44,7 @@ build-backend = "setuptools.build_meta"
|
|
|
44
44
|
|
|
45
45
|
[project]
|
|
46
46
|
name = "orbi-cli"
|
|
47
|
-
version = "0.7.
|
|
47
|
+
version = "0.7.7"
|
|
48
48
|
description = "Orbi — local AI development worker: claims GitHub Issues, runs Pi in isolated worktrees, and delivers PRs (GitHub Issues + labels are the only state store)."
|
|
49
49
|
readme = "README.md"
|
|
50
50
|
requires-python = ">=3.14"
|
|
@@ -257,6 +257,11 @@ PROVIDER_AUTH_WORDS = ("unauthorized", "forbidden", "api key")
|
|
|
257
257
|
# (Issue #1465): its 401 shapes, its rejected-key sentences and
|
|
258
258
|
# `unauthorized`; `forbidden` stays in `PROVIDER_AUTH_WORDS` for
|
|
259
259
|
# `pi_process`'s own startup classifier, which is not this regex.
|
|
260
|
+
# `api key` is the provider's own missing/rejected-key sentence ("No API
|
|
261
|
+
# key found for provider …"), the marker `PROVIDER_AUTH_WORDS` still
|
|
262
|
+
# lists; only `incorrect api key` survived #1762, so the plain sentence
|
|
263
|
+
# regressed to `unclassified` (Issue #1768). An argv word cannot carry
|
|
264
|
+
# it — the space keeps it out of a branch, label or check name.
|
|
260
265
|
_CREDENTIAL_MISSING_RE = re.compile(
|
|
261
266
|
r"bad credentials"
|
|
262
267
|
r"|(?:http|status)\s+401\b"
|
|
@@ -265,7 +270,7 @@ _CREDENTIAL_MISSING_RE = re.compile(
|
|
|
265
270
|
# relabelled as a credential problem (Issue #1465).
|
|
266
271
|
r"|(?:^|[\s\"'(])401:"
|
|
267
272
|
r"|authentication (?:failed|required|fails)"
|
|
268
|
-
r"|
|
|
273
|
+
r"|api key"
|
|
269
274
|
r"|unauthorized",
|
|
270
275
|
re.IGNORECASE,
|
|
271
276
|
)
|
|
@@ -289,9 +294,11 @@ _WORKFLOW_PERMISSION_RE = re.compile(
|
|
|
289
294
|
# sentence differs by whether a delivery PR already exists.
|
|
290
295
|
_WORKFLOW_PERMISSION_CAUSE_KEY = "failure.workflow_cause"
|
|
291
296
|
# The remaining text-only transient markers: network-level failures and
|
|
292
|
-
# GitHub's own server sentences. This set names no timeout
|
|
293
|
-
#
|
|
294
|
-
# `
|
|
297
|
+
# GitHub's own server sentences. This set names no timeout: a timeout is
|
|
298
|
+
# decided by `subprocess.TimeoutExpired` or, for a source that gives no
|
|
299
|
+
# status and no type, by `_TEXT_TIMEOUT_RE` below. A GitHub request that
|
|
300
|
+
# received a response reports its status and never reads that wording; a
|
|
301
|
+
# status-less gh transport failure is the one exception (Issue #1768).
|
|
295
302
|
_GITHUB_TRANSIENT_RE = re.compile(
|
|
296
303
|
r"http 5\d\d|http 429|rate limit|"
|
|
297
304
|
r"something went wrong while executing your query|"
|
|
@@ -310,11 +317,23 @@ def _timeout_is_text_only(error: BaseException | None) -> bool:
|
|
|
310
317
|
"""Whether only the failure's text can say it was a timeout.
|
|
311
318
|
|
|
312
319
|
`subprocess.TimeoutExpired` IS a timeout (decided by type) and a
|
|
313
|
-
GitHub request reports its status, so
|
|
314
|
-
non-gh subprocess (Pi, `git
|
|
320
|
+
GitHub request that received a response reports its status, so
|
|
321
|
+
neither reads the wording; a non-gh subprocess (Pi, `git
|
|
322
|
+
fetch`/`git push`) has only its output. gh's own transport can fail
|
|
323
|
+
before any response exists — a TLS handshake timeout exits 1 with no
|
|
324
|
+
status and no `TimeoutExpired` (cli/cli#1761) — so a structured gh
|
|
325
|
+
failure that reports NO status keeps the wording as its only signal
|
|
326
|
+
(Issue #1768). A response status, when present, still wins.
|
|
315
327
|
"""
|
|
316
328
|
command = getattr(error, "cmd", None)
|
|
317
|
-
|
|
329
|
+
if not isinstance(command, list):
|
|
330
|
+
return False
|
|
331
|
+
if command[:1] != ["gh"]:
|
|
332
|
+
return True
|
|
333
|
+
return (
|
|
334
|
+
isinstance(error, gh_response.GitHubRequestFailed)
|
|
335
|
+
and error.status is None
|
|
336
|
+
)
|
|
318
337
|
|
|
319
338
|
|
|
320
339
|
# reason_code -> (action_code, retry_safe). `retry_safe` is whether a
|
|
@@ -717,12 +717,15 @@ def check_release_gates(repo: str, release_commit: str,
|
|
|
717
717
|
|
|
718
718
|
A real `gh` failure (auth, rate limit, API error) propagates
|
|
719
719
|
unchanged — a gate that cannot be checked is a failed gate. The one
|
|
720
|
-
exception is GitHub's HTTP 403 for the check-runs
|
|
721
|
-
response status, never from the output text): it
|
|
722
|
-
missing Checks:read credential permission so the
|
|
723
|
-
actionable. A
|
|
724
|
-
|
|
725
|
-
|
|
720
|
+
exception is GitHub's HTTP 403 **with quota left** for the check-runs
|
|
721
|
+
query (read from the response status, never from the output text): it
|
|
722
|
+
is reported as a missing Checks:read credential permission so the
|
|
723
|
+
blocked release is actionable. A rate-limited 403 (no quota left) is
|
|
724
|
+
a transient GitHub failure and propagates unchanged, so it requeues
|
|
725
|
+
instead of asking for a credential repair (Issue #1768). A wait budget
|
|
726
|
+
that runs out raises `failure.ReleaseGateWaitTimeout`, which the
|
|
727
|
+
blocked comment reports as a human decision rather than a transient
|
|
728
|
+
failure (Issue #1760).
|
|
726
729
|
"""
|
|
727
730
|
evidence: list[str] = []
|
|
728
731
|
open_deliveries: set[int] = set()
|
|
@@ -774,8 +777,11 @@ def check_release_gates(repo: str, release_commit: str,
|
|
|
774
777
|
except subprocess.CalledProcessError as error:
|
|
775
778
|
# The response status — not the output text — tells a missing
|
|
776
779
|
# Checks:read permission from every other read failure
|
|
777
|
-
# (Issue #1760).
|
|
778
|
-
|
|
780
|
+
# (Issue #1760). A rate-limited 403 is GitHub's own limit, not
|
|
781
|
+
# a permission: it propagates unchanged and classifies as the
|
|
782
|
+
# transient failure that requeues (Issue #1768).
|
|
783
|
+
if (gh_response.status_of(error) == 403
|
|
784
|
+
and not gh_response.transient(error)):
|
|
779
785
|
# Its own type: the status meaning travels on the type
|
|
780
786
|
# while the reader-facing sentence names the missing
|
|
781
787
|
# scope, so classification never reads the words
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: orbi-cli
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.7
|
|
4
4
|
Summary: Orbi — local AI development worker: claims GitHub Issues, runs Pi in isolated worktrees, and delivers PRs (GitHub Issues + labels are the only state store).
|
|
5
5
|
Author: xqliu
|
|
6
6
|
License-Expression: AGPL-3.0-only OR LicenseRef-Sustainable-Use-1.0
|
|
@@ -1057,6 +1057,22 @@ def test_check_release_gates_reports_missing_checks_permission(monkeypatch):
|
|
|
1057
1057
|
release.check_release_gates("o/r", "abc123", 99)
|
|
1058
1058
|
|
|
1059
1059
|
|
|
1060
|
+
def test_rate_limited_check_runs_read_stays_transient(monkeypatch):
|
|
1061
|
+
"""A rate-limited 403 on the check-runs read is GitHub's rate limit, not
|
|
1062
|
+
a missing permission: it propagates unwrapped and classifies as a
|
|
1063
|
+
transient failure that requeues (Issue #1768). The status and the
|
|
1064
|
+
`X-Ratelimit-Remaining: 0` header — not the output text — say so."""
|
|
1065
|
+
make_gate_gh(monkeypatch, check_runs_failure=(403, 0))
|
|
1066
|
+
monkeypatch.setattr(runner.time, "sleep", lambda _s: None)
|
|
1067
|
+
with pytest.raises(Exception) as raised:
|
|
1068
|
+
release.check_release_gates("o/r", "abc", 1)
|
|
1069
|
+
assert not isinstance(raised.value, failure.GitHubPermissionDenied)
|
|
1070
|
+
result = failure.classify(str(raised.value), error=raised.value)
|
|
1071
|
+
assert result.reason_code == "github_transient"
|
|
1072
|
+
assert result.action_code == "requeue"
|
|
1073
|
+
assert result.retry_safe is True
|
|
1074
|
+
|
|
1075
|
+
|
|
1060
1076
|
def test_check_release_gates_reraises_real_gh_failure(monkeypatch):
|
|
1061
1077
|
make_gate_gh(monkeypatch, check_runs=None)
|
|
1062
1078
|
with pytest.raises(subprocess.CalledProcessError):
|
|
@@ -1271,3 +1271,43 @@ def test_release_failure_comment_classifies_a_gate_wait_timeout_as_blocked():
|
|
|
1271
1271
|
assert record is not None
|
|
1272
1272
|
assert record.reason_code == "human_decision_required"
|
|
1273
1273
|
assert record.action_code == "decide"
|
|
1274
|
+
|
|
1275
|
+
|
|
1276
|
+
# --------------------------------------------------------------------------
|
|
1277
|
+
# Issue #1768: the three #1762 review regressions keep their classification
|
|
1278
|
+
# --------------------------------------------------------------------------
|
|
1279
|
+
|
|
1280
|
+
def test_pi_no_api_key_found_is_a_credential_problem():
|
|
1281
|
+
"""The provider's own missing-key sentence is `credential_missing`: the
|
|
1282
|
+
marker `PROVIDER_AUTH_WORDS` still lists was dropped from the text
|
|
1283
|
+
classifier by #1762 and comes back here (Issue #1768).
|
|
1284
|
+
|
|
1285
|
+
The marker cannot be forged by a command's argv: it contains a space,
|
|
1286
|
+
so no branch, label or check name can carry it (the #1760 concern)."""
|
|
1287
|
+
result = failure.classify(
|
|
1288
|
+
"Pi session failed: No API key found for provider deepseek")
|
|
1289
|
+
assert result.reason_code == "credential_missing"
|
|
1290
|
+
assert result.action_code == "check_credentials"
|
|
1291
|
+
assert result.retry_safe is False
|
|
1292
|
+
|
|
1293
|
+
|
|
1294
|
+
def test_gh_tls_handshake_timeout_is_transient():
|
|
1295
|
+
"""gh's own transport can fail before any response exists: a TLS
|
|
1296
|
+
handshake timeout exits 1 with no status and no `TimeoutExpired`
|
|
1297
|
+
(cli/cli#1761), so the wording is the only signal there is
|
|
1298
|
+
(Issue #1768). A response status, when present, still wins."""
|
|
1299
|
+
from orbi import gh_response
|
|
1300
|
+
|
|
1301
|
+
error = gh_response.GitHubRequestFailed(
|
|
1302
|
+
1, ["gh", "api", "repos/o/r/issues"],
|
|
1303
|
+
stderr='Post "https://api.github.com/graphql": net/http: '
|
|
1304
|
+
"TLS handshake timeout",
|
|
1305
|
+
status=None,
|
|
1306
|
+
)
|
|
1307
|
+
result = failure.classify(
|
|
1308
|
+
"gh api repos/o/r/issues failed: net/http: TLS handshake timeout",
|
|
1309
|
+
error=error,
|
|
1310
|
+
)
|
|
1311
|
+
assert result.reason_code == "github_transient"
|
|
1312
|
+
assert result.action_code == "requeue"
|
|
1313
|
+
assert result.retry_safe is True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|