Skip to content

fix: case-insensitive actor and label conditions#589

Merged
ColeMurray merged 3 commits intomainfrom
fix/case-insensitive-conditions
May 5, 2026
Merged

fix: case-insensitive actor and label conditions#589
ColeMurray merged 3 commits intomainfrom
fix/case-insensitive-conditions

Conversation

@ColeMurray
Copy link
Copy Markdown
Owner

@ColeMurray ColeMurray commented May 2, 2026

Summary

  • Actor condition: "Colemurray" now matches "ColeMurray" — GitHub usernames are case-insensitive, so our condition matching should be too.
  • Label condition: Same fix — "bug" now matches a label named "Bug".
  • Adds 5 tests covering case-insensitive matching for both conditions (include/exclude/any_of/none_of).

Context

Discovered when configuring a pull_request.opened automation with an actor condition of "Colemurray" — it silently failed because Array.includes() is case-sensitive and GitHub returned "ColeMurray" in sender.login.

Test plan

  • npm test -w @open-inspect/shared — 64 tests pass
  • Typecheck passes
  • Lint passes (pre-commit hook)

Summary by CodeRabbit

  • Bug Fixes

    • GitHub actor and label conditions now match case-insensitively. Previously, condition matching required exact case matching; now conditions will match regardless of letter casing.
  • Tests

    • Added comprehensive test coverage for case-insensitive matching behavior in actor and label conditions.

ColeMurray added 2 commits May 1, 2026 23:17
The Durable Object already syncs status transitions to D1 via
syncSessionIndexStatus in transitionSessionStatus. The router's
synchronous D1 writes after calling the DO were redundant.

- Remove D1 writes from handleArchiveSession, handleUnarchiveSession,
  handleUpdateSessionTitle, and handleCancelChild
- Add syncSessionIndexTitle to the DO so title updates are synced
  to D1 from within the DO (matching the pattern used for status)
- Wire the new sync through SessionLifecycleHandlerDeps
GitHub treats usernames and labels as case-insensitive, but our
condition evaluators used exact string matching. This caused automations
to silently skip when users entered e.g. "Colemurray" instead of
"ColeMurray".
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 2, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9195cd08-6066-436a-9427-38e2c6f7ca6c

📥 Commits

Reviewing files that changed from the base of the PR and between b07cd16 and 114ad3a.

📒 Files selected for processing (1)
  • packages/shared/src/triggers/registry.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/shared/src/triggers/registry.ts

📝 Walkthrough

Walkthrough

Case-insensitive matching is implemented for GitHub actor and label trigger conditions by lowercasing event data and configured values during comparison. Tests verify the new behavior across include, exclude, any_of, and none_of operators.

Changes

Case-Insensitive Actor and Label Matching

Layer / File(s) Summary
Core Implementation
packages/shared/src/triggers/registry.ts
label.evaluate lowercases both event labels and configured values before overlap detection; actor.evaluate lowercases the event actor and performs case-insensitive comparisons for include and other operators.
Test Coverage
packages/shared/src/triggers/conditions.test.ts
New test suites for actor and label conditions validate case-insensitive matching across include, exclude, any_of, and none_of operators with different casing variations.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 Hop along with eager paws,
No more case-sensitive laws!
Actor, label—match with grace,
LOWERCASE wins the matching race!
Tests confirm our fuzzy might, 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main changes: making actor and label condition matching case-insensitive, which directly aligns with the core intent of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/case-insensitive-conditions

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

Copy link
Copy Markdown
Contributor

@open-inspect open-inspect Bot left a comment

Choose a reason for hiding this comment

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

Summary

