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.
Files changed (186) hide show
  1. {orbi_cli-0.7.6/src/orbi_cli.egg-info → orbi_cli-0.7.7}/PKG-INFO +1 -1
  2. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/pyproject.toml +1 -1
  3. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/__init__.py +1 -1
  4. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/failure.py +26 -7
  5. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/release.py +14 -8
  6. {orbi_cli-0.7.6 → orbi_cli-0.7.7/src/orbi_cli.egg-info}/PKG-INFO +1 -1
  7. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_bootstrap_release.py +16 -0
  8. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_failure.py +40 -0
  9. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/LICENSE +0 -0
  10. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/README.md +0 -0
  11. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/docs/licenses/sustainable-use-license.md +0 -0
  12. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/setup.cfg +0 -0
  13. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/blocked_comment.py +0 -0
  14. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/branch_reclaim.py +0 -0
  15. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/checks.py +0 -0
  16. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/claim.py +0 -0
  17. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/clarify.py +0 -0
  18. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/cli.py +0 -0
  19. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/cli_source.py +0 -0
  20. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/comment_kind.py +0 -0
  21. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/config.py +0 -0
  22. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/delivery_comment.py +0 -0
  23. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/delivery_labels.py +0 -0
  24. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/delivery_scene.py +0 -0
  25. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/engine_source.py +0 -0
  26. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/example_config.toml +0 -0
  27. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/failure_report.py +0 -0
  28. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/gh_response.py +0 -0
  29. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/git_identity.py +0 -0
  30. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/git_transport.py +0 -0
  31. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/github.py +0 -0
  32. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/gitops.py +0 -0
  33. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/human_review.py +0 -0
  34. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/journal.py +0 -0
  35. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/language.py +0 -0
  36. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/language_terms.py +0 -0
  37. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/launchd_deploy.py +0 -0
  38. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/merge_gate.py +0 -0
  39. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/merge_handoff.py +0 -0
  40. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/milestone.py +0 -0
  41. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/milestone_command.py +0 -0
  42. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/notify.py +0 -0
  43. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/pi_activity.py +0 -0
  44. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/pi_command.py +0 -0
  45. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/pi_process.py +0 -0
  46. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/pi_recovery.py +0 -0
  47. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/pi_session.py +0 -0
  48. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/pilot_setup.py +0 -0
  49. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/pilot_slots.py +0 -0
  50. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/progress.py +0 -0
  51. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/progress_display.py +0 -0
  52. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/progress_outcome.py +0 -0
  53. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/progress_summary.py +0 -0
  54. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/release_comments.py +0 -0
  55. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/release_git.py +0 -0
  56. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/release_notes.py +0 -0
  57. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/repo_config.py +0 -0
  58. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/resume_scan.py +0 -0
  59. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/review_merge.py +0 -0
  60. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/review_rounds.py +0 -0
  61. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/run_state.py +0 -0
  62. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/runner.py +0 -0
  63. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/runner_health.py +0 -0
  64. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/scene.py +0 -0
  65. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/scheduler.py +0 -0
  66. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/scheduler_units.py +0 -0
  67. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/session_command.py +0 -0
  68. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/source_pool.py +0 -0
  69. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/status_observe.py +0 -0
  70. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/suggest.py +0 -0
  71. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/systemd_deploy.py +0 -0
  72. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi/ticket_command.py +0 -0
  73. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi_cli.egg-info/SOURCES.txt +0 -0
  74. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi_cli.egg-info/dependency_links.txt +0 -0
  75. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi_cli.egg-info/entry_points.txt +0 -0
  76. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/src/orbi_cli.egg-info/top_level.txt +0 -0
  77. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_agents_md.py +0 -0
  78. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_apply_label_patch.py +0 -0
  79. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_base_branch_failure.py +0 -0
  80. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_blocked_comment.py +0 -0
  81. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_bootstrap_adapters.py +0 -0
  82. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_bootstrap_delivery.py +0 -0
  83. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_bootstrap_pi.py +0 -0
  84. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_bootstrap_preflight.py +0 -0
  85. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_bootstrap_runner.py +0 -0
  86. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_branch_reclaim.py +0 -0
  87. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_ci_failure_issue_workflow.py +0 -0
  88. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_ci_failure_triage.py +0 -0
  89. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_ci_macos_workflow.py +0 -0
  90. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_ci_workflow.py +0 -0
  91. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_clarify.py +0 -0
  92. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_claude_plugin.py +0 -0
  93. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_cli_install.py +0 -0
  94. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_cli_milestone.py +0 -0
  95. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_cli_packaging.py +0 -0
  96. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_cli_source.py +0 -0
  97. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_comment_identity.py +0 -0
  98. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_comment_readers_contract.py +0 -0
  99. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_concurrency_e2e.py +0 -0
  100. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_constitution.py +0 -0
  101. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_coverage_gate.py +0 -0
  102. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_deliver_pr_fakes.py +0 -0
  103. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_delivery_branch_delete.py +0 -0
  104. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_delivery_labels.py +0 -0
  105. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_delivery_scene.py +0 -0
  106. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docker_docs.py +0 -0
  107. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docker_entrypoint.py +0 -0
  108. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docker_image_workflow.py +0 -0
  109. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docs_i18n.py +0 -0
  110. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docs_issue_shapes.py +0 -0
  111. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docs_labels.py +0 -0
  112. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docs_mermaid.py +0 -0
  113. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docs_releases.py +0 -0
  114. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docs_single_source.py +0 -0
  115. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_docs_site.py +0 -0
  116. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_engine_source.py +0 -0
  117. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_failure_report.py +0 -0
  118. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_fix_needed_loop.py +0 -0
  119. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_gh_read_retry.py +0 -0
  120. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_gh_response.py +0 -0
  121. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_git_base_smoke.py +0 -0
  122. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_git_merge_smoke.py +0 -0
  123. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_git_network_retry.py +0 -0
  124. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_git_transport.py +0 -0
  125. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_github.py +0 -0
  126. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_github_fakes.py +0 -0
  127. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_gitops.py +0 -0
  128. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_gitops_fakes.py +0 -0
  129. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_human_review.py +0 -0
  130. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_import_graph.py +0 -0
  131. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_install_sh.py +0 -0
  132. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_issue_1367.py +0 -0
  133. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_issue_461.py +0 -0
  134. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_issue_template_config.py +0 -0
  135. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_journal.py +0 -0
  136. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_journal_events.py +0 -0
  137. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_launchd_deploy.py +0 -0
  138. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_license.py +0 -0
  139. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_message_language.py +0 -0
  140. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_milestone_command.py +0 -0
  141. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_notify.py +0 -0
  142. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_ops_e2e.py +0 -0
  143. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_orbi.py +0 -0
  144. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_orbi_dashboard.py +0 -0
  145. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_orbi_exporter.py +0 -0
  146. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_patch_ratchet.py +0 -0
  147. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_pi_activity.py +0 -0
  148. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_pi_command.py +0 -0
  149. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_pi_extensions.py +0 -0
  150. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_pi_provider_templates.py +0 -0
  151. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_pi_recovery.py +0 -0
  152. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_pi_session.py +0 -0
  153. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_pilot_setup.py +0 -0
  154. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_pilot_slots.py +0 -0
  155. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_pr_issue_link_workflow.py +0 -0
  156. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_prerequisite_check.py +0 -0
  157. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_progress.py +0 -0
  158. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_progress_wiring.py +0 -0
  159. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_prompt_contract.py +0 -0
  160. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_public_contracts.py +0 -0
  161. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_publish_workflow.py +0 -0
  162. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_release_docs_workflow.py +0 -0
  163. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_release_ticket_templates.py +0 -0
  164. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_repo_config.py +0 -0
  165. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_resume_continue_e2e.py +0 -0
  166. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_resume_e2e.py +0 -0
  167. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_resume_pr.py +0 -0
  168. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_review_merge.py +0 -0
  169. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_run_artifacts.py +0 -0
  170. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_run_id_e2e.py +0 -0
  171. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_runner_config.py +0 -0
  172. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_runner_health.py +0 -0
  173. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_runner_source_freshness.py +0 -0
  174. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_scene.py +0 -0
  175. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_scheduler.py +0 -0
  176. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_size_ratchet.py +0 -0
  177. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_src_layout.py +0 -0
  178. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_status_observe.py +0 -0
  179. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_steering_review.py +0 -0
  180. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_suggest.py +0 -0
  181. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_suite_hygiene.py +0 -0
  182. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_systemd_deploy.py +0 -0
  183. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_systemd_timer.py +0 -0
  184. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_ticket_command.py +0 -0
  185. {orbi_cli-0.7.6 → orbi_cli-0.7.7}/tests/test_user_acceptance_contract.py +0 -0
  186. {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.6
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.6"
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"
@@ -18,4 +18,4 @@ the package would shadow the installed package for every process with
18
18
  the checkout root on sys.path. The direct-execution compatibility entry
19
19
  is `python3 -m orbi.cli` (development path only).
20
20
  """
21
- __version__ = "0.7.6"
21
+ __version__ = "0.7.7"
@@ -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"|incorrect api key"
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 — a GitHub
293
- # request reports its status, and a real transport timeout IS a
294
- # `subprocess.TimeoutExpired` (Issue #1760).
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 neither reads the wording; a
314
- non-gh subprocess (Pi, `git fetch`/`git push`) has only its output.
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
- return isinstance(command, list) and command[:1] != ["gh"]
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 query (read from the
721
- response status, never from the output text): it is reported as a
722
- missing Checks:read credential permission so the blocked release is
723
- actionable. A wait budget that runs out raises
724
- `failure.ReleaseGateWaitTimeout`, which the blocked comment reports as
725
- a human decision rather than a transient failure (Issue #1760).
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
- if gh_response.status_of(error) == 403:
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.6
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