Skip to content

Convert nextflow lint to a Stop hook (supersedes #5) - #10

Closed
edmundmiller wants to merge 3 commits into
masterfrom
claude/nextflow-lint-hook-NM8lc
Closed

Convert nextflow lint to a Stop hook (supersedes #5)#10
edmundmiller wants to merge 3 commits into
masterfrom
claude/nextflow-lint-hook-NM8lc

Conversation

@edmundmiller

@edmundmiller edmundmiller commented Jun 16, 2026

Copy link
Copy Markdown
Member

Supersedes #5 (closed). Adds a nextflow lint integration as an if-scoped, async PostToolUse hook, addressing @pditommaso's review on #5.

Design

  • if-scoped PostToolUse (hooks/scripts/nextflow-lint.sh): fires only on .nf/.config edits. The if patterns (Edit(*.nf), Write(*.nf), MultiEdit(*.nf) and the *.config variants) use gitignore semantics, so a bare *.nf matches at any depth (root and nested). One handler per tool×extension since if takes a single pattern.
  • async (asyncRewake): the lint runs in the background — never blocks the edit. On a lint failure it exits 2 and the output is surfaced to Claude as a system reminder to fix, rather than a blocking nag.
  • No state machinery: this replaces the earlier Stop-hook + record-edit companion + per-session state file. The script just extracts the edited file path and lints that one file.
  • Guards: command -v jq, command -v nextflow — skips silently if either is missing (fixes the original macOS-no-jq exit-127 spam).

Addressing the review

@pditommaso's point Resolution
🔴 Missing jq guard command -v jq || exit 0
🔴 N× synchronous JVM stalls per turn async — lint runs off the critical path, never blocks
🔴 Blocking exit 2 interrupts the agent asyncRewake → non-blocking system reminder
🟡 PostToolUse → Stop hook We went further: if-scoped async PostToolUse, no Stop hook or state file

Tradeoff worth a look, @pditommaso

This lints per edit rather than once per turn, so it can still run on a half-finished file mid-composition. Because it's async + non-blocking (a soft system reminder, not exit-2 blocking), a stray intermediate-state reminder is cheap and ignorable — but if you'd prefer strict once-per-turn-on-final-state semantics, that requires the Stop-hook approach (which can't use if). Happy to switch.

jq note

I checked whether the edited path is exposed as an env var so we could drop the jq dependency — it isn't (only on stdin JSON, not interpolatable into command/args). So robust per-file targeting needs jq. The jq-free alternative is to lint the whole project dir each edit, but that's slower and re-surfaces unrelated pre-existing issues. Kept jq (guarded).

Verified locally

  • clean file → exit 0; bad file → exit 2 + stderr; nonexistent path → exit 0; empty path → exit 0; no nextflow → silent; no jq → silent (no spam)
  • shellcheck v0.10.0 clean; claude plugin validate accepts the asyncRewake + if config

Test plan

  • Edit an invalid .nf in Claude Code; confirm the edit isn't blocked and a lint system-reminder arrives shortly after
  • Edit a clean .nf; nothing reported
  • Edit a nested file (workflows/main.nf); confirm the if glob matches
  • macOS without jq: silent

https://claude.ai/code/session_01BDX5Wh7CXx1hXbfJCyBmMN

Rewrite the linter to address the review on the original PostToolUse
implementation:

- Stop hook lints once per turn, on the final file state — one JVM
  startup instead of N, no false positives on half-finished edits.
- PostToolUse companion records edited .nf/.config paths to a per-session
  state file so Stop knows what to check.
- Guard against missing jq (the original silently aborted at exit 127 on
  every edit when jq was absent — common on macOS).
- Guard against re-entry via stop_hook_active so a persistent lint failure
  doesn't trap Claude in an infinite stop loop.
- Skip silently when nextflow isn't installed (preserves the original
  graceful behavior).

https://claude.ai/code/session_01BDX5Wh7CXx1hXbfJCyBmMN
@bentsherman

Copy link
Copy Markdown
Member

This seems more reasonable since it waits for the agent to finish editing. It also seems better than encoding it in the skill and hoping the agent remembers to do it

I wonder if it would be feasible to just run the LSP in the background and have the agent check the LSP diagnostics after every turn. That would eliminate the JVM overhead and you wouldn't need to record the edits because the LSP does that for you

claude added 2 commits June 18, 2026 15:10
The Stop hook blocked the turn from ending (exit 2 prevents stopping),
holding up the user while the JVM started. Mark it asyncRewake: it runs
in the background, the user gets control back immediately, and a lint
failure is surfaced to Claude as a system reminder to fix on the next
turn instead of blocking.

https://claude.ai/code/session_01BDX5Wh7CXx1hXbfJCyBmMN
Replace the Stop hook + PostToolUse record-edit companion + per-session
state file with a single PostToolUse hook scoped by `if` patterns to
.nf/.config files (gitignore-style, matches at any depth). asyncRewake
runs the lint in the background per edit; a failure surfaces to Claude
as a system reminder without blocking. Drops the state-tracking
machinery entirely; the script now just extracts the edited path and
lints that one file.

https://claude.ai/code/session_01BDX5Wh7CXx1hXbfJCyBmMN
@bentsherman

Copy link
Copy Markdown
Member

Related: nextflow-io/language-server#146

We can minimize the JVM overhead in any case by providing a native build

@bentsherman bentsherman mentioned this pull request Jun 18, 2026
4 tasks
@edmundmiller

Copy link
Copy Markdown
Member Author

This seems more reasonable since it waits for the agent to finish editing. It also seems better than encoding it in the skill and hoping the agent remembers to do it

I wonder if it would be feasible to just run the LSP in the background and have the agent check the LSP diagnostics after every turn. That would eliminate the JVM overhead and you wouldn't need to record the edits because the LSP does that for you

https://code.claude.com/docs/en/plugins-reference#lsp-servers

😆

@bentsherman

Copy link
Copy Markdown
Member

Closing in favor of LSP integration (#12 -> #11)

@bentsherman
bentsherman deleted the claude/nextflow-lint-hook-NM8lc branch July 23, 2026 13:11
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.

3 participants