This PR updates actor and label trigger conditions to compare case-insensitively, and it also moves session-title D1 index syncing into the session DO so title/status index updates live alongside the canonical lifecycle transitions.

  • PR Title: fix: case-insensitive actor and label conditions (#589)
  • Author: @ColeMurray
  • Files changed: 6
  • Additions/Deletions: +79 / -38

Critical Issues

None.

Suggestions

  • [Observability] packages/control-plane/src/session/durable-object.ts:1474 - The previous router path warned when SessionIndexStore.updateTitle() returned false. The new background sync logs thrown errors but no longer records the missing-row case. This looks non-blocking, but it may be worth preserving that warning if we still rely on it to detect D1/DO drift.

Nitpicks

None.

Positive Feedback

  • The case-insensitive condition changes are minimal and preserve the existing operator semantics.
  • The added tests cover both actor and label behavior across positive and negative cases.
  • Centralizing index sync with the DO lifecycle flow reduces the chance of router/DO behavior diverging.

Questions

None.

Verdict

Approve: Ready to merge, no blocking issues.

Validation: npm test -w @open-inspect/shared passed (64 tests) and npm test -w @open-inspect/control-plane -- session-lifecycle.handler.test.ts passed (17 tests).

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/shared/src/triggers/conditions.test.ts (2)

62-74: ⚡ Quick win

Consider adding the positive none_of path.

Similarly, the label tests cover any_oftrue and none_offalse. The passing case of none_of (label not present in event → true) is absent, so a regression making none_of always return false would slip through.

✅ Suggested additional assertion
    it("rejects labels with different casing (none_of)", () => {
      const event = buildMockEvent("github", { labels: ["Bug"] });
      const conditions = [{ type: "label" as const, operator: "none_of" as const, value: ["BUG"] }];
      expect(matchesConditions(conditions, event, conditionRegistry)).toBe(false);
    });
+
+    it("passes none_of when label is absent (case-insensitive)", () => {
+      const event = buildMockEvent("github", { labels: ["Enhancement"] });
+      const conditions = [{ type: "label" as const, operator: "none_of" as const, value: ["bug"] }];
+      expect(matchesConditions(conditions, event, conditionRegistry)).toBe(true);
+    });
  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/triggers/conditions.test.ts` around lines 62 - 74, Add a
positive test for the label "none_of" path: create a mock GitHub event via
buildMockEvent with labels that do NOT include the tested value (e.g., event
labels ["Enhancement"]) then assert matchesConditions([{ type: "label",
operator: "none_of", value: ["bug"] }], event, conditionRegistry) returns true;
place this new it(...) alongside the existing label condition tests so the
none_of behavior is validated in both false and true cases.

36-60: ⚡ Quick win

Consider adding the positive exclude path.

The new actor tests cover includetrue, excludefalse, and exact includetrue. There is no test asserting that exclude returns true when the actor is not in the exclusion list — i.e., the passing side of exclude. Without it, a regression that makes exclude always return false would not be caught by these new tests.

✅ Suggested additional assertion
    it("matches actor with exact casing", () => {
      const event = buildMockEvent("github", { actor: "octocat" });
      const conditions = [
        { type: "actor" as const, operator: "include" as const, value: ["octocat"] },
      ];
      expect(matchesConditions(conditions, event, conditionRegistry)).toBe(true);
    });
+
+    it("allows actor not in exclusion list (exclude)", () => {
+      const event = buildMockEvent("github", { actor: "octocat" });
+      const conditions = [
+        { type: "actor" as const, operator: "exclude" as const, value: ["COLEMURRAY"] },
+      ];
+      expect(matchesConditions(conditions, event, conditionRegistry)).toBe(true);
+    });
  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/triggers/conditions.test.ts` around lines 36 - 60, Add a
positive test for the `exclude` path in the actor condition block: within the
same describe("actor condition (case-insensitive)") add an `it` that uses
`buildMockEvent("github", { actor: "ColeMurray" })`, a conditions array like `{
type: "actor", operator: "exclude", value: ["someone_else"] }`, and assert
`matchesConditions(conditions, event, conditionRegistry)` is `true`; this
ensures `matchesConditions` (and the actor condition logic) correctly returns
true when the actor is not in the exclusion list.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/shared/src/triggers/conditions.test.ts`:
- Around line 62-74: Add a positive test for the label "none_of" path: create a
mock GitHub event via buildMockEvent with labels that do NOT include the tested
value (e.g., event labels ["Enhancement"]) then assert matchesConditions([{
type: "label", operator: "none_of", value: ["bug"] }], event, conditionRegistry)
returns true; place this new it(...) alongside the existing label condition
tests so the none_of behavior is validated in both false and true cases.
- Around line 36-60: Add a positive test for the `exclude` path in the actor
condition block: within the same describe("actor condition (case-insensitive)")
add an `it` that uses `buildMockEvent("github", { actor: "ColeMurray" })`, a
conditions array like `{ type: "actor", operator: "exclude", value:
["someone_else"] }`, and assert `matchesConditions(conditions, event,
conditionRegistry)` is `true`; this ensures `matchesConditions` (and the actor
condition logic) correctly returns true when the actor is not in the exclusion
list.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ded8c145-b438-4249-996b-0f6d7d81e4a5

📥 Commits

Reviewing files that changed from the base of the PR and between bf70d40 and b07cd16.

📒 Files selected for processing (6)
  • packages/control-plane/src/router.ts
  • packages/control-plane/src/session/durable-object.ts
  • packages/control-plane/src/session/http/handlers/session-lifecycle.handler.test.ts
  • packages/control-plane/src/session/http/handlers/session-lifecycle.handler.ts
  • packages/shared/src/triggers/conditions.test.ts
  • packages/shared/src/triggers/registry.ts
💤 Files with no reviewable changes (1)
  • packages/control-plane/src/router.ts

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

@ColeMurray ColeMurray merged commit a3c5807 into main May 5, 2026
18 checks passed
@ColeMurray ColeMurray deleted the fix/case-insensitive-conditions branch May 5, 2026 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant