diff --git a/parser/src/command.rs b/parser/src/command.rs
index 7edde0f60..05125974b 100644
--- a/parser/src/command.rs
+++ b/parser/src/command.rs
@@ -11,6 +11,7 @@ pub mod prioritize;
 pub mod relabel;
 pub mod second;
 pub mod shortcut;
+pub mod triage;
 
 pub fn find_command_start(input: &str, bot: &str) -> Option<usize> {
     input.to_ascii_lowercase().find(&format!("@{}", bot))
@@ -27,6 +28,7 @@ pub enum Command<'a> {
     Glacier(Result<glacier::GlacierCommand, Error<'a>>),
     Shortcut(Result<shortcut::ShortcutCommand, Error<'a>>),
     Close(Result<close::CloseCommand, Error<'a>>),
+    Triage(Result<triage::TriageCommand, Error<'a>>),
 }
 
 #[derive(Debug)]
@@ -131,6 +133,11 @@ impl<'a> Input<'a> {
             Command::Close,
             &original_tokenizer,
         ));
+        success.extend(parse_single_command(
+            triage::TriageCommand::parse,
+            Command::Triage,
+            &original_tokenizer,
+        ));
 
         if success.len() > 1 {
             panic!(
@@ -191,6 +198,7 @@ impl<'a> Command<'a> {
             Command::Glacier(r) => r.is_ok(),
             Command::Shortcut(r) => r.is_ok(),
             Command::Close(r) => r.is_ok(),
+            Command::Triage(r) => r.is_ok(),
         }
     }
 
diff --git a/parser/src/command/triage.rs b/parser/src/command/triage.rs
new file mode 100644
index 000000000..f3c44f6ab
--- /dev/null
+++ b/parser/src/command/triage.rs
@@ -0,0 +1,82 @@
+//! The triage command parser.
+//!
+//! Gives the priority to be changed to.
+//!
+//! The grammar is as follows:
+//!
+//! ```text
+//! Command: `@bot triage {high,medium,low,P-high,P-medium,P-low}`
+//! ```
+
+use crate::error::Error;
+use crate::token::{Token, Tokenizer};
+use std::fmt;
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum Priority {
+    Critical,
+    High,
+    Medium,
+    Low,
+}
+
+#[derive(PartialEq, Eq, Debug)]
+pub struct TriageCommand {
+    pub priority: Priority,
+}
+
+#[derive(PartialEq, Eq, Debug)]
+pub enum ParseError {
+    ExpectedPriority,
+    ExpectedEnd,
+}
+
+impl std::error::Error for ParseError {}
+
+impl fmt::Display for ParseError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            ParseError::ExpectedPriority => write!(f, "expected priority (critical, high, medium, low)"),
+            ParseError::ExpectedEnd => write!(f, "expected end of command"),
+        }
+    }
+}
+
+impl TriageCommand {
+    pub fn parse<'a>(input: &mut Tokenizer<'a>) -> Result<Option<Self>, Error<'a>> {
+        let mut toks = input.clone();
+        if let Some(Token::Word("triage")) = toks.peek_token()? {
+            toks.next_token()?;
+            let priority = match toks.peek_token()? {
+                Some(Token::Word("critical")) | Some(Token::Word("P-critical")) => {
+                    toks.next_token()?;
+                    Priority::Critical
+                }
+                Some(Token::Word("high")) | Some(Token::Word("P-high")) => {
+                    toks.next_token()?;
+                    Priority::High
+                }
+                Some(Token::Word("medium")) | Some(Token::Word("P-medium")) => {
+                    toks.next_token()?;
+                    Priority::Medium
+                }
+                Some(Token::Word("low")) | Some(Token::Word("P-low")) => {
+                    toks.next_token()?;
+                    Priority::Low
+                }
+                _ => {
+                    return Err(toks.error(ParseError::ExpectedPriority));
+                }
+            };
+            if let Some(Token::Dot) | Some(Token::EndOfLine) = toks.peek_token()? {
+                toks.next_token()?;
+                *input = toks;
+                return Ok(Some(TriageCommand { priority: priority }));
+            } else {
+                return Err(toks.error(ParseError::ExpectedEnd));
+            }
+        } else {
+            return Ok(None);
+        }
+    }
+}
diff --git a/src/config.rs b/src/config.rs
index 176e92275..474a1769f 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -30,6 +30,7 @@ pub(crate) struct Config {
     pub(crate) github_releases: Option<GitHubReleasesConfig>,
     pub(crate) review_submitted: Option<ReviewSubmittedConfig>,
     pub(crate) shortcut: Option<ShortcutConfig>,
+    pub(crate) triage: Option<TriageConfig>,
 }
 
 #[derive(PartialEq, Eq, Debug, serde::Deserialize)]
