[BUG] LSP plugin extensionToLanguage does not match compound file extensions

Resolved 💬 4 comments Opened Dec 30, 2025 by srlynch1 Closed Feb 14, 2026

LSP plugin support for compound file extensions

Summary

The LSP plugin system's extensionToLanguage configuration does not support compound file extensions like .tfcomponent.hcl, .tfdeploy.hcl, or .tftest.hcl. The extension matching algorithm only extracts the final segment after the last ., causing these files to match .hcl instead of their specific compound extension.

Evidence

terraform-ls Works Correctly (Direct Test)

We tested terraform-ls directly using the LSP protocol, bypassing Claude Code:

=== example.tfcomponent.hcl (languageId: terraform-stack) ===
  Symbols: 2
    - component "vpc"
    - component "database"

=== example.tfdeploy.hcl (languageId: terraform-deploy) ===
  Symbols: 4
    - deployment "development"
    - deployment "staging"
    - deployment "production"
    - orchestrate "rolling"

=== test.tftest.hcl (languageId: terraform-test) ===
  Symbols: 2
    - run "valid_pet_name"
    - run "create_server"

terraform-ls fully supports all compound extensions when the correct languageId is provided.

Claude Code Fails (Extension Matching Issue)

Using the same files through Claude Code's LSP tool:

LSP operation: documentSymbol
File: example.tfcomponent.hcl
Result: "No LSP server available for file type: .hcl"

LSP operation: documentSymbol
File: example.tfdeploy.hcl
Result: "No LSP server available for file type: .hcl"

LSP operation: documentSymbol
File: test.tftest.hcl
Result: "No LSP server available for file type: .hcl"

Plugin Configuration

The plugin configuration is correct:

{
  "lspServers": {
    "terraform": {
      "command": "terraform-ls",
      "args": ["serve"],
      "extensionToLanguage": {
        ".tf": "terraform",
        ".tfvars": "terraform-vars",
        ".tfstack.hcl": "terraform-stack",
        ".tfcomponent.hcl": "terraform-stack",
        ".tfdeploy.hcl": "terraform-deploy",
        ".tftest.hcl": "terraform-test",
        ".hcl": "terraform-stack"
      }
    }
  }
}

Root Cause

Evidence from error message: When LSP fails, Claude Code reports "No LSP server available for file type: .hcl" - confirming it extracts only the final extension segment.

Likely implementation: Claude Code appears to use Node.js path.extname() or similar, which for example.tfcomponent.hcl returns only .hcl.

What happens:

  1. Takes filename example.tfcomponent.hcl
  2. Extracts extension as .hcl (confirmed by error message)
  3. Looks up .hcl in extensionToLanguage - doesn't find the intended .tfcomponent.hcl mapping
  4. Returns error: "No LSP server available for file type: .hcl"

Expected Behavior

The extension matching should try the longest/most-specific match first:

  1. Take filename example.tfcomponent.hcl
  2. Try .tfcomponent.hcl → MATCH → return terraform-stack
  3. Send languageId: "terraform-stack" to terraform-ls
  4. LSP operations work correctly

Use Case

HashiCorp Terraform uses compound extensions for different file types:

| Extension | Purpose | Language ID |
|-----------|---------|-------------|
| .tf | Terraform configuration | terraform |
| .tfvars | Variable definitions | terraform-vars |
| .tftest.hcl | Terraform test files | terraform-test |
| .tfcomponent.hcl | Stacks component definitions | terraform-stack |
| .tfdeploy.hcl | Stacks deployment configs | terraform-deploy |
| .tfstack.hcl | Stacks definitions | terraform-stack |

This pattern is also used by other ecosystems:

  • TypeScript: .spec.ts, .test.ts, .d.ts
  • Angular: .component.ts, .service.ts, .module.ts
  • Vue: .vue.ts

Proposed Solution

Longest Match Wins: Sort extension keys by length (descending) and return the first match:

def get_language_id(filename, extension_map):
    # Sort by length descending
    sorted_extensions = sorted(extension_map.keys(), key=len, reverse=True)

    for ext in sorted_extensions:
        if filename.endswith(ext):
            return extension_map[ext]

    return None

Example:

filename: "example.tfcomponent.hcl"
sorted_extensions: [".tfcomponent.hcl", ".tfstack.hcl", ".tfdeploy.hcl", ".tftest.hcl", ".tfvars", ".hcl", ".tf"]
check: "example.tfcomponent.hcl".endswith(".tfcomponent.hcl") → True
return: "terraform-stack"

Environment

  • Claude Code CLI: Latest
  • OS: macOS Darwin 25.1.0
  • terraform-ls: 0.38.3

Test Script

To reproduce the terraform-ls direct test:

# Full test script available - sends LSP protocol messages directly to terraform-ls
# Confirms the language server works correctly with all compound extensions
# Issue is isolated to Claude Code's extension matching

Related Issues

  • #14803 - LSP plugins not recognized (race condition - fixed)
  • #15619 - Enable LSP tool to connect to installed language servers

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