Skip to content

[Windsurf] Rules loaded 2x-9x per response with nested git repos (token waste) #305

@a343

Description

@a343

When a folder containing multiple git repositories is opened as a single workspace in Windsurf, rules defined in .windsurf/rules/ are loaded multiple times per Cascade response — once per detected git root. This causes escalating duplication (2x, 3x, up to 9x for glob rules), wasting ~5,250 tokens per response (up to 15,000+ with glob rules).

Related Issues (NOT duplicates)

After investigation, found related but different issues:

Issue Problem Why this is different
#194 Rules not detected when git repo is in parent folder Rules work in our case, but are duplicated
#157 .windsurfrules not functioning at all Rules function correctly, just loaded N times
Reddit Rules don't appear in customizations UI Rules visible, but multiply per git root
Key distinction: This is a token efficiency/optimization bug, not a functionality bug. Rules work perfectly — they're just loaded redundantly, wasting context window.

Environment

Windsurf Version: Latest (as of March 2026)
OS: Windows 11
Workspace layout: Single root folder (C:\workspace-emp) containing 22+ git repositories as subfolders
Rules location: C:\workspace-emp.windsurf\rules/ (11 rule files)
No nested .windsurf/rules/: Subfolders do NOT have their own rules

Steps to Reproduce

Create a folder (e.g., C:\workspace-emp) containing multiple git repos as subfolders:
C:\workspace-emp/
├── .windsurf/rules/
│ ├── proactive-intelligence.md (trigger: always_on)
│ ├── microservice-patterns.md (trigger: model_decision)
│ ├── java-secure-coding.md (trigger: model_decision)
│ ├── spring-boot-observability.md (trigger: model_decision)
│ ├── rest-controller-conventions.md (trigger: glob)
│ └── ... (6 more rules)
├── repo-a/ (.git inside)
├── repo-b/ (.git inside)
└── repo-c/ (.git inside)

Open C:\workspace-emp in Windsurf as a single workspace
Open files from one of the sub-repos (e.g., repo-a/src/...)
Send a message to Cascade
Observe that rules are loaded multiple times per response (visible in system prompt metadata)

Expected Behavior

Each rule from .windsurf/rules/ should be loaded exactly once per Cascade response, regardless of how many git roots exist inside the workspace folder.

Actual Behavior

Rules are loaded 2x or 3x per response. The multiplicity correlates with the number of workspace contexts Windsurf internally creates from detected git roots.

Evidence

Tracking Method
We tracked rule loading using a post_cascade_response hook over 11 days (~142 Cascade responses). The hook logs which rules were loaded per response to .windsurf/hooks/rules_usage.csv.

Escalation Over Time
Period Multiplicity per rule Lines per response
Mar 20-23 1x (correct) ~3
Mar 24-26 2x ~9
Mar 26-27 Mix of 2x and 3x ~9-13
Mar 30 Consistently 3x ~13
Multiplicity grows within a single conversation
The same trajectory (b13e992c) started at 2x and escalated to 3x within the same Cascade conversation:

First response — 2x

2026-03-26T10:31:23,b13e992c,Always On,proactive-intelligence.md
2026-03-26T10:31:23,b13e992c,Always On,proactive-intelligence.md

Later response in SAME conversation — 3x

2026-03-26T12:50:37,b13e992c,Always On,proactive-intelligence.md
2026-03-26T12:50:37,b13e992c,Always On,proactive-intelligence.md
2026-03-26T12:50:37,b13e992c,Always On,proactive-intelligence.md
Glob rules multiply even worse (up to 9x)
Glob-triggered rules multiply by workspace context AND by number of matching files:

9 copies of the same rule in a single response

2026-03-27T11:15:02,003e3410,Glob,rest-controller-conventions.md
2026-03-27T11:15:02,003e3410,Glob,rest-controller-conventions.md
2026-03-27T11:15:02,003e3410,Glob,rest-controller-conventions.md
2026-03-27T11:15:02,003e3410,Glob,rest-controller-conventions.md
2026-03-27T11:15:02,003e3410,Glob,rest-controller-conventions.md
2026-03-27T11:15:02,003e3410,Glob,rest-controller-conventions.md
2026-03-27T11:15:02,003e3410,Glob,rest-controller-conventions.md
2026-03-27T11:15:02,003e3410,Glob,rest-controller-conventions.md
2026-03-27T11:15:02,003e3410,Glob,rest-controller-conventions.md
Global rules are NOT affected
global_rules.md (type: Global) always appears exactly 1x per response — correctly deduplicated. This proves deduplication IS possible and working for global rules.

Token Impact
At 3x duplication (current state), each response wastes approximately 5,250 extra tokens on duplicate rule content:

Rule Size Extra copies Wasted tokens
proactive-intelligence.md 1,598 B 2 ~800
java-secure-coding.md 2,717 B 2 ~1,400
microservice-patterns.md 2,104 B 2 ~1,050
spring-boot-observability.md 4,076 B 2 ~2,000
Total per response ~5,250
With glob rules at 9x, spikes can reach 15,000+ wasted tokens per response.

Root Cause Hypothesis
Windsurf detects git repositories inside the workspace folder and creates separate workspace contexts for each. The system prompt metadata confirms this:

The USER has 2 active workspaces:
C:\workspace-emp -> C:/workspace-emp
C:\workspace-emp\repo-a -> org/repo-a (git root: C:\workspace-emp\repo-a)
Rules from .windsurf/rules/ are resolved independently per workspace context. Since the rules directory is at the parent level, both contexts find and load the same rules, causing duplication.

Suggested Fix
Deduplicate rules by their absolute file path before injecting into the system prompt. If two workspace contexts resolve the same .windsurf/rules/some-rule.md file, it should only be included once.

Implementation approach:

Collect all rules from all workspace contexts
Deduplicate by absolute file path
Inject unique rules into system prompt
This is the same approach already working correctly for global_rules.md.

Hook Script for Reproduction (Optional)
To reproduce the issue, create .windsurf/hooks.json:

{
"hooks": {
"post_cascade_response": {
"command": "python .windsurf/hooks/track_rules.py"
}
}
}
And the tracking script .windsurf/hooks/track_rules.py:

import os, sys, re, datetime, csv

trajectory_id = os.environ.get('CASCADE_TRAJECTORY_ID', 'unknown')
log_file = '.windsurf/hooks/rules_usage.csv'

def main():
input_text = sys.stdin.read()
# Parse rules from system prompt
pattern = r'(?:Always On|Model Decision|Glob|Manual),([^\n]+.md)'
matches = re.findall(pattern, input_text)

timestamp = datetime.datetime.now().isoformat()

with open(log_file, 'a', newline='') as f:
    writer = csv.writer(f)
    for match in matches:
        rule_name = match.strip()
        trigger_type = 'Always On' if 'Always On' in input_text.split(rule_name)[0][-50:] else 'Other'
        writer.writerow([timestamp, trajectory_id, trigger_type, rule_name])

if name == 'main':
main()
/cc @codeium-team

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions