brainiac-github 0.3.3 → 0.3.4
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.
- checksums.yaml +4 -4
- data/lib/brainiac/plugins/github/handler.rb +69 -14
- data/lib/brainiac/plugins/github/version.rb +1 -1
- data/lib/brainiac/plugins/github.rb +2 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 49d9d73cefe9f24ff840f683ea53a4ff2276cbd89495037765dcc65fb3370fae
|
|
4
|
+
data.tar.gz: 9de4f939b32f3f7c6f802d64e5a5280cc2de108fc771212b865bb17b8a12d21e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f3b54d02be22dbc9072aa3170a7073ef912b40f9555b5fdfde9a0cea3c4212fcf84c5e797026cdf094b1ef4e220804784a30361e64377e00470a785e6481d2e6
|
|
7
|
+
data.tar.gz: 6cc367893d43b6276e141902e21b17e48c416a84c76e989bc0d34620223ee47b52fd02ec01650684e3adde7410d39ac42e9de8eeece11a895c43aea2e7d1b414
|
|
@@ -4,6 +4,17 @@ module Brainiac
|
|
|
4
4
|
module Plugins
|
|
5
5
|
module Github
|
|
6
6
|
module Handler
|
|
7
|
+
# Marker that starts every crash-notification comment (see register_crash_handler!).
|
|
8
|
+
CRASH_COMMENT_PREFIX = "💥"
|
|
9
|
+
|
|
10
|
+
# Circuit breaker: max comment dispatches per (repo, PR, agent) within the window.
|
|
11
|
+
# Stops any comment -> dispatch -> crash -> comment feedback loop, regardless of
|
|
12
|
+
# who posted the comment or what it says.
|
|
13
|
+
COMMENT_DISPATCH_LIMIT = 4
|
|
14
|
+
COMMENT_DISPATCH_WINDOW = 120 # seconds
|
|
15
|
+
COMMENT_DISPATCH_LOCK = Mutex.new
|
|
16
|
+
COMMENT_DISPATCH_TIMES = Hash.new { |h, k| h[k] = [] }
|
|
17
|
+
|
|
7
18
|
class << self
|
|
8
19
|
def handle_pr_merged(payload)
|
|
9
20
|
pr = payload["pull_request"]
|
|
@@ -254,20 +265,8 @@ module Brainiac
|
|
|
254
265
|
comment_user_type = comment.dig("user", "type")
|
|
255
266
|
repo_name = payload.dig("repository", "full_name")
|
|
256
267
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
return [200, { status: "ignored", reason: "not a PR comment" }.to_json]
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
# If the PR was opened by a remote agent bot (not local to this machine),
|
|
263
|
-
# skip dispatch — the other machine that owns the agent will handle it.
|
|
264
|
-
pr_author = issue.dig("user", "login")
|
|
265
|
-
pr_author_type = issue.dig("user", "type")
|
|
266
|
-
unless pr_author_owned_locally?(pr_author, pr_author_type)
|
|
267
|
-
pr_number = issue["number"]
|
|
268
|
-
LOG.info "[GitHub] PR ##{pr_number} owned by remote agent (#{pr_author}) — skipping dispatch"
|
|
269
|
-
return [200, { status: "ignored", reason: "PR owned by remote agent" }.to_json]
|
|
270
|
-
end
|
|
268
|
+
rejection = issue_comment_rejection(issue, comment_body)
|
|
269
|
+
return rejection if rejection
|
|
271
270
|
|
|
272
271
|
project_result = identify_project_by_repo(repo_name)
|
|
273
272
|
unless project_result
|
|
@@ -301,6 +300,12 @@ module Brainiac
|
|
|
301
300
|
bot_ignore_reason = bot_comment_ignore_reason(comment_user, comment_user_type, agent_name, pr_number)
|
|
302
301
|
return bot_ignore_reason if bot_ignore_reason
|
|
303
302
|
|
|
303
|
+
if comment_dispatch_throttled?(repo_name, pr_number, agent_name)
|
|
304
|
+
LOG.warn "[GitHub] PR ##{pr_number}: >#{COMMENT_DISPATCH_LIMIT} dispatches for #{agent_name} in " \
|
|
305
|
+
"#{COMMENT_DISPATCH_WINDOW}s — possible loop, ignoring comment"
|
|
306
|
+
return [200, { status: "ignored", reason: "dispatch rate limit" }.to_json]
|
|
307
|
+
end
|
|
308
|
+
|
|
304
309
|
branch = fetch_pr_branch(repo_name, pr_number, agent_name, project_config)
|
|
305
310
|
result = find_work_item_by_branch(branch)
|
|
306
311
|
|
|
@@ -421,6 +426,42 @@ module Brainiac
|
|
|
421
426
|
pr_author_owned_locally?(pr_author, pr_author_type)
|
|
422
427
|
end
|
|
423
428
|
|
|
429
|
+
# Early-exit guards for issue_comment events. Returns a Rack-style response
|
|
430
|
+
# array when the comment should be ignored, or nil to continue dispatch.
|
|
431
|
+
def issue_comment_rejection(issue, comment_body)
|
|
432
|
+
unless issue["pull_request"]
|
|
433
|
+
LOG.info "Issue comment on non-PR issue ##{issue["number"]}, ignoring"
|
|
434
|
+
return [200, { status: "ignored", reason: "not a PR comment" }.to_json]
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
# If the PR was opened by a remote agent bot (not local to this machine),
|
|
438
|
+
# skip dispatch — the other machine that owns the agent will handle it.
|
|
439
|
+
pr_author = issue.dig("user", "login")
|
|
440
|
+
pr_author_type = issue.dig("user", "type")
|
|
441
|
+
unless pr_author_owned_locally?(pr_author, pr_author_type)
|
|
442
|
+
LOG.info "[GitHub] PR ##{issue["number"]} owned by remote agent (#{pr_author}) — skipping dispatch"
|
|
443
|
+
return [200, { status: "ignored", reason: "PR owned by remote agent" }.to_json]
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# Crash notifications are posted via `gh` as the human's account when the app
|
|
447
|
+
# client isn't configured, so the bot-login checks below can't catch them.
|
|
448
|
+
# Without this guard each crash comment re-dispatches the agent, which crashes
|
|
449
|
+
# again, forever.
|
|
450
|
+
if crash_notification_comment?(comment_body)
|
|
451
|
+
LOG.info "[GitHub] Ignoring crash notification comment on PR ##{issue["number"]}"
|
|
452
|
+
return [200, { status: "ignored", reason: "crash notification" }.to_json]
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
nil
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# True when a comment is a crash notification posted by the crash handler.
|
|
459
|
+
# These are posted via `gh` as the human account when no app client is
|
|
460
|
+
# configured, so the bot-login checks can't catch them — match on the prefix.
|
|
461
|
+
def crash_notification_comment?(comment_body)
|
|
462
|
+
comment_body.to_s.lstrip.start_with?(CRASH_COMMENT_PREFIX)
|
|
463
|
+
end
|
|
464
|
+
|
|
424
465
|
# Check PR ownership given the author login and account type.
|
|
425
466
|
# Used by both pull_request_review (has full PR object) and issue_comment
|
|
426
467
|
# (has issue.user which is the PR author on PR-linked issues).
|
|
@@ -631,6 +672,20 @@ module Brainiac
|
|
|
631
672
|
# 1. Self-triggered: bot matches the dispatching agent (prevents loops)
|
|
632
673
|
# 2. Remote agent: bot matches a known agent that isn't local (other machine handles it)
|
|
633
674
|
# Allows: cross-agent LOCAL bot comments (e.g. GLaDOS commenting on Galen's PR)
|
|
675
|
+
# Records a dispatch attempt; true when the (repo, PR, agent) is over the limit.
|
|
676
|
+
def comment_dispatch_throttled?(repo_name, pr_number, agent_name)
|
|
677
|
+
key = [repo_name, pr_number, agent_name.to_s.downcase]
|
|
678
|
+
now = Time.now.to_f
|
|
679
|
+
COMMENT_DISPATCH_LOCK.synchronize do
|
|
680
|
+
times = COMMENT_DISPATCH_TIMES[key]
|
|
681
|
+
times.reject! { |t| now - t > COMMENT_DISPATCH_WINDOW }
|
|
682
|
+
return true if times.size >= COMMENT_DISPATCH_LIMIT
|
|
683
|
+
|
|
684
|
+
times << now
|
|
685
|
+
false
|
|
686
|
+
end
|
|
687
|
+
end
|
|
688
|
+
|
|
634
689
|
def bot_comment_ignore_reason(comment_user, comment_user_type, agent_name, pr_number)
|
|
635
690
|
return nil unless comment_user_type == "Bot"
|
|
636
691
|
|
|
@@ -67,7 +67,8 @@ module Brainiac
|
|
|
67
67
|
agent_key = ctx[:agent_key]
|
|
68
68
|
snippet = ctx[:snippet]
|
|
69
69
|
snippet_block = snippet ? "\n```\n#{snippet[-1500..]}\n```" : ""
|
|
70
|
-
comment_body = "
|
|
70
|
+
comment_body = "#{Handler::CRASH_COMMENT_PREFIX} **#{agent_display} crashed** " \
|
|
71
|
+
"(exit code #{ctx[:exit_status]})\n\nLog: `#{ctx[:log_file]}`#{snippet_block}"
|
|
71
72
|
|
|
72
73
|
begin
|
|
73
74
|
if AppClient.configured?(agent_key)
|