Skip to content

Commit 08adc06

Browse files
committed
Fix #5180: Handle empty .roomodes files gracefully
- Detect empty .roomodes files and initialize with proper structure - Handle malformed files by attempting automatic repair - Ensure consistent default content creation across all methods - Maintain backward compatibility with existing functionality - Prevent 'customModes: Required' errors for users with empty files
1 parent 3a8ba27 commit 08adc06

File tree

1 file changed

+52
-6
lines changed

1 file changed

+52
-6
lines changed

src/core/config/CustomModesManager.ts

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ import { t } from "../../i18n"
1616

1717
const ROOMODES_FILENAME = ".roomodes"
1818

19+
/**
20+
* Creates a default .roomodes file content with proper structure
21+
*/
22+
function createDefaultRoomodesContent(): string {
23+
return yaml.stringify(
24+
{
25+
customModes: [],
26+
},
27+
{ lineWidth: 0 },
28+
)
29+
}
30+
1931
export class CustomModesManager {
2032
private static readonly cacheTTL = 10_000
2133

@@ -121,21 +133,30 @@ export class CustomModesManager {
121133
let cleanedContent = stripBom(content)
122134
cleanedContent = this.cleanInvisibleCharacters(cleanedContent)
123135

136+
// Handle empty or whitespace-only content
137+
if (!cleanedContent || cleanedContent.trim() === "") {
138+
console.log(`[CustomModesManager] Empty content detected in ${filePath}`)
139+
return {}
140+
}
141+
124142
try {
125-
return yaml.parse(cleanedContent)
143+
const parsed = yaml.parse(cleanedContent)
144+
// Handle null result from YAML parser (empty document)
145+
return parsed === null ? {} : parsed
126146
} catch (yamlError) {
127147
// For .roomodes files, try JSON as fallback
128148
if (filePath.endsWith(ROOMODES_FILENAME)) {
129149
try {
130150
// Try parsing the original content as JSON (not the cleaned content)
131-
return JSON.parse(content)
151+
const jsonParsed = JSON.parse(content)
152+
return jsonParsed === null ? {} : jsonParsed
132153
} catch (jsonError) {
133154
// JSON also failed, show the original YAML error
134155
const errorMsg = yamlError instanceof Error ? yamlError.message : String(yamlError)
135156
console.error(`[CustomModesManager] Failed to parse YAML from ${filePath}:`, errorMsg)
136157

137158
const lineMatch = errorMsg.match(/at line (\d+)/)
138-
const line = lineMatch ? lineMatch[1] : "unknown"
159+
const line = lineMatch ? lineMatch[1] : "1"
139160
vscode.window.showErrorMessage(t("common:customModes.errors.yamlParseError", { line }))
140161

141162
// Return empty object to prevent duplicate error handling
@@ -154,6 +175,20 @@ export class CustomModesManager {
154175
try {
155176
const content = await fs.readFile(filePath, "utf-8")
156177
const settings = this.parseYamlSafely(content, filePath)
178+
179+
// Handle empty or null content gracefully
180+
if (!settings || (typeof settings === "object" && Object.keys(settings).length === 0)) {
181+
// For .roomodes files, create a default structure if empty
182+
if (filePath.endsWith(ROOMODES_FILENAME)) {
183+
console.log(
184+
`[CustomModesManager] Empty .roomodes file detected, initializing with default structure`,
185+
)
186+
// Initialize the file with proper structure
187+
await fs.writeFile(filePath, createDefaultRoomodesContent(), "utf-8")
188+
}
189+
return []
190+
}
191+
157192
const result = customModesSettingsSchema.safeParse(settings)
158193

159194
if (!result.success) {
@@ -166,6 +201,17 @@ export class CustomModesManager {
166201
.join("\n")
167202

168203
vscode.window.showErrorMessage(t("common:customModes.errors.schemaValidationError", { issues }))
204+
205+
// Try to fix common issues automatically
206+
if (
207+
result.error.issues.some(
208+
(issue) => issue.path.includes("customModes") && issue.code === "invalid_type",
209+
)
210+
) {
211+
console.log(`[CustomModesManager] Attempting to fix malformed .roomodes file`)
212+
await fs.writeFile(filePath, createDefaultRoomodesContent(), "utf-8")
213+
return []
214+
}
169215
}
170216

171217
return []
@@ -216,7 +262,7 @@ export class CustomModesManager {
216262
const fileExists = await fileExistsAtPath(filePath)
217263

218264
if (!fileExists) {
219-
await this.queueWrite(() => fs.writeFile(filePath, yaml.stringify({ customModes: [] }, { lineWidth: 0 })))
265+
await this.queueWrite(() => fs.writeFile(filePath, createDefaultRoomodesContent()))
220266
}
221267

222268
return filePath
@@ -420,7 +466,7 @@ export class CustomModesManager {
420466
content = await fs.readFile(filePath, "utf-8")
421467
} catch (error) {
422468
// File might not exist yet.
423-
content = yaml.stringify({ customModes: [] }, { lineWidth: 0 })
469+
content = createDefaultRoomodesContent()
424470
}
425471

426472
let settings
@@ -491,7 +537,7 @@ export class CustomModesManager {
491537
public async resetCustomModes(): Promise<void> {
492538
try {
493539
const filePath = await this.getCustomModesFilePath()
494-
await fs.writeFile(filePath, yaml.stringify({ customModes: [] }, { lineWidth: 0 }))
540+
await fs.writeFile(filePath, createDefaultRoomodesContent())
495541
await this.context.globalState.update("customModes", [])
496542
this.clearCache()
497543
await this.onUpdate()

0 commit comments

Comments
 (0)