From 2fcf738e84ac3310496825af3a3e45959fcf4c16 Mon Sep 17 00:00:00 2001 From: Joshua Cold Date: Thu, 21 May 2026 11:28:15 -0600 Subject: [PATCH 1/3] feat(github): Arg to adjust comment to github when there are 0 findings - When enabled with zero violations the resulting comment will just be that `squawk` found no issues in the github report without showing files and paths --- crates/squawk/src/config.rs | 12 +++++++ crates/squawk/src/github.rs | 36 ++++++++++++++++--- crates/squawk/src/main.rs | 3 ++ ...st_config__load_assume_in_transaction.snap | 1 + ...k__config__test_config__load_cfg_full.snap | 1 + ...fig__test_config__load_excluded_paths.snap | 1 + ...fig__test_config__load_excluded_rules.snap | 1 + ...onfig__load_excluded_rules_with_alias.snap | 1 + ..._test_config__load_fail_on_violations.snap | 1 + ...fig__test_config__load_included_rules.snap | 1 + ...nfig__load_only_comment_on_violations.snap | 21 +++++++++++ ..._config__test_config__load_pg_version.snap | 1 + ...enerating_clean_comment_no_violations.snap | 7 ++++ 13 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 crates/squawk/src/snapshots/squawk__config__test_config__load_only_comment_on_violations.snap create mode 100644 crates/squawk/src/snapshots/squawk__github__test_github_comment__generating_clean_comment_no_violations.snap diff --git a/crates/squawk/src/config.rs b/crates/squawk/src/config.rs index e7312bef..04d3fbb6 100644 --- a/crates/squawk/src/config.rs +++ b/crates/squawk/src/config.rs @@ -17,6 +17,8 @@ const FILE_NAME: &str = ".squawk.toml"; pub struct UploadToGitHubConfig { #[serde(default)] pub fail_on_violations: Option, + #[serde(default)] + pub only_comment_on_violations: Option, } #[derive(Debug, Default, Deserialize)] @@ -258,6 +260,16 @@ fail_on_violations = true assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf()))); } #[test] + fn load_only_comment_on_violations() { + let squawk_toml = NamedTempFile::new().expect("generate tempFile"); + let file = r" +[upload_to_github] +only_comment_on_violations = true + "; + fs::write(&squawk_toml, file).expect("Unable to write file"); + assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf()))); + } + #[test] fn load_included_rules() { let squawk_toml = NamedTempFile::new().expect("generate tempFile"); let file = r#" diff --git a/crates/squawk/src/github.rs b/crates/squawk/src/github.rs index 0d62432f..53e4e5ea 100644 --- a/crates/squawk/src/github.rs +++ b/crates/squawk/src/github.rs @@ -88,6 +88,7 @@ pub fn check_and_comment_on_pr(cfg: Config) -> Result<()> { let UploadToGithubArgs { paths, fail_on_violations, + only_comment_on_violations, github_private_key, github_api_url, github_token, @@ -106,6 +107,15 @@ pub fn check_and_comment_on_pr(cfg: Config) -> Result<()> { fail_on_violations }; + let only_comment_on_violations = + if let Some(only_comment_on_violations_cfg) = + cfg.upload_to_github.only_comment_on_violations + { + only_comment_on_violations_cfg + } else { + only_comment_on_violations + }; + let github_app = create_gh_app( &github_api_url, &github_install_id, @@ -133,8 +143,16 @@ pub fn check_and_comment_on_pr(cfg: Config) -> Result<()> { info!("no files checked, exiting"); return Ok(()); } - info!("generating github comment body"); - let comment_body = get_comment_body(&file_results, VERSION); + + let violations: usize = file_results.iter().map(|f| f.violations.len()).sum(); + + let comment_body = if only_comment_on_violations && violations == 0 { + info!("no violations found, posting clean summary comment"); + get_clean_comment_body(file_results.len()) + } else { + info!("generating github comment body"); + get_comment_body(&file_results, VERSION) + }; comment_on_pr( github_app.as_ref(), @@ -151,8 +169,6 @@ pub fn check_and_comment_on_pr(cfg: Config) -> Result<()> { fmt_github_annotations(&mut handle, &file_results)?; } - let violations: usize = file_results.iter().map(|f| f.violations.len()).sum(); - if fail_on_violations && violations > 0 { let files = file_results.len(); bail!("Found {violations} violation(s) across {files} file(s)"); @@ -161,6 +177,12 @@ pub fn check_and_comment_on_pr(cfg: Config) -> Result<()> { Ok(()) } +fn get_clean_comment_body(file_count: usize) -> String { + format!( + "{COMMENT_HEADER}\n\n✅ 0 violations across {file_count} file(s)" + ) +} + fn get_comment_body(files: &[CheckReport], version: &str) -> String { let violations_count: usize = files.iter().map(|x| x.violations.len()).sum(); let violations_emoji = get_violations_emoji(violations_count); @@ -428,6 +450,12 @@ ALTER TABLE "core_recipe" ADD COLUMN "foo" integer DEFAULT 10; assert_snapshot!(body); } + #[test] + fn generating_clean_comment_no_violations() { + let body = super::get_clean_comment_body(8); + assert_snapshot!(body); + } + /// Ideally the logic won't leave a comment when there are no migrations but /// better safe than sorry #[test] diff --git a/crates/squawk/src/main.rs b/crates/squawk/src/main.rs index 41e89a57..69b91d7b 100644 --- a/crates/squawk/src/main.rs +++ b/crates/squawk/src/main.rs @@ -26,6 +26,9 @@ pub struct UploadToGithubArgs { /// Exits with an error if violations are found #[arg(long)] fail_on_violations: bool, + /// Only posts a report comment on violations + #[arg(long)] + only_comment_on_violations: bool, #[arg(long, env = "SQUAWK_GITHUB_PRIVATE_KEY")] github_private_key: Option, #[arg(long, env = "SQUAWK_GITHUB_PRIVATE_KEY_BASE64")] diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_assume_in_transaction.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_assume_in_transaction.snap index b8a89dea..1a628ab9 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_assume_in_transaction.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_assume_in_transaction.snap @@ -14,6 +14,7 @@ Ok( ), upload_to_github: UploadToGitHubConfig { fail_on_violations: None, + only_comment_on_violations: None, }, }, ), diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_cfg_full.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_cfg_full.snap index cc6e930b..e45130e2 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_cfg_full.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_cfg_full.snap @@ -26,6 +26,7 @@ Ok( ), upload_to_github: UploadToGitHubConfig { fail_on_violations: None, + only_comment_on_violations: None, }, }, ), diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_paths.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_paths.snap index 183c8182..cd23a328 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_paths.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_paths.snap @@ -14,6 +14,7 @@ Ok( assume_in_transaction: None, upload_to_github: UploadToGitHubConfig { fail_on_violations: None, + only_comment_on_violations: None, }, }, ), diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules.snap index 9aabf238..0e92f595 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules.snap @@ -14,6 +14,7 @@ Ok( assume_in_transaction: None, upload_to_github: UploadToGitHubConfig { fail_on_violations: None, + only_comment_on_violations: None, }, }, ), diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules_with_alias.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules_with_alias.snap index 01910f51..33eddb6a 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules_with_alias.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules_with_alias.snap @@ -15,6 +15,7 @@ Ok( assume_in_transaction: None, upload_to_github: UploadToGitHubConfig { fail_on_violations: None, + only_comment_on_violations: None, }, }, ), diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_fail_on_violations.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_fail_on_violations.snap index 994c15fb..83e4b93b 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_fail_on_violations.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_fail_on_violations.snap @@ -14,6 +14,7 @@ Ok( fail_on_violations: Some( true, ), + only_comment_on_violations: None, }, }, ), diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_included_rules.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_included_rules.snap index 1977a09b..7131a556 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_included_rules.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_included_rules.snap @@ -14,6 +14,7 @@ Ok( assume_in_transaction: None, upload_to_github: UploadToGitHubConfig { fail_on_violations: None, + only_comment_on_violations: None, }, }, ), diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_only_comment_on_violations.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_only_comment_on_violations.snap new file mode 100644 index 00000000..5f821f19 --- /dev/null +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_only_comment_on_violations.snap @@ -0,0 +1,21 @@ +--- +source: crates/squawk/src/config.rs +expression: "ConfigFile::parse(Some(squawk_toml.path().to_path_buf()))" +--- +Ok( + Some( + ConfigFile { + excluded_paths: [], + excluded_rules: [], + included_rules: [], + pg_version: None, + assume_in_transaction: None, + upload_to_github: UploadToGitHubConfig { + fail_on_violations: None, + only_comment_on_violations: Some( + true, + ), + }, + }, + ), +) diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_pg_version.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_pg_version.snap index c380224b..7c7fe260 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_pg_version.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_pg_version.snap @@ -20,6 +20,7 @@ Ok( assume_in_transaction: None, upload_to_github: UploadToGitHubConfig { fail_on_violations: None, + only_comment_on_violations: None, }, }, ), diff --git a/crates/squawk/src/snapshots/squawk__github__test_github_comment__generating_clean_comment_no_violations.snap b/crates/squawk/src/snapshots/squawk__github__test_github_comment__generating_clean_comment_no_violations.snap new file mode 100644 index 00000000..0309b320 --- /dev/null +++ b/crates/squawk/src/snapshots/squawk__github__test_github_comment__generating_clean_comment_no_violations.snap @@ -0,0 +1,7 @@ +--- +source: crates/squawk/src/github.rs +expression: body +--- +# Squawk Report + +✅ 0 violations across 8 file(s) From a014504e724fb586eb7a422995d93a704256f3d3 Mon Sep 17 00:00:00 2001 From: Joshua Cold Date: Thu, 21 May 2026 11:33:29 -0600 Subject: [PATCH 2/3] docs(github): update documentation about only_comment_on_violations --- docs/docs/cli.md | 1 + docs/docs/github_app.md | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/docs/docs/cli.md b/docs/docs/cli.md index ea22f9bb..097c7aef 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -149,6 +149,7 @@ excluded_paths = [ ] [upload_to_github] fail_on_violations = true +only_comment_on_violations = true ``` See the [Squawk website](https://squawkhq.com/docs/rules) for documentation on each rule with examples and reasoning. diff --git a/docs/docs/github_app.md b/docs/docs/github_app.md index a33d0fe6..55e4962d 100644 --- a/docs/docs/github_app.md +++ b/docs/docs/github_app.md @@ -161,3 +161,23 @@ To use Squawk as a GitHub App, Squawk needs a corresponding GitHub App so it can +## Only comment on violations + +By default, Squawk posts a PR comment on every run. To suppress the full report +when there are no violations, pass `--only-comment-on-violations` to the +`upload-to-github` subcommand: + +```bash +squawk upload-to-github --only-comment-on-violations example.sql +``` + +Or set it in `.squawk.toml`: + +```toml +[upload_to_github] +only_comment_on_violations = true +``` + +When no violations are found, Squawk will post a brief ✅ summary instead of +the full report. + From 187b6f37ef3c352a046880282506647455112d51 Mon Sep 17 00:00:00 2001 From: Joshua Cold Date: Thu, 21 May 2026 11:38:39 -0600 Subject: [PATCH 3/3] chore(github): linting change --- crates/squawk/src/github.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/crates/squawk/src/github.rs b/crates/squawk/src/github.rs index 53e4e5ea..87130f9e 100644 --- a/crates/squawk/src/github.rs +++ b/crates/squawk/src/github.rs @@ -107,14 +107,13 @@ pub fn check_and_comment_on_pr(cfg: Config) -> Result<()> { fail_on_violations }; - let only_comment_on_violations = - if let Some(only_comment_on_violations_cfg) = - cfg.upload_to_github.only_comment_on_violations - { - only_comment_on_violations_cfg - } else { - only_comment_on_violations - }; + let only_comment_on_violations = if let Some(only_comment_on_violations_cfg) = + cfg.upload_to_github.only_comment_on_violations + { + only_comment_on_violations_cfg + } else { + only_comment_on_violations + }; let github_app = create_gh_app( &github_api_url, @@ -178,9 +177,7 @@ pub fn check_and_comment_on_pr(cfg: Config) -> Result<()> { } fn get_clean_comment_body(file_count: usize) -> String { - format!( - "{COMMENT_HEADER}\n\n✅ 0 violations across {file_count} file(s)" - ) + format!("{COMMENT_HEADER}\n\n✅ 0 violations across {file_count} file(s)") } fn get_comment_body(files: &[CheckReport], version: &str) -> String {