Skip to content

Migrate GitHub webhook normalization into github tool - #758

Merged
ilblackdragon merged 12 commits into
stagingfrom
split/github-tool-migration
Mar 12, 2026
Merged

ilblackdragon merged 12 commits into
stagingfrom
split/github-tool-migration

Conversation

@ilblackdragon

Copy link
Copy Markdown
Member

Summary

  • add handle_webhook action to tools-src/github
  • move GitHub event normalization/enrichment into tool implementation
  • declare GitHub webhook HMAC verification requirements in capabilities
  • update GitHub tool docs for webhook-driven flow

Validation

  • cargo check (tools-src/github)
  • cargo test (tools-src/github)

@github-actions github-actions Bot added scope: docs Documentation size: XL 500+ changed lines risk: low Changes to docs, tests, or low-risk modules labels Mar 9, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the GitHub tool by integrating robust webhook handling capabilities, enabling event-driven workflows. It centralizes GitHub event normalization and enrichment within the tool, and expands the tool's API to support a wider range of interactions, including creating and merging pull requests, managing issue and pull request comments, and retrieving status information. These changes streamline automation and improve the tool's responsiveness to GitHub events.

Highlights

  • Webhook Integration: Introduced a new handle_webhook action to process incoming GitHub webhook events, enabling event-driven workflows.
  • Event Normalization: Migrated GitHub event normalization and enrichment logic directly into the tool's implementation, providing structured event data.
  • Security Enhancements: Declared HMAC verification requirements for GitHub webhooks within the capabilities manifest, bolstering security for incoming events.
  • Expanded Pull Request Management: Added new actions for creating and merging pull requests, listing and replying to pull request comments, and retrieving pull request reviews.
  • Issue Comment Management: Implemented new actions to list existing issue comments and create new comments on issues.
  • Status Checks: Included an action to retrieve the combined status of a commit, useful for CI/CD integrations.
  • Documentation Updates: Updated the README.md to reflect the new features and provide examples for the new pull request actions.
Changelog
  • tools-src/github/README.md
    • Updated the "Features" section to include new issue and pull request functionalities.
    • Added example JSON payloads for create_pull_request and merge_pull_request actions.
  • tools-src/github/github-tool.capabilities.json
    • Added a webhook capability block specifying HMAC verification details for GitHub webhooks.
    • Extended the http capability to allow PUT requests to api.github.com.
  • tools-src/github/src/lib.rs
    • Imported HashMap and Serialize for new data structures.
    • Added new GitHubAction variants: ListIssueComments, CreateIssueComment, CreatePullRequest, ListPullRequestComments, ReplyPullRequestComment, GetPullRequestReviews, GetCombinedStatus, MergePullRequest, and HandleWebhook.
    • Defined new structs GitHubWebhookRequest, ToolWebhookResponse, and SystemEventIntent for webhook processing.
    • Implemented corresponding functions for the new GitHubAction variants, including list_issue_comments, create_issue_comment, create_pull_request, list_pull_request_comments, reply_pull_request_comment, get_pull_request_reviews, get_combined_status, merge_pull_request, and handle_webhook.
    • Added helper functions header_value, github_event_type, and github_enriched_payload for webhook event processing and normalization.
    • Updated the JSON schema to include the new actions and their parameters.
    • Added new unit tests for validate_merge_method, github_event_type_normalization, github_enriched_payload_extracts_common_fields, handle_webhook_requires_event_header, and handle_webhook_emits_event_intent.
Activity
  • No specific activity (comments, reviews, etc.) has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@github-actions github-actions Bot added the contributor: core 20+ merged PRs label Mar 9, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request successfully migrates GitHub webhook normalization logic into the github tool, adding a handle_webhook action and several other new actions for issues and pull requests. The changes are well-structured, and the addition of webhook capabilities to the tool's manifest is a good practice. I've found one area for improvement in the header parsing logic to make it more robust, aligning with best practices for handling service-specific requirements.

Note: Security Review did not run due to the size of the PR.

Comment thread tools-src/github/src/lib.rs Outdated
Comment on lines +1044 to +1049
fn header_value<'a>(headers: &'a HashMap<String, String>, key: &str) -> Option<&'a str> {
headers
.get(key)
.or_else(|| headers.get(&key.to_ascii_lowercase()))
.or_else(|| headers.get(&key.to_ascii_uppercase()))
.map(String::as_str)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation for header_value is not fully case-insensitive as required by the HTTP specification for header names. It only checks for the exact key, the all-lowercase version, and the all-uppercase version. This could fail to find headers with different casing, such as X-GitHub-Event if the lookup key is x-github-event.

