Skip to content

Add /oops-clear Slack slash command and case-insensitive machine matching#155

Merged
jantman merged 11 commits into
mainfrom
slack-slash-command
Jun 12, 2026
Merged

Add /oops-clear Slack slash command and case-insensitive machine matching#155
jantman merged 11 commits into
mainfrom
slack-slash-command

Conversation

@jantman

@jantman jantman commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a /oops-clear Slack slash command for clearing machines (Oops / maintenance lock-out) and makes all machine name/alias matching case-insensitive.

Implements the feature described in docs/features/completed/slack-slack-command.md.

1. /oops-clear slash command

Restricted to the control channel (SLACK_CONTROL_CHANNEL_ID); invocations elsewhere get an ephemeral rejection.

  • /oops-clear <machine name> — clears the named machine (by name or alias), equivalent to the existing clear at-mention. Silent ack on success (the existing oops/control channel posts cover the outcome); ephemeral messages for invalid names or already-clear machines.
  • /oops-clear (no argument) — opens a Block Kit modal with a single dropdown (static_select, no default) listing all currently Oopsed or locked-out machines. Selecting one and submitting clears it. If nothing is Oopsed/locked-out, an ephemeral notice is shown instead.

2. Case-insensitive matching

MachinesConfig.get_machine() now resolves names and aliases case-insensitively, which applies to both the new slash command and the existing @mention commands (both funnel through that method).

Implementation notes

  • Shared _clear_machine() / _invalid_machine_msg() helpers; the existing mention clear() was refactored onto them (no behavior change).
  • Modal submission handles the already-cleared race and missing/unknown selections gracefully.
  • No new dependencies — reuses the existing slack-bolt AsyncApp over Socket Mode.

Docs

  • docs/source/slack.rst: setup steps to create the slash command + enable Interactivity (no Request URL under Socket Mode), the commands Bot Token Scope, and usage for both /oops-clear forms plus a case-insensitivity note.
  • Refreshed HELP_RESPONSE and the CLAUDE.md Slack-lookup note.

Testing

  • nox -s tests (309 passing), pre-commit, mypy, typeguard, docs all pass.
  • slack_handler.py at 100% coverage.
  • ⚠️ nox -s safety fails only on pre-existing upstream CVEs in transitive dependencies (aiohttp, dulwich, idna). This branch changes no dependencies (pyproject.toml/poetry.lock untouched), so it is unrelated to this feature and best handled as a separate dependency bump.

🤖 Generated with Claude Code

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 adds a new /oops-clear Slack slash command (direct and modal-driven forms) for clearing Oops/maintenance lock-out states, and updates machine name/alias resolution to be case-insensitive via MachinesConfig.get_machine(). This improves operator UX while keeping existing at-mention behavior aligned with the new command.

Changes:

  • Implement /oops-clear with control-channel gating, a no-arg modal flow, and shared clear/validation helpers in SlackHandler.
  • Make MachinesConfig.get_machine() resolve machine names and aliases case-insensitively via lowercased lookup maps.
  • Extend test coverage for new Slack flows and case-insensitive matching; update Slack setup/usage documentation accordingly.

Reviewed changes

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

Show a summary per file
File Description
tests/test_slack_handler.py Adds unit tests for /oops-clear (direct + modal) and for case-insensitive at-mention command matching; updates init registration expectations.
tests/models/test_machine.py Adds tests ensuring get_machine() matches both names and aliases regardless of case.
src/dm_mac/slack_handler.py Registers and implements /oops-clear plus modal submission handling; refactors clear logic into reusable helpers; updates help text.
src/dm_mac/models/machine.py Adds lowercase lookup dicts and updates get_machine() to be case-insensitive.
docs/source/slack.rst Documents new commands scope, interactivity enablement, slash command setup steps, and /oops-clear usage.
docs/features/completed/slack-slack-command.md Adds the completed feature design/plan doc describing the implementation and progress.
CLAUDE.md Updates Slack lookup note to mention case-insensitive matching and /oops-clear.

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

Comment thread src/dm_mac/models/machine.py Outdated
Comment thread src/dm_mac/slack_handler.py Outdated
Comment thread docs/features/completed/slack-slack-command.md Outdated
Comment thread docs/features/completed/slack-slack-command.md Outdated
Comment thread docs/source/slack.rst Outdated
@claude

claude Bot commented Jun 12, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

jantman and others added 11 commits June 12, 2026 16:52
…mmand

Write the implementation plan into the slack-slack-command feature
document. The plan covers a /oops-clear slash command (direct
`/oops-clear <machine>` form and a Block Kit modal form) restricted to
the control channel, plus case-insensitive machine name/alias matching
for both the slash command and existing @mention commands.

Organized into four milestones: case-insensitive get_machine(), the
direct slash-command form, the interactive modal, and acceptance
criteria. Records two confirmed decisions: silent ack() on successful
clear (no ephemeral confirmation) and keeping the generic "Slack" source
label without Machine model changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ensitive

Add lowercased name/alias lookup maps (machines_by_name_lower,
machines_by_alias_lower) populated alongside the existing exact-match
maps, and change get_machine() to resolve name_or_alias.lower() against
them. This makes machine-name matching case-insensitive for every caller
of get_machine(), including the existing Slack @mention commands and the
forthcoming /oops-clear slash command. The original exact-match maps are
left intact for existing callers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add unit tests covering MachinesConfig.get_machine() resolving machine
names and aliases regardless of case, plus a SlackHandler test asserting
the @mention oops command matches a mixed-case machine alias
("MeTaL MiLl"). Update the feature document Milestone 1 progress. All
nox tests passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Refactor SlackHandler.clear() to delegate to two new reusable helpers
that the forthcoming /oops-clear slash command and modal will share:

