Imported from FarmVivi/Fluxcord (
.claude/skills/verify/SKILL.md). Install upstream withnpx skills add FarmVivi/Fluxcord --skill verify. Copyright stays with the author.
Verify a change (Fluxcord)
Rule from CLAUDE.md: every source change is verified before the task is reported done. A change that was not compiled and tested is not done. Report the real result, including failures.
1. Pick the scope
!git status --short | head -30
| Touched files | Minimum command |
|---|---|
Only fluxcord-api |
mvn -q -pl fluxcord-core -am test (reactor rebuilds api first) |
fluxcord-core (main or test) |
mvn -q test -pl fluxcord-core |
| One test class | mvn -q test -pl fluxcord-core -Dtest=AudioMixerTest (or Class#method) |
A plugin (plugins/*, examples/*, plugin-template) |
mvn -q -pl <module-path> -am package -DskipTests (e.g. -pl plugins/music-plugin -am) |
Root pom.xml, shade config, resource filtering, anything cross-module, or before a commit |
mvn -q clean package (full reactor, runs all tests) |
-q keeps output short; drop it when a failure needs the full log. Maven prints BUILD SUCCESS / BUILD FAILURE at the end — grep for it if output is long. Run in foreground; don't start background builds you then forget.
2. Read the result properly
COMPILATION ERROR→ fix the code, re-run. Never silence with-Dmaven.test.skip.Tests run: N, Failures: F, Errors: E→ openfluxcord-core/target/surefire-reports/<Test>.txtfor the stack trace.- A test that already failed before your change: say so explicitly, don't hide it, don't "fix" it by deleting it without asking.
- Warnings about
Enable-Native-Access/ JDAVE natives on non-glibc hosts are expected; not a failure.
3. Smoke run (--smoke, or whenever runtime behaviour changed)
Compiling doesn't prove plugin loading, JDA wiring, command sync or storage. When the change touches boot, plugin lifecycle, commands, storage or audio:
- Private pre-check (see
CLAUDE.local.mdif present): the local token may be shared with another running instance of the bot; make sure no other instance runs before starting one here. - Ensure
fluxcord-core/run/config.ymlexists with a realdiscord.token(git-ignored). Check presence without printing it (grep -c "token:", or the token length viaawk). If missing, ask the user — never invent or print a token. - Build what the run needs:
mvn -B -ntp -q -pl fluxcord-core -am package -DskipTests(+ the plugin module if a plugin changed), then copy fresh plugin jars intofluxcord-core/run/plugins/(plugins/music-plugin/target/*-shaded.jaror the example jars). - Run from
fluxcord-core/run/, withshutdownsent on stdin after the boot so the clean shutdown path is exercised too (Windows:Stop-Process/taskkillkill without running the JVM shutdown hook, so never judge the shutdown from them):
(cd fluxcord-core/run && mkdir -p logs ( (sleep 40; echo shutdown) | java -Dlogback.configurationFile=logback-dev.xml --enable-native-access=ALL-UNNAMED \ -jar ../target/fluxcord-core-*-shaded.jar > logs/smoke.log 2>&1 )mvn -pl fluxcord-core exec:execstarts the same thing in the foreground with the right working directory, but its stdin is not forwarded — use it for interactive runs, the jar for scripted ones.) - While it runs (in another shell, or by shortening the sleep loop):
curl -s localhost:8081/healthz,/readyz(ok once fully started),/version. - Read
logs/smoke.logignoringWARNING:(JDK) andDEBUGlines. Expected sequence:Loaded plugin: <id>→Synchronizing N global commands→Command service enabled→Plugin fully enabled: <id>→Started in X.Xs!→Global commands synchronized→ (aftershutdown)Shutting down...→Plugin fully disabled→Command service disabled→Goodbye!. AnyERROR/stack trace or a missingGoodbye!is a finding. A missingStarted inafter ~30 s usually means a wrong token or a privileged intent not enabled in the developer portal. - Report what you observed (quote the relevant log lines and timings), not what you expected. Leave
run/as you found it exceptlogs/.
If a smoke run is impossible (no token, no network, another instance already running), state it plainly in the final message.
4. Docs-only change
If nothing under src/, pom.xml or resources changed, no build is needed: say so and run touch .claude/state/last-build so the Stop hook lets the turn end.
Improvement loop (mandatory — see /skill-maintenance)
Before ending a task where this skill was used: fix anything above that turned out wrong; add a dated one-liner to Learnings for anything that cost time (measured timings, flaky tests, needed flags); delete entries that no longer hold. Keep this file < 500 lines.
Learnings
- 2026-09-19: Initial version; commands taken from CLAUDE.md / root pom (
defaultGoal=clean package). - 2026-09-20: Measured on the Windows workstation: full
mvn -B -ntp verify≈ 35-40 s warm; a single core test class ≈ 15 s (mostly Maven startup).-ntpsilences the transfer-progress noise. - 2026-09-20: IntelliJ holds locks on
target/while it re-indexes/compiles (e.g. right after agit stash), makingmvn cleanfail with "Failed to clean project". Wait a few seconds or pass-Dmaven.clean.failOnError=false; never kill the IDE java processes. - 2026-09-20: A test that passes alone but fails in the full suite is usually a race in production code, not test order — treat it as a bug (see
fluxcord-storagelearnings for theFileDataStorage.close()case). - 2026-09-20:
exec:javaignored<workingDirectory>(in-process goal): the bot started at the repo root and createdconfig.yml+logs/there. Switched the pom toexec:exec; if strayconfig.yml/logs/appear at the root, that is the symptom. First measured smoke run: boot 4.5-5 s, 15 global commands synced, clean shutdown < 1 s. - 2026-09-20: CI now runs
mvn -B -ntp verify(.github/workflows/ci.yml) on push/PR; JaCoCo reports land in**/target/site/jacoco/(core coverage 9 % at start). - 2026-09-20: The git remote is named
github(notorigin):git push github develop.gh run watch <id> --exit-statusis a convenient way to wait for CI after a push. - 2026-09-20: A 0-byte
target/jacoco.execwith a green build means the surefire fork was killed before JaCoCo's shutdown hook ran: look fortarget/surefire-reports/*.dumpstream("Surefire is going to kill self fork JVM. The exit has elapsed 30 seconds after System.exit(0)"). Cause here: a test readSystem.in(the realConsoleCommandServiceinsideFluxcordRuntimeTest), which is surefire's command channel. Never let production code under test touchSystem.in; inject the stream. - 2026-09-22: For a plugin module use
mvn -o clean test -pl plugins/music-plugin(install the api first withmvn -o clean install -pl fluxcord-api -DskipTestsafter changing it). Withoutclean, an incremental test-compile fails withcannot access PluginDataStorageAdapter/cannot find symbol: method getLanguage()/CommandContext cannot be converted to CommandContext, although the installed api jar contains those classes — the errors are a stale incremental state, not a missing dependency.-amdoes not help. - 2026-09-22: Application logging is off during tests (
src/test/resources/logback-test.xmlin core and music-plugin). Many tests exercise error paths on purpose, and their WARN/ERROR lines used to fill the build output — grepping a CI log for "ERROR" returned ~100 expected lines and hid the real failure. Turn them back on for a debugging session withmvn test -Dfluxcord.test.log.level=DEBUG. A genuine failure is in the surefire report, not in the console log. - 2026-09-22: Mockito forbids creating and stubbing a mock inside an ongoing
when(...):when(player.getPlayingTrack()).thenReturn(track("a"))wheretrack()itself stubs fails at runtime withUnfinishedStubbingException, pointing at the setup line of an unrelated test. Build the inner mock into a local first. Same trap with a helper returning a stubbed mock (when(plugin.getLanguage()).thenReturn(languageAdapter())). - 2026-09-20: Coverage for SonarCloud is the aggregate
fluxcord-core/target/site/jacoco-aggregate/jacoco.xml(api + core), built bymvn verify -pl fluxcord-core -am; the per-modulesite/jacoco/jacoco.xmlshows api at 0 % by construction. SonarCloud itself is readable through the SonarQube MCP server (search_sonar_issues_in_projects,search_files_by_coverage, project keyFarmVivi_fluxcord, branchdevelop) when the user has it configured.
Known issues / open questions
- Confirm
mvn -q -pl fluxcord-core -am testpicks up api changes without a priorinstall(it should, same reactor). - 2026-09-20:
java.lang.Error: Unresolved compilation problemin a surefire run means the IDE (ECJ) wrote stale classes intotarget/test-classes;rm -rf fluxcord-core/target/test-classes(or aclean) beforemvn test -pl fluxcord-core. - 2026-09-20:
cannot access X / class file for X not foundwhile compiling core tests = the IDE (ECJ) overwrotefluxcord-api/target/classesor the~/.m2api jar is stale. Usemvn -q test -pl fluxcord-core -am ...(builds the api first) ormvn -q install -pl fluxcord-api -DskipTests. A running bot locksfluxcord-core/target/*-shaded.jar: stop it beforemvn clean.
Learnings (2026-09-26)
- The smoke run earns its place. 724 unit tests said nothing about two real i18n misses and a misleading log line; one local run with
DEBUGsurfaced all three. After adding commands to a plugin, grep the smoke log forno translation for—PluginLanguageAdapterwarns on every miss since 2026-09-26 — and read the synchronised command count (Synchronizing N global commands). - Do not trust a log line's arrow.
getString key='x' -> 'ns:x'looked like a failed lookup for every plugin; it was the adapter printing the key it was about to look up. Read the code that emits a suspicious log line before concluding anything from it — and when a log invites that misreading, fix the log. - Assert on every scripted edit. A
str.replacethat matches nothing returns the string unchanged and the script still prints "patched"; a test then fails for a reason that has nothing to do with the code. Every scripted replacement in a file mustassertits pattern was found, or use the Edit tool. - Writing Java character literals through a shell heredoc is a trap (
'''arrives as''). Prefer a formulation that needs no escaping —value.replace("''", "").contains("'")instead of counting chars — or use the Edit tool. - 2026-09-26 (Mockito in
fluxcord-api): a module whose tests mock its own classes cannot use Mockito's default inline mock maker together with JaCoCo — both transform the same bytecode, and on JDK 25 the Byte Buddy agent's self-attach fails, so every mock errors with "Could not initialize inline Byte Buddy mock maker" underverifywhile passing under plaintest. Fix: asrc/test/resources/mockito-extensions/org.mockito.plugins.MockMakerfile containingmock-maker-subclass. Cost: final classes and final methods can no longer be mocked, which is a nudge in the right direction anyway (use the real object over a mocked backend). Other modules are unaffected. - 2026-09-26: a surefire run that dies with "The forked VM terminated without properly saying goodbye" is worth reading the dumpstream for before blaming the code: here it was
insufficient memory for the Java Runtime Environment— the machine's commit charge was at 34 of 35 GB. CheckGet-CimInstance Win32_OperatingSystembefore debugging a phantom test failure. - 2026-09-26 (flaky, caught by CI again):
Mockito.reset(spy)drops the stubs, not just the recorded invocations. InMusicManagerTestaresetbetween two loads restored the realloadItemOrdered, whose asynchronous callback then queued a track behind the test's back — green locally, red on the faster runner. Almost everyresetis avoidable: verify with a matcher specific to the call (eq(query)) and each invocation is counted on its own. Treat aresetin a test as a smell, and never reset a spy whose stubs are what make the test deterministic. - 2026-09-27:
mvn -pl plugins/<x> testresolvesfluxcord-apifrom~/.m2, not from the working tree, so a plugin test can silently run against a stale api jar. It cost four confusing failures:PcmAudioequality andtoStringlooked unimplemented because the installed snapshot predated them. Whenever a plugin test touches api types, put the module in the reactor -mvn -o -pl fluxcord-api,plugins/<x> test- which is also far cheaper than-am(that drags in core's ~900 tests).