To ensure robust, case-insensitive matching, I suggest iterating through the headers and comparing keys case-insensitively.

    headers
        .iter()
        .find(|(k, _)| k.eq_ignore_ascii_case(key))
        .map(|(_, v)| v.as_str())
References
  1. Generic token validation functions, or any functions interacting with service-specific headers, require robust header handling, including case-insensitive matching, to correctly process custom headers and service-specific requirements.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6c43d04. header_value now iterates all headers with to_ascii_lowercase() comparison, handling all case variants including mixed-case like X-Github-Event.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6c43d04. header_value now iterates all headers with to_ascii_lowercase() comparison.

@zmanian zmanian left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Overall this is a well-structured PR that follows the existing patterns in the codebase. The webhook normalization logic is clean, the enrichment approach is sensible, and the new CRUD actions are consistent with the existing ones. Good test coverage for the webhook path. A few issues to address:

Issues

1. header_value case handling is fragile (medium)

The header_value helper checks exact key, lowercase, and uppercase -- but HTTP headers are case-insensitive and can be mixed-case (e.g., X-GitHub-Event, x-github-event, X-Github-Event). The current approach misses mixed-case variants. Consider normalizing to lowercase on lookup:

fn header_value<'a>(headers: &'a HashMap<String, String>, key: &str) -> Option<&'a str> {
    let lower = key.to_ascii_lowercase();
    headers
        .iter()
        .find(|(k, _)| k.to_ascii_lowercase() == lower)
        .map(|(_, v)| v.as_str())
}

This is a correctness issue since the runtime delivering the webhook may normalize headers differently.

2. comment_id should be u64, not u32 (medium)

GitHub API uses 64-bit integer IDs for comments (and other resources). The ReplyPullRequestComment::comment_id field is u32, which will silently truncate large IDs. The same concern applies to pr_number and issue_number, though those are less likely to overflow in practice. At minimum, comment_id should be u64 since comment IDs are allocated from a global namespace and are already well into the billions.

3. test_validate_merge_method is a no-op test (low)

This test validates that Rust's contains method works on a literal array -- it doesn't exercise merge_pull_request() at all. Either test the actual function with valid/invalid merge methods, or remove this test.

4. handle_webhook exposed in LLM-facing schema (medium)

The handle_webhook action appears in the tool's JSON schema, meaning the LLM could invoke it directly with crafted webhook payloads, bypassing HMAC verification (which is done at the runtime level before the tool is called). Consider whether this action should be excluded from the schema or whether the tool should validate that it's being called from the webhook dispatch path rather than direct LLM invocation.

5. put_string_normalized overwrites repository and sender objects (low)

GitHub payloads include repository and sender as full JSON objects. The enrichment logic replaces these with just the string values (full_name and login). This is a deliberate design choice for flattening, but downstream consumers lose access to e.g. repository.private, repository.default_branch, sender.avatar_url. Consider using distinct keys like repository_name and sender_login to preserve the original objects.

6. README docs are incomplete

The new actions list_issue_comments, create_issue_comment, list_pull_request_comments, reply_pull_request_comment, get_pull_request_reviews, get_combined_status, and handle_webhook are not documented in the README. Only create_pull_request and merge_pull_request got examples.

Looks Good

  • Consistent input validation (validate_path_segment, validate_input_length, url_encode_path) on all new endpoints
  • Proper merge_method allowlist validation
  • Webhook capability declaration in capabilities.json with HMAC config
  • Good test coverage for event type normalization and enrichment
  • Adding PUT method to the HTTP allowlist for the merge endpoint

No blocking issues -- items 1, 2, and 4 are worth addressing before merge. The rest are nice-to-haves.

@github-actions github-actions Bot added scope: agent Agent core (agent loop, router, scheduler) risk: medium Business logic, config, or moderate-risk modules and removed risk: low Changes to docs, tests, or low-risk modules labels Mar 9, 2026
* Add reusable gateway workflow test harness with mock LLM server

* Fix clippy issues in workflow harness

* Stabilize trace E2E test rig and approval behavior

* Address PR review feedback on gateway workflow harness

- Extract shared TestChannelHandle into test_channel.rs with name override
  support, eliminating ~55 lines of duplication between test_rig.rs and
  gateway_workflow_harness.rs
- Remove redundant RoutineEngine creation that was immediately overwritten
  by Agent::run()
- Replace flaky sleep(500ms) with polling loop for routine run count check
- Use components.context_manager instead of creating a fresh ContextManager
  for job tools, ensuring agent and tools share the same instance

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix import ordering in gateway_workflow_harness

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

