Purpose
This skill covers the complete Vale linting workflow for InfluxData documentation, including running Vale, understanding the rule configuration, adding vocabulary terms, and creating custom rules.
Use this skill when:
- Running Vale style checks on documentation
- Debugging Vale warnings or errors
- Adding terms to the vocabulary (accept/ignore lists)
- Creating or modifying custom Vale rules
- Understanding why certain patterns are flagged
Quick Reference
# Run Vale on specific files
.ci/vale/vale.sh --config=.vale.ini content/influxdb3/core/**/*.md
Run with minimum alert level
.ci/vale/vale.sh --config=.vale.ini --minAlertLevel=warning content/path/
Sync Vale packages (after .vale.ini changes).ci/vale/vale.sh sync
Show Vale configuration.ci/vale/vale.sh ls-config
## Part 1: How Vale Runs
### Execution via `.ci/vale/vale.sh`
The wrapper script `.ci/vale/vale.sh` runs Vale using:
1. **Local binary** (preferred) — if `vale` is installed and version >= 3.x
2. **Docker fallback** — `jdkato/vale:v${VALE_VERSION}` (pinned version),
only if the Docker daemon is running
3. **Graceful skip** — if neither is available, the wrapper prints a warning
and exits 0 so other pre-commit hooks still run
```bash
# The wrapper handles binary vs Docker automatically
.ci/vale/vale.sh --config=.vale.ini content/path/
# In CI, the pr-vale-check.yml workflow installs the Vale binary
# directly (reads version from vale.sh), so Docker is not needed.
Sandboxed agent sessions (no Docker daemon, GitHub release downloads
blocked): Vale can't run locally, so the wrapper skips it (exit 0) with a
warning. Do NOT use git commit --no-verify — that bypasses all hooks, not
just Vale. Let the commit proceed normally; the pr-vale-check.yml workflow
is the authoritative gate and blocks merge on errors. It sets VALE_STRICT=1,
which makes the wrapper fail instead of skip if Vale is ever unavailable in
CI. To run Vale in a sandbox anyway, ask an environment admin to allowlist
the Vale release host (github.com/vale-cli/vale) in the environment's
network policy.
Critical limitation: Only files inside the repository are accessible when using Docker fallback. Files in /tmp or other external paths will silently fail.
macOS note: The CI script .github/scripts/vale-check.sh uses declare -A (associative arrays) which requires bash 4+. macOS ships bash 3.2. Use /opt/homebrew/bin/bash or run tests in CI instead.
Configuration Files
| File | Purpose |
|---|---|
.vale.ini |
Main configuration (styles, packages, rule toggles) |
.ci/vale/styles/InfluxDataDocs/ |
Custom rules for InfluxData docs |
.ci/vale/styles/config/vocabularies/InfluxDataDocs/ |
Vocabulary (accept/reject terms) |
content/*/.vale.ini |
Product-specific overrides |
Part 2: Understanding Vale Rules
Rule Sources
Vale uses multiple rule sources, configured in .vale.ini:
BasedOnStyles = Vale, InfluxDataDocs, Google, write-good
- Vale - Built-in rules (Spelling, Terms, Repetition)
- InfluxDataDocs - Custom rules for this repo
- Google - Google Developer Documentation Style Guide
- write-good - Plain English suggestions
Disabled Rules (and Why)
Rules are disabled in two categories across .vale.ini and all product configs:
Mechanical rules disabled (replaced by custom equivalents or incompatible with InfluxDB syntax):
| Rule | Reason |
|---|---|
Google.Acronyms |
Custom InfluxDataDocs.Acronyms handles this |
Google.DateFormat |
Custom InfluxDataDocs.DateFormat handles this |
Google.Ellipses |
Custom InfluxDataDocs.Ellipses handles this |
Google.Headings |
Too strict for technical doc headings |
Google.WordList |
Custom InfluxDataDocs.WordList handles this |
Google.Units |
Flags InfluxDB duration literals (30d, 24h); custom InfluxDataDocs.Units checks byte units only |
Vale.Spelling |
Custom InfluxDataDocs.Spelling handles this |
Vale.Terms |
False positives from URLs, file paths, and code |
Style rules disabled (high false-positive rate in technical docs):
| Rule | Reason |
|---|---|
Google.Contractions |
Not relevant to InfluxData style |
Google.FirstPerson |
Tutorials use "I" intentionally |
Google.Passive |
Technical docs use passive voice legitimately |
Google.We |
"We recommend" is standard in docs |
Google.Will |
Future tense is standard in docs |
write-good.Cliches |
High false positive rate |
write-good.Passive |
Duplicate of Google.Passive concern |
write-good.So |
Starting with "So" is fine |
write-good.ThereIs |
Often the clearest phrasing |
write-good.TooWordy |
Flags legitimate terms: aggregate, expiration, multiple |
write-good.Weasel |
Context-dependent, better handled during content review |
Active Custom Rules
| Rule | Purpose |
|---|---|
InfluxDataDocs.Spelling |
Spell checking with technical term exclusions |
InfluxDataDocs.Units |
Byte units only (allows duration literals) |
InfluxDataDocs.WordList |
Terminology standards (admin → administrator) |
InfluxDataDocs.Capitalization |
Heading case rules |
InfluxDataDocs.Branding |
Product name consistency |
Part 3: Adding Vocabulary Terms
Accept List (Technical Terms)
Add terms to .ci/vale/styles/config/vocabularies/InfluxDataDocs/accept.txt:
# Case-insensitive by default
CPUs
systemd
preconfigured
# Regex patterns for variations
[Dd]ownsampl(e|ed|es|ing)
subprocess(es)?
# Exact case matching (regex)
(?i)InfluxQL
Ignore List (Spelling Only)
Add terms to .ci/vale/styles/InfluxDataDocs/Terms/ignore.txt:
# Simple words (case-insensitive)
cgroup
humantime
deadman
# The ignore.txt is referenced by Spelling.yml
Key difference in this repo:
accept.txt- Terms that are part of the shared spelling vocabulary (viaVocab = InfluxDataDocs). In this repository, it does not currently create substitution rules becauseVale.Termsis disabled in.vale.ini. IfVale.Termsis enabled in the future, these terms may also drive substitution behavior.ignore.txt- Additional terms to skip in spell checking only (an ignore list layered on top of the shared vocabulary; no substitution rules).
When to Use Each
| Scenario | File |
|---|---|
| Technical term that's spelled correctly | ignore.txt |
| Preferred capitalization (API, CLI) | accept.txt |
| Product name with specific casing | accept.txt |
| Variable name appearing in prose | ignore.txt |
Part 4: Creating Custom Rules
This section covers where rules live in this repo and shows two real examples. For the rule-authoring deep dive — rule types, the regexp2 engine, PCRE lookarounds, and testing patterns in isolation — use the vale-rule-config skill.
Rule File Location
Custom rules go in .ci/vale/styles/InfluxDataDocs/:
.ci/vale/styles/InfluxDataDocs/
├── Acronyms.yml
├── Branding.yml
├── Capitalization.yml
├── Ellipses.yml
├── Spelling.yml
├── Units.yml # Custom: allows duration literals
├── WordList.yml
└── Terms/
├── ignore.txt
└── query-functions.txt
Example: Custom Units Rule
This rule validates byte units while allowing InfluxDB duration literals:
# .ci/vale/styles/InfluxDataDocs/Units.yml
extends: existence
message: "Put a nonbreaking space between the number and the unit in '%s'."
link: "https://developers.google.com/style/units-of-measure"
nonword: true
level: warning
# Only check byte units. Duration units (ns, ms, s, min, h, d) are excluded
# because InfluxDB duration literals use no space (e.g., 30d, 24h, 1h).
tokens:
- \b\d+(?:B|kB|MB|GB|TB|PB)
Example: Spelling Rule with Scope Exclusions
# .ci/vale/styles/InfluxDataDocs/Spelling.yml
extends: spelling
message: "Did you really mean '%s'?"
level: warning
# Exclude from spell checking:
scope:
- ~code # Fenced code blocks
- ~raw # Inline code
- ~table.header
- ~table.cell
ignore:
- InfluxDataDocs/Terms/ignore.txt
- InfluxDataDocs/Terms/query-functions.txt
filters:
# Ignore URL paths
- '/[a-zA-Z0-9/_\-\.\{\}]+'
# Ignore full URLs
- 'https?://[^\s\)\]>"]+'
Part 5: Debugging Vale Issues
Common Problems
"0 errors in stdin"
- File is outside the repository (Docker can't access it)
- Solution: Use files inside the repo for testing
Rule not triggering
- Check if rule is disabled in
.vale.ini - Verify rule file has valid YAML syntax
- Run
vale syncafter adding new packages
False positives in URLs/code
- Add patterns to
TokenIgnoresin.vale.ini - Add scope exclusions (
~code,~raw) to the rule - Add terms to
ignore.txt
Debugging Commands
# Show all configuration
.ci/vale/vale.sh ls-config
# Validate YAML syntax
node -e "require('js-yaml').load(require('fs').readFileSync('path/to/rule.yml'))"
# Test specific file
.ci/vale/vale.sh --config=.vale.ini --minAlertLevel=suggestion path/to/file.md
Part 6: Vale Cannot Inspect URLs
TokenIgnores in .vale.ini strips all URLs before any rules run:
TokenIgnores = https?://[^\s\)\]>"]+
This means no Vale rule can match URL content. An earlier attempt to create a SupportLink.yml rule to validate support.influxdata.com URL patterns failed for this reason — the URLs were stripped before the rule could see them. Support URL validation uses a separate shell script (.ci/scripts/check-support-links.sh) instead.
Keep this in mind when designing rules: if the pattern to match is inside a URL, use a shell script or pre-commit hook, not a Vale rule.
Part 7: TokenIgnores vs Rule Filters
TokenIgnores (in .vale.ini)
Applied globally to all rules. Matches whole tokens:
TokenIgnores = /[a-zA-Z0-9/_\-\.]+, \
https?://[^\s\)\]>"]+, \
`[^`]+`
Limitation: Cannot match substrings within words. If "api" appears in /api/v3/write, the URL pattern must match the entire URL to exclude it.
Rule Filters (in rule YAML)
Applied to specific rules. Can match patterns within text:
filters:
- '[Ss]erverless' # Allow both cases
- '/[a-zA-Z0-9/_\-\.\{\}]+' # URL paths
When to Use Each
| Use Case | Approach |
|---|---|
| Exclude entire URLs from all rules | TokenIgnores |
| Exclude inline code from all rules | TokenIgnores with backtick pattern |
| Exclude patterns from one rule | Rule-specific filters |
| Skip checking inside code blocks | Rule scope: [~code] |
Part 7: Workflow Examples
Adding a New Technical Term
# 1. Identify the term and its usage
grep -r "systemd" content/
# 2. Add to ignore list (spelling only)
echo "systemd" >> .ci/vale/styles/InfluxDataDocs/Terms/ignore.txt
# 3. Or add to accept.txt (if it should influence Vale.Terms)
echo "systemd" >> .ci/vale/styles/config/vocabularies/InfluxDataDocs/accept.txt
# 4. Test the change
.ci/vale/vale.sh --config=.vale.ini content/path/with/term.md
Creating a Product-Specific Override
[!Important] Product-specific
.vale.inifiles must include the same disabled rules as the root.vale.ini. Rules disabled in the root config are not inherited by product-specific configs. Omitting them re-enables the rules for those products. For example, omittingGoogle.Units = NOcauses duration literals like7d,24hto be flagged as errors in product-specific linting runs.
# 1. Create product-specific .vale.ini
cat > content/influxdb3/cloud-dedicated/.vale.ini << 'EOF'
StylesPath = ../../../.ci/vale/styles
MinAlertLevel = warning
Vocab = InfluxDataDocs
Packages = Google, write-good, Hugo
[*.md]
BasedOnStyles = Vale, InfluxDataDocs, Cloud-Dedicated, Google, write-good
# --- Disabled mechanical rules ---
Google.Acronyms = NO
Google.DateFormat = NO
Google.Ellipses = NO
Google.Headings = NO
Google.WordList = NO
Google.Units = NO
Vale.Spelling = NO
Vale.Terms = NO
# --- Disabled style rules (high false-positive rate in technical docs) ---
Google.Contractions = NO
Google.FirstPerson = NO
Google.Passive = NO
Google.We = NO
Google.Will = NO
write-good.Cliches = NO
write-good.Passive = NO
write-good.So = NO
write-good.ThereIs = NO
write-good.TooWordy = NO
write-good.Weasel = NO
TokenIgnores = /[a-zA-Z0-9/_\-\.]+, \
https?://[^\s\)\]>"]+, \
`[^`]+`
EOF
# 2. Run Vale with product config
.ci/vale/vale.sh --config=content/influxdb3/cloud-dedicated/.vale.ini \
content/influxdb3/cloud-dedicated/**/*.md
Debugging Why a Pattern is Flagged
# 1. Check which rule is triggering
.ci/vale/vale.sh --config=.vale.ini path/to/file.md
# Output shows: InfluxDataDocs.WordList, Vale.Terms, etc.
# 2. Read the rule file
cat .ci/vale/styles/InfluxDataDocs/WordList.yml
# 3. Check if term is in vocabulary
grep -i "term" .ci/vale/styles/config/vocabularies/InfluxDataDocs/*.txt
# 4. Check TokenIgnores patterns
grep TokenIgnores .vale.ini
Checklist: Before Committing Vale Changes
- Ran
vale syncif packages changed - Tested changes on sample files
- Verified no unexpected rules are disabled
- Added comments explaining why rules are disabled
- Kept vocabulary files alphabetized
- Used ignore.txt for spelling-only terms
- Used accept.txt for terms that should influence substitution
Related Files
| File | Purpose |
|---|---|
.vale.ini |
Main configuration |
.vale-instructions.ini |
Config for non-content files (READMEs, AGENTS.md, etc.) |
.ci/vale/vale.sh |
Vale wrapper (local binary or Docker fallback) |
.ci/vale/styles/ |
All Vale style rules |
.ci/scripts/check-support-links.sh |
Support URL validation (can't use Vale — see Part 6) |
.github/scripts/vale-check.sh |
CI script: groups files by product config, runs Vale |
.github/scripts/resolve-shared-content.sh |
CI script: resolves content/shared/* to product pages |
.github/workflows/pr-vale-check.yml |
CI workflow: runs Vale on PR changes |
lefthook.yml |
Pre-commit hooks that run Vale |
DOCS-TESTING.md |
Testing documentation (includes Vale CI section) |