- _invalid_machine_msg(name): the standard "invalid machine name or
  alias" message.
- _clear_machine(mach): clears oops and/or lock-out, returning None when
  something was cleared (channel posts cover it) or a message string when
  the machine was already clear.

Pure refactor with no behavior change; existing SlackHandler tests pass
unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…l gating

Register the /oops-clear slash command and implement its direct form in
SlackHandler.oops_clear_command(). The command is restricted to the
control channel (SLACK_CONTROL_CHANNEL_ID); invocations elsewhere get an
ephemeral rejection. With a machine name/alias argument it resolves the
machine (case-insensitively) and clears it via the shared _clear_machine
helper: invalid names and already-clear machines get an ephemeral
message, while a successful clear acks silently (the existing oops/
control channel posts cover the outcome). When no argument is given it
currently acks with a usage hint; the Block Kit selection modal replaces
this in Milestone 3.

Update test_init to assert the new command registration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add SlackHandler.oops_clear_command() direct-form tests covering: clears
an oopsed machine with a silent ack, acks with a message when the machine
is already clear, rejects an invalid machine name, rejects invocation
from a non-control channel without changing state, and matches the
machine alias case-insensitively. Update the feature document Milestone 2
progress. All 28 slack handler tests passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implement the no-argument /oops-clear flow. When invoked with no machine
name, oops_clear_command() gathers machines that are currently oopsed or
locked out; if none, it acks with an ephemeral notice, otherwise it opens
a Block Kit modal (built by _build_clear_modal) whose single required
static_select lists those machines (option text = display name, value =
machine name) with no default selection.

Register and implement the view-submission handler
oops_clear_modal_submit(), keyed on the MODAL_CALLBACK_ID
("oops_clear_modal"): it acks to close the modal, extracts the selected
machine, and clears it via the shared _clear_machine helper. A machine
cleared between modal open and submit, or a missing selection, is handled
gracefully. Modal block/action/callback ids are class constants. test_init
updated to assert the view registration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add tests covering the Block Kit modal flow: no-arg command opens a modal
whose options exactly match the oopsed/locked machines (sorted by display
name, text = display name, value = name, no initial_option); no-arg
command with nothing oopsed/locked acks ephemerally and opens no modal;
modal submission clears the selected machine; and submission for an
already-clear machine, with no selection, or for an unknown machine is
handled gracefully. slack_handler.py is now at 100% test coverage. Update
the feature document Milestone 3 progress.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Acceptance-criteria milestone for the /oops-clear slash command and
case-insensitive machine matching feature:

- docs/source/slack.rst: document creating the /oops-clear slash command
  and enabling Interactivity (no Request URL needed under Socket Mode),
  add the "commands" Bot Token Scope, and add a usage section for both
  /oops-clear forms plus a note that name/alias matching is
  case-insensitive.
- slack_handler.py: extend HELP_RESPONSE to mention case-insensitive
  matching and the /oops-clear slash command.
- CLAUDE.md: note that Slack machine lookups are case-insensitive and
  cover both at-mentions and the slash command.
- Move the feature document to docs/features/completed/.

nox tests (309), pre-commit, mypy, typeguard, and docs all pass. The
safety session fails only on pre-existing upstream CVEs (aiohttp,
dulwich, idna) in transitive deps; this branch changes no dependencies,
so it is unrelated to this feature.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address Copilot review feedback on PR #155:

- machine.py: detect case-insensitive collisions when building the
  lowercased alias lookup map and raise a clear ValueError at load
  instead of silently overwriting an entry. The alias map is now
  populated in a second pass after all machine names are known, so a
  collision (alias vs. alias, or alias vs. another machine's name) is
  detected regardless of config order. An alias equal to the machine's
  own name remains allowed. Add tests for both collision cases and the
  allowed self-alias case.
- slack_handler.py: drop the internal "see Milestone 3" planning
  reference from oops_clear_command's docstring and tidy the wrapping.
- docs/features/completed/slack-slack-command.md: fix the relative link
  to the feature README (../README.md now that the doc lives in
  completed/) and remove stray tool-output artifact lines at the end.
- docs/source/slack.rst: refer to the Block Kit "modal" rather than
  "dialog" for consistency.

All nox sessions (tests, pre-commit, mypy, docs) pass; safety now also
passes after the main-branch dependency refresh.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jantman jantman force-pushed the slack-slash-command branch from 00e6058 to de14e0e Compare June 12, 2026 21:06
@github-actions

Copy link
Copy Markdown

Coverage

Coverage Report
FileStmtsMissBranchBrPartCoverMissing
src/dm_mac
   __init__.py73060100% 
   cli_utils.py15000100% 
   neon_fob_adder.py2321560593%79, 116–117, 124, 270, 333–334, 341, 364–367, 454–456
   neongetter.py211154399%309
   slack_handler.py2200560100% 
   utils.py25040100% 
src/dm_mac/models
   __init__.py0000100% 
   api_schemas.py34000100% 
   machine.py592162001697%615, 695, 987–989, 1101–1110, 1168
   users.py1030320100% 
src/dm_mac/views
   __init__.py0000100% 
   api.py32000100% 
   machine.py1030120100% 
   prometheus.py1320121100% 
TOTAL1772324362598% 

Tests Skipped Failures Errors Time
312 0 💤 0 ❌ 0 🔥 25.831s ⏱️

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 7 out of 7 changed files in this pull request and generated no new comments.

@jantman jantman merged commit 371da7b into main Jun 12, 2026
28 checks passed
@jantman jantman deleted the slack-slash-command branch June 12, 2026 21:22
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.

2 participants