@zmanian zmanian left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review after commits 42e8a6a, e8395b4, 2d8c7db.

Previous feedback status

1. header_value case handling (medium) -- NOT addressed. The function still checks exact, lowercase, and uppercase only. Mixed-case variants like X-Github-Event will be missed. The suggested fix (iterate and compare with to_ascii_lowercase()) is straightforward and would close this gap.

2. comment_id should be u64 (medium) -- NOT addressed. ReplyPullRequestComment::comment_id is still u32. GitHub comment IDs are in the billions range globally. This will silently truncate.

3. test_validate_merge_method no-op test (low) -- The test has been removed entirely. However, test_validate_event_in_create_pr_review (line 830) has the exact same problem: it validates that Rust's contains works on a literal array rather than testing the actual create_pr_review function's event validation. Consider removing or rewriting it.

4. handle_webhook exposed in LLM-facing schema (medium) -- NOT addressed. handle_webhook is still present in the JSON schema. The new commits do not add any guard against direct LLM invocation of this action.

5. put_string_normalized overwrites repository/sender objects (low) -- NOT addressed. Still uses repository and sender keys, overwriting the original objects.

6. README docs incomplete (low) -- PARTIALLY addressed. The README now includes create_pull_request and merge_pull_request examples. The other new actions (list_issue_comments, create_issue_comment, list_pull_request_comments, reply_pull_request_comment, get_pull_request_reviews, get_combined_status, handle_webhook) are still not documented in the README, though they are discoverable via the JSON schema.

New commits review

Commit e8395b4 -- Test rig stabilization:

  • TestChannelHandle extracted from test_rig.rs into test_channel.rs and made public -- good refactoring, enables reuse by the new gateway harness.
  • Default auto_approve_tools changed from None to Some(true) in TestRigBuilder, and .with_auto_approve_tools(true) added to all test sites. This fixes flaky tests caused by approval prompts blocking the agent loop. Reasonable change.
  • max_tool_calls assertion relaxed from <= 4 to <= 8 (line 252) -- this warrants a comment explaining why the bound doubled. Is it because auto-approve now lets the agent run more iterations per turn?
  • verify_expects now includes failed tool status events in assertion output -- good diagnostic improvement.
  • thread_ops.rs change: auto_approve_tools config now short-circuits the session lock for UnlessAutoApproved. Clean and correct.

Commit 2d8c7db -- Gateway workflow harness:

  • Well-structured mock OpenAI server with rule-based response matching. Clean design.
  • gateway_workflow_integration.rs exercises the full chat-to-webhook-to-routine pipeline. Good coverage.
  • The MockGithubWebhookTool in the harness uses .unwrap_or("unknown") (line 447 of harness) -- acceptable in test code.
  • The harness uses #[cfg(feature = "libsql")] gating, which is correct for the test infrastructure.
  • Polling loops with tokio::time::sleep(100ms) and bounded iteration counts (30, 50) are reasonable for integration tests.

Summary

Items 1, 2, and 4 from the original review remain unaddressed. Items 1 and 2 are correctness issues worth fixing before merge. Item 4 is a design consideration that can be tracked separately if there is a plan for it.

The new test infrastructure is solid and well-organized. The test rig stabilization fixes are sensible.

@ilblackdragon
ilblackdragon force-pushed the split/generic-webhook-infra branch from a367284 to 819db92 Compare March 11, 2026 01:30
Base automatically changed from split/generic-webhook-infra to staging March 11, 2026 03:36
…migration

# Conflicts:
#	Cargo.lock
#	skills/ironclaw-workflow-orchestrator/SKILL.md
#	src/agent/routine.rs
#	src/agent/routine_engine.rs
#	src/agent/thread_ops.rs
#	src/channels/web/handlers/routines.rs
#	src/channels/web/server.rs
#	src/main.rs
#	src/tools/builtin/routine.rs
#	src/tools/registry.rs
#	src/tools/schema_validator.rs
#	src/tools/wasm/capabilities.rs
#	src/tools/wasm/capabilities_schema.rs
#	src/webhooks/mod.rs
#	tests/e2e_builtin_tool_coverage.rs
#	tests/fixtures/llm_traces/tools/routine_system_event_emit.json
#	tests/fixtures/llm_traces/tools/skill_install_routine_webhook_sim.json
#	tests/support/test_rig.rs
Copilot AI review requested due to automatic review settings March 11, 2026 07:20
@github-actions github-actions Bot added scope: dependencies Dependency updates risk: low Changes to docs, tests, or low-risk modules and removed risk: medium Business logic, config, or moderate-risk modules labels Mar 11, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates GitHub webhook event normalization/enrichment into the github WASM tool itself, adds a handle_webhook action for webhook-driven flows, and expands the tool’s GitHub API surface (comments/PR creation/status/merge) while declaring webhook HMAC requirements via tool capabilities.

Changes:

  • Add handle_webhook to the GitHub tool and implement GitHub event type normalization + payload enrichment for emitting system_events.
  • Expand GitHub tool actions (issue comments, PR creation, PR review comments, combined status, merge PR).
  • Add webhook HMAC capability metadata and introduce integration test harnesses for gateway + webhook workflows.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tools-src/github/src/lib.rs Adds new GitHub actions plus webhook normalization/enrichment and schema/tests updates.
tools-src/github/github-tool.capabilities.json Declares webhook HMAC verification parameters and allows HTTP PUT for merge.
tools-src/github/README.md Updates docs to reflect new issue/PR capabilities and webhook-driven operations.
tests/support/test_rig.rs Refactors test rig to use TestChannelHandle from test_channel.
tests/support/test_channel.rs Introduces TestChannelHandle wrapper (with optional name override).
tests/support/mod.rs Exposes new support modules for integration harness and mock OpenAI server.
tests/support/mock_openai_server.rs Adds an in-process OpenAI-compatible mock server for integration tests.
tests/support/gateway_workflow_harness.rs Adds a gateway+webhook end-to-end harness (server + webhook ingress + mock tool).
tests/gateway_workflow_integration.rs Adds an integration test that exercises chat → routine creation → webhook ingestion flow.
tests/e2e_routine_heartbeat.rs Adds/adjusts system-event routine trigger tests (but currently introduces compile issues).
skills/ironclaw-workflow-orchestrator/SKILL.md Updates workflow orchestrator guidance to reference webhook endpoint + optional HMAC secret.
registry/tools/github.json Bumps GitHub tool version to 0.2.1.
Cargo.lock Updates dependency lockfile entries.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/e2e_routine_heartbeat.rs Outdated
// -----------------------------------------------------------------------

#[tokio::test]
async fn system_event_trigger_matches_and_filters() {

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces a second test function named system_event_trigger_matches_and_filters in the same module, which will cause a duplicate definition compile error. Remove this duplicate test or rename it so there is only one function with that name in the module.

Suggested change
async fn system_event_trigger_matches_and_filters() {
async fn routine_cooldown() {

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6c43d04 — the duplicate test function was removed.

Comment thread tests/e2e_routine_heartbeat.rs Outdated
Comment on lines +426 to +433
let engine = Arc::new(RoutineEngine::new(
RoutineConfig::default(),
db.clone(),
llm,
ws,
notify_tx,
None,
));

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RoutineEngine::new currently requires tools: Arc<ToolRegistry> and safety: Arc<SafetyLayer> parameters (in addition to scheduler). This call site passes only 7 arguments, so it won’t compile; construct minimal ToolRegistry/SafetyLayer (as done elsewhere in this file) and pass them to RoutineEngine::new.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6c43d04 — the orphaned code with missing args was removed entirely.

Comment thread tools-src/github/src/lib.rs Outdated
Comment on lines +1046 to +1051
.get(key)
.or_else(|| headers.get(&key.to_ascii_lowercase()))
.or_else(|| headers.get(&key.to_ascii_uppercase()))
.map(String::as_str)
}

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

header_value() is intended to be case-insensitive, but the current implementation only tries the exact key, all-lowercase, and all-uppercase variants. It will miss common header casing like X-GitHub-Event. Consider normalizing the headers map to lowercase once (when building GitHubWebhookRequest) or doing a true ASCII case-insensitive lookup (e.g., iterate keys and compare with eq_ignore_ascii_case).

Suggested change
.get(key)
.or_else(|| headers.get(&key.to_ascii_lowercase()))
.or_else(|| headers.get(&key.to_ascii_uppercase()))
.map(String::as_str)
}
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case(key))
.map(|(_, v)| v.as_str())
}

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6c43d04 — header_value() now iterates all keys with eq_ignore_ascii_case (full case-insensitive lookup).