@@ -158,6 +159,15 @@ pub(crate) struct ReviewSubmittedConfig {
     pub(crate) reviewed_label: String,
 }
 
+#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
+pub(crate) struct TriageConfig {
+    pub(crate) remove: Vec<String>,
+    pub(crate) critical: String,
+    pub(crate) high: String,
+    pub(crate) medium: String,
+    pub(crate) low: String,
+}
+
 pub(crate) async fn get(gh: &GithubClient, repo: &str) -> Result<Arc<Config>, ConfigurationError> {
     if let Some(config) = get_cached_config(repo) {
         log::trace!("returning config for {} from cache", repo);
@@ -310,6 +320,7 @@ mod tests {
                 notify_zulip: None,
                 github_releases: None,
                 review_submitted: None,
+                triage: None,
             }
         );
     }
diff --git a/src/handlers.rs b/src/handlers.rs
index 1b58533be..8ebf0c4f4 100644
--- a/src/handlers.rs
+++ b/src/handlers.rs
@@ -38,6 +38,7 @@ mod relabel;
 mod review_submitted;
 mod rustc_commits;
 mod shortcut;
+mod triage;
 
 pub async fn handle(ctx: &Context, event: &Event) -> Vec<HandlerError> {
     let config = config::get(&ctx.github, &event.repo_name()).await;
@@ -242,6 +243,7 @@ command_handlers! {
     major_change: Second,
     shortcut: Shortcut,
     close: Close,
+    triage: Triage,
 }
 
 pub struct Context {
diff --git a/src/handlers/triage.rs b/src/handlers/triage.rs
new file mode 100644
index 000000000..01f85fa86
--- /dev/null
+++ b/src/handlers/triage.rs
@@ -0,0 +1,49 @@
+//! Triage command
+//!
+//! Removes I-nominated tag and adds priority label.
+
+use crate::{
+    config::TriageConfig,
+    github::{self, Event},
+    handlers::Context,
+};
+use parser::command::triage::{Priority, TriageCommand};
+
+pub(super) async fn handle_command(
+    ctx: &Context,
+    config: &TriageConfig,
+    event: &Event,
+    cmd: TriageCommand,
+) -> anyhow::Result<()> {
+    let event = if let Event::IssueComment(e) = event {
+        e
+    } else {
+        // not interested in other events
+        return Ok(());
+    };
+
+    let is_team_member =
+        if let Err(_) | Ok(false) = event.comment.user.is_team_member(&ctx.github).await {
+            false
+        } else {
+            true
+        };
+    if !is_team_member {
+        anyhow::bail!("Cannot use triage command as non-team-member");
+    }
+
+    let mut labels = event.issue.labels().to_owned();
+    let add = match cmd.priority {
+        Priority::Critical => &config.critical,
+        Priority::High => &config.high,
+        Priority::Medium => &config.medium,
+        Priority::Low => &config.low,
+    };
+    labels.push(github::Label { name: add.clone() });
+    labels.retain(|label| !config.remove.contains(&label.name));
+    if &labels[..] != event.issue.labels() {
+        event.issue.set_labels(&ctx.github, labels).await?;
+    }
+
+    Ok(())
+}