Comment thread tools-src/github/src/lib.rs Outdated
"action": { "const": "handle_webhook" },
"webhook": {
"type": "object",
"description": "Generic webhook payload envelope from core runtime"

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSON schema for handle_webhook declares webhook as an unstructured object. Since handle_webhook requires at least webhook.headers and webhook.body_json, consider defining the expected webhook properties in the schema (and marking the required ones) so tool-call generation/validation can catch malformed inputs earlier.

Suggested change
"description": "Generic webhook payload envelope from core runtime"
"description": "Generic webhook payload envelope from core runtime",
"properties": {
"headers": {
"type": "object",
"description": "HTTP headers for the webhook request",
"additionalProperties": { "type": "string" }
},
"body_json": {
"type": "object",
"description": "Parsed JSON body of the webhook request"
}
},
"required": ["headers", "body_json"],
"additionalProperties": true

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handle_webhook action has been removed from the LLM-facing schema entirely (6c43d04) — it's only invoked by the webhook ingress runtime, not by the LLM. No schema validation needed.

Comment thread tools-src/github/src/lib.rs Outdated
Comment on lines +1535 to +1540
#[test]
fn test_validate_merge_method() {
let valid = ["merge", "squash", "rebase"];
assert!(valid.contains(&"merge"));
assert!(!valid.contains(&"invalid"));
}

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_validate_merge_method re-implements the list of valid merge methods instead of exercising the actual merge_pull_request validation logic, so it won’t fail if the implementation changes. Consider replacing it with a unit test that calls merge_pull_request (or the validation branch) and asserts that invalid merge_method values are rejected and valid ones are accepted.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6c43d04 — the no-op test was replaced with test_header_value_case_insensitive which exercises the actual implementation.

- Fix header_value to use fully case-insensitive lookup (iterate with
  to_ascii_lowercase) instead of checking only exact/lower/upper variants
- Change comment_id from u32 to u64 to handle GitHub's billion-range IDs
- Remove handle_webhook from LLM-facing JSON schema to prevent direct
  invocation bypassing HMAC verification
- Rename enrichment keys from repository/sender to repository_name/
  sender_login to preserve original JSON objects in webhook payloads
- Remove put_string_normalized helper (no longer needed)
- Replace no-op tests (test_validate_event_in_create_pr_review,
  test_validate_merge_method) with test_header_value_case_insensitive
- Add README docs for 6 undocumented actions (list_issue_comments,
  create_issue_comment, list_pull_request_comments,
  reply_pull_request_comment, get_pull_request_reviews,
  get_combined_status)
- Add comment explaining max_tool_calls <= 8 bound in e2e test
- Fix gateway workflow harness: add webhook_capability with secret auth
  to MockGithubWebhookTool, matching staging's hardened webhook security
- Fix merge artifacts: remove duplicate test function, orphaned code
  fragment in e2e_routine_heartbeat

[skip-regression-check]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ilblackdragon

Copy link
Copy Markdown
Member Author

Review feedback addressed in 6c43d04

1. header_value case handling (medium) — Fixed. Now iterates all headers with to_ascii_lowercase() comparison, handling all case variants. Added test_header_value_case_insensitive test.

2. comment_id u32 → u64 (medium) — Fixed. Changed to u64 in both the enum variant and function signature.

3. No-op tests (low) — Fixed. Removed test_validate_event_in_create_pr_review and test_validate_merge_method, replaced with test_header_value_case_insensitive that tests actual behavior.

4. handle_webhook in LLM schema (medium) — Fixed. Removed from the oneOf schema array. The action is still callable internally via serde deserialization (webhook dispatch path) but the LLM won't see it as an available action.

5. put_string_normalized overwrites objects (low) — Fixed. Changed to repository_name and sender_login keys using put_if_missing, preserving the original repository and sender JSON objects.

6. README docs incomplete (low) — Fixed. Added examples for list_issue_comments, create_issue_comment, list_pull_request_comments, reply_pull_request_comment, get_pull_request_reviews, get_combined_status.

7. max_tool_calls bound comment — Added explanatory comment for the <= 8 bound.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 11, 2026 15:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1152 to +1160
put_if_missing(
&mut obj,
"repository_name",
payload
.pointer("/repository/full_name")
.and_then(|v| v.as_str())
.map(|s| serde_json::Value::String(s.to_string())),
);
put_if_missing(

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

github_enriched_payload adds repository_name, but it leaves the existing repository field as the raw GitHub object. System-event routine filters currently match only top-level keys by string value, and the workflow orchestrator templates use event_filters.repository (string full_name). With the current payload shape, routines filtering on repository will not match webhook events. Consider normalizing so the emitted payload includes a string repository (e.g. repository.full_name) for filtering/back-compat (and preserve the original object under a different key if needed), or update the routine templates/docs to filter on repository_name instead.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 67e0018 — updated SKILL.md event filter docs and all workflow-routines.md templates to use repository_name and sender_login (matching the enriched payload keys).

Comment thread tools-src/github/src/lib.rs Outdated
Comment on lines +1181 to +1187
put_if_missing(
&mut obj,
"pr_number",
payload.pointer("/pull_request/number").cloned(),
);
put_if_missing(
&mut obj,

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For issue_comment webhooks on PRs you emit event types like pr.comment.* (via /issue/pull_request), but github_enriched_payload only sets pr_number from /pull_request/number, which isn’t present for issue_comment payloads. This means PR-comment events may lack pr_number even though they’re classified as PR events. Consider setting pr_number from /issue/number when the issue is a PR (and pr_number is missing).

Suggested change
put_if_missing(
&mut obj,
"pr_number",
payload.pointer("/pull_request/number").cloned(),
);
put_if_missing(
&mut obj,
let pr_number = payload
.pointer("/pull_request/number")
.cloned()
.or_else(|| {
// For issue_comment webhooks on PRs, there is no top-level
// /pull_request object, but /issue/pull_request is present and
// /issue/number corresponds to the PR number.
if payload.pointer("/issue/pull_request").is_some() {
payload.pointer("/issue/number").cloned()
} else {
None
}
});
put_if_missing(&mut obj, "pr_number", pr_number);
put_if_missing(
&mut obj,

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 67e0018 — pr_number now falls back to /issue/number when /pull_request/number is absent and /issue/pull_request exists. Added test_enriched_payload_pr_number_from_issue_comment regression test.

- GitHub tool authenticated (for issue/PR/comment/status operations).
- Events are emitted via `event_emit` tool calls (a future HTTP webhook ingestion endpoint is planned but not yet available).
- GitHub webhook delivery configured to `POST /webhook/tools/github`.
- Optional webhook HMAC secret configured in secrets store as `github_webhook_secret`.

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs say the GitHub webhook HMAC secret is optional, but the GitHub tool now declares an hmac_secret_name capability, and the generic webhook ingress treats any configured HMAC mechanism as required (it will reject requests if the secret is missing). Either make the secret requirement explicit here, or adjust the tool capability if you truly want webhook auth to be optional.

Suggested change
- Optional webhook HMAC secret configured in secrets store as `github_webhook_secret`.
- Webhook HMAC secret configured in the secrets store as `github_webhook_secret` (required for GitHub webhook delivery).

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 67e0018 — updated SKILL.md to mark the webhook HMAC secret as required.

Comment thread tests/support/mod.rs
Comment on lines 1 to +6
pub mod assertions;
pub mod cleanup;
pub mod gateway_workflow_harness;
pub mod instrumented_llm;
pub mod metrics;
pub mod mock_openai_server;

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests/support/mod.rs now unconditionally compiles gateway_workflow_harness, but that module depends on the libsql feature (it imports ironclaw::db::libsql::LibSqlBackend). This can break cargo test/cargo clippy --tests for feature sets that don’t include libsql. Consider gating the module export with #[cfg(feature = "libsql")] (similar to how tests/gateway_workflow_integration.rs is gated), or feature-gating the contents of the harness module.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 67e0018 — added #[cfg(feature = "libsql")] gate on gateway_workflow_harness in tests/support/mod.rs. Verified cargo check --tests passes without the libsql feature.

"display_name": "GitHub",
"kind": "tool",
"version": "0.2.0",
"version": "0.2.1",

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The registry manifest version was bumped to 0.2.1, but the GitHub tool source still declares 0.2.0 in tools-src/github/Cargo.toml and tools-src/github/github-tool.capabilities.json. If the release/packaging flow expects these versions to match (as they do for other tools), this can cause artifact/version skew. Please align the tool crate/capabilities versions with the registry version (or explain why they intentionally differ).

Suggested change
"version": "0.2.1",
"version": "0.2.0",

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 67e0018 — bumped Cargo.toml and capabilities.json to 0.2.1 to match the registry manifest.

ilblackdragon and others added 2 commits March 11, 2026 17:40
…, version alignment

- Update SKILL.md and workflow-routines.md templates to use `repository_name`
  and `sender_login` (matching enriched payload field names)
- Mark webhook HMAC secret as required in SKILL.md prerequisites
- Fall back to `/issue/number` for `pr_number` on issue_comment PR webhooks
- Gate `gateway_workflow_harness` module behind `#[cfg(feature = "libsql")]`
- Align tool version to 0.2.1 in Cargo.toml and capabilities.json

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 12, 2026 01:39

@zmanian zmanian left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review: All feedback addressed

All six items from my previous two reviews have been fixed in commit 6c43d04 and subsequent commits:

  1. header_value case handling -- Fixed. Now iterates all headers with to_ascii_lowercase() on both sides, handling any case variant. Regression test added.

  2. comment_id u32 -> u64 -- Fixed. Changed to u64 throughout (enum, function sig, JSON schema).

  3. No-op test functions -- Fixed. Removed the stdlib-testing no-ops, replaced with test_header_value_case_insensitive that tests actual behavior.

  4. handle_webhook in LLM schema -- Fixed. Removed from schema so the LLM can't bypass HMAC verification by calling it directly.

  5. put_string_normalized overwriting repository/sender -- Fixed. Enrichment keys renamed to repository_name and sender_login, preserving original objects.

  6. README docs incomplete -- Fixed. All 6 missing actions documented.

Additional improvement in 67e0018: pr_number enrichment now falls back to /issue/number for issue_comment webhooks on PRs where /pull_request/number is absent. Regression test added.

LGTM.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +62 to +78
let mut payload = params
.pointer("/webhook/body_json")
.cloned()
.unwrap_or_else(|| serde_json::json!({}));
if payload.get("repository").and_then(|v| v.as_str()).is_none()
&& let Some(full_name) = payload
.pointer("/repository/full_name")
.and_then(|v| v.as_str())
{
payload["repository"] = serde_json::json!(full_name);
}
let event_type = format!(
"{}.{}",
if event == "issues" { "issue" } else { event },
action
);

Copilot AI Mar 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MockGithubWebhookTool normalizes webhook payloads into a top-level repository string, but the real GitHub tool now enriches payloads with repository_name / sender_login (and routines/docs filter on those). This mismatch means the gateway workflow harness test can diverge from production behavior. Consider updating the mock tool’s normalization to populate repository_name (and other normalized fields used by routines) so end-to-end tests validate the same filtering keys.

Copilot uses AI. Check for mistakes.
Comment on lines +1045 to +1048
let lower = key.to_ascii_lowercase();
headers
.iter()
.find(|(k, _)| k.to_ascii_lowercase() == lower)

Copilot AI Mar 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

header_value() lowercases the search key and then allocates a new lowercase String for every header key during iteration. Since this function is intended to be case-insensitive, using eq_ignore_ascii_case on the existing strings avoids repeated allocations while keeping behavior the same.

Suggested change
let lower = key.to_ascii_lowercase();
headers
.iter()
.find(|(k, _)| k.to_ascii_lowercase() == lower)
headers
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case(key))

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +36
serde_json::json!({
"name": "wf-ci-webhook-demo",
"description": "CI webhook workflow demo",
"trigger_type": "system_event",
"event_source": "github",
"event_type": "issue.opened",
"event_filters": {"repository": "nearai/ironclaw"},
"action_type": "lightweight",

Copilot AI Mar 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this integration test, the created routine filters on event_filters.repository, but the GitHub tool’s webhook normalization emits repository_name (and SKILL/docs were updated accordingly). As written, this test can pass while not reflecting the real webhook payload shape. Update the routine creation (and any synthetic event payloads) to use repository_name (and related normalized keys like sender_login) so the test exercises the production flow accurately.

Copilot uses AI. Check for mistakes.
@ilblackdragon
ilblackdragon merged commit f05896f into staging Mar 12, 2026
13 checks passed
@ilblackdragon
ilblackdragon deleted the split/github-tool-migration branch March 12, 2026 01:52
@ironclaw-ci ironclaw-ci Bot mentioned this pull request Mar 12, 2026
bkutasi pushed a commit to bkutasi/ironclaw that referenced this pull request Mar 28, 2026
* Add event-triggered routines and workflow skill templates

* Add generic host-verified webhook ingress for tools

* Migrate GitHub webhook normalization into github tool

* Bump github tool registry version

* Stabilize trace E2E test rig and approval behavior

* Add reusable gateway workflow harness with mock LLM server (nearai#762)

* Add reusable gateway workflow test harness with mock LLM server

* Fix clippy issues in workflow harness

* Stabilize trace E2E test rig and approval behavior

* Address PR review feedback on gateway workflow harness

- Extract shared TestChannelHandle into test_channel.rs with name override
  support, eliminating ~55 lines of duplication between test_rig.rs and
  gateway_workflow_harness.rs
- Remove redundant RoutineEngine creation that was immediately overwritten
  by Agent::run()
- Replace flaky sleep(500ms) with polling loop for routine run count check
- Use components.context_manager instead of creating a fresh ContextManager
  for job tools, ensuring agent and tools share the same instance

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix import ordering in gateway_workflow_harness

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* Address PR nearai#758 review feedback

- Fix header_value to use fully case-insensitive lookup (iterate with
  to_ascii_lowercase) instead of checking only exact/lower/upper variants
- Change comment_id from u32 to u64 to handle GitHub's billion-range IDs
- Remove handle_webhook from LLM-facing JSON schema to prevent direct
  invocation bypassing HMAC verification
- Rename enrichment keys from repository/sender to repository_name/
  sender_login to preserve original JSON objects in webhook payloads
- Remove put_string_normalized helper (no longer needed)
- Replace no-op tests (test_validate_event_in_create_pr_review,
  test_validate_merge_method) with test_header_value_case_insensitive
- Add README docs for 6 undocumented actions (list_issue_comments,
  create_issue_comment, list_pull_request_comments,
  reply_pull_request_comment, get_pull_request_reviews,
  get_combined_status)
- Add comment explaining max_tool_calls <= 8 bound in e2e test
- Fix gateway workflow harness: add webhook_capability with secret auth
  to MockGithubWebhookTool, matching staging's hardened webhook security
- Fix merge artifacts: remove duplicate test function, orphaned code
  fragment in e2e_routine_heartbeat

[skip-regression-check]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix formatting in gateway workflow harness

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Address Copilot review: filter keys, pr_number fallback, feature gate, version alignment

- Update SKILL.md and workflow-routines.md templates to use `repository_name`
  and `sender_login` (matching enriched payload field names)
- Mark webhook HMAC secret as required in SKILL.md prerequisites
- Fall back to `/issue/number` for `pr_number` on issue_comment PR webhooks
- Gate `gateway_workflow_harness` module behind `#[cfg(feature = "libsql")]`
- Align tool version to 0.2.1 in Cargo.toml and capabilities.json

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
drchirag1991 pushed a commit to drchirag1991/ironclaw that referenced this pull request Apr 8, 2026
* Add event-triggered routines and workflow skill templates

* Add generic host-verified webhook ingress for tools

* Migrate GitHub webhook normalization into github tool

* Bump github tool registry version

* Stabilize trace E2E test rig and approval behavior

* Add reusable gateway workflow harness with mock LLM server (nearai#762)

* Add reusable gateway workflow test harness with mock LLM server

* Fix clippy issues in workflow harness

* Stabilize trace E2E test rig and approval behavior

* Address PR review feedback on gateway workflow harness

- Extract shared TestChannelHandle into test_channel.rs with name override
  support, eliminating ~55 lines of duplication between test_rig.rs and
  gateway_workflow_harness.rs
- Remove redundant RoutineEngine creation that was immediately overwritten
  by Agent::run()
- Replace flaky sleep(500ms) with polling loop for routine run count check
- Use components.context_manager instead of creating a fresh ContextManager
  for job tools, ensuring agent and tools share the same instance

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix import ordering in gateway_workflow_harness

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* Address PR nearai#758 review feedback

- Fix header_value to use fully case-insensitive lookup (iterate with
  to_ascii_lowercase) instead of checking only exact/lower/upper variants
- Change comment_id from u32 to u64 to handle GitHub's billion-range IDs
- Remove handle_webhook from LLM-facing JSON schema to prevent direct
  invocation bypassing HMAC verification
- Rename enrichment keys from repository/sender to repository_name/
  sender_login to preserve original JSON objects in webhook payloads
- Remove put_string_normalized helper (no longer needed)
- Replace no-op tests (test_validate_event_in_create_pr_review,
  test_validate_merge_method) with test_header_value_case_insensitive
- Add README docs for 6 undocumented actions (list_issue_comments,
  create_issue_comment, list_pull_request_comments,
  reply_pull_request_comment, get_pull_request_reviews,
  get_combined_status)
- Add comment explaining max_tool_calls <= 8 bound in e2e test
- Fix gateway workflow harness: add webhook_capability with secret auth
  to MockGithubWebhookTool, matching staging's hardened webhook security
- Fix merge artifacts: remove duplicate test function, orphaned code
  fragment in e2e_routine_heartbeat

[skip-regression-check]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix formatting in gateway workflow harness

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Address Copilot review: filter keys, pr_number fallback, feature gate, version alignment

- Update SKILL.md and workflow-routines.md templates to use `repository_name`
  and `sender_login` (matching enriched payload field names)
- Mark webhook HMAC secret as required in SKILL.md prerequisites
- Fall back to `/issue/number` for `pr_number` on issue_comment PR webhooks
- Gate `gateway_workflow_harness` module behind `#[cfg(feature = "libsql")]`
- Align tool version to 0.2.1 in Cargo.toml and capabilities.json

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: low Changes to docs, tests, or low-risk modules scope: agent Agent core (agent loop, router, scheduler) scope: dependencies Dependency updates scope: docs Documentation size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants