{"record":{"id":"3b8c56f258fdbd5b","repo":"siyuan-note/siyuan","slug":"write-file-failed","errorCode":null,"errorMessage":"write file failed: ","messagePattern":"write file failed: ","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/httprequest.go","lineNumber":368,"sourceCode":"\tif resp.ContentLength > maxReadBytes {\n\t\treturn statusCode, contentType, \"\", errors.New(\"response too large\")\n\t}\n\n\trespBody, rerr := io.ReadAll(io.LimitReader(resp.Body, maxReadBytes))\n\tif rerr != nil {\n\t\treturn statusCode, contentType, \"\", errors.New(\"read body failed: \" + rerr.Error())\n\t}\n\n\t// 二进制响应落盘，返回文件路径，供智能体按需进一步处理。\n\tif !isTextContentType(contentType) {\n\t\timportDir := filepath.Join(TempDir, \"import\")\n\t\tif merr := os.MkdirAll(importDir, 0755); merr != nil {\n\t\t\treturn statusCode, contentType, \"\", errors.New(\"create import dir failed: \" + merr.Error())\n\t\t}\n\t\tfilename := extractFilename(rawURL, contentType)\n\t\tfilePath := filepath.Join(importDir, filename)\n\t\tif werr := os.WriteFile(filePath, respBody, 0644); werr != nil {\n\t\t\treturn statusCode, contentType, \"\", errors.New(\"write file failed: \" + werr.Error())\n\t\t}\n\t\treturn statusCode, contentType, fmt.Sprintf(\"Saved to: %s (%d bytes)\", filePath, len(respBody)), nil\n\t}\n\n\treturn statusCode, contentType, truncateRunes(string(respBody), maxHTTPRequestChars), nil\n}\n\n// isTextContentType 判断 Content-Type 是否为可直接展示给智能体的文本类响应。\n// 覆盖 text/*、application/json、application/xml、application/*+json 等。\nfunc isTextContentType(contentType string) bool {\n\tct := strings.ToLower(strings.TrimSpace(strings.SplitN(contentType, \";\", 2)[0]))\n\tif ct == \"\" {\n\t\treturn false\n\t}\n\tif strings.HasPrefix(ct, \"text/\") {\n\t\treturn true\n\t}\n\tswitch ct {","sourceCodeStart":350,"sourceCodeEnd":386,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/util/httprequest.go#L350-L386","documentation":"For binary (non-text) HTTP responses, HTTPRequest saves the already-read body bytes to <TempDir>/import/<filename> with os.WriteFile(filePath, respBody, 0644). If the file write fails, the error is wrapped as \"write file failed: <cause>\". The directory was created successfully, so failures here are usually about the specific file: permissions, disk space, or name collisions with unwritable entries.","triggerScenarios":"HTTPRequest() with a binary Content-Type where os.WriteFile to <TempDir>/import/<extracted filename> fails — disk full, permission denied, the target path exists as a directory, or a filename extracted from URL/Content-Type contains path-hostile content.","commonSituations":"Full disk after a large download, another process holding the file with restrictive locks/ACLs, an 'import' directory whose files were made read-only, or servers sending Content-Disposition/URLs that extract to unusual filenames.","solutions":["Read the wrapped cause after \"write file failed: \" to distinguish ENOSPC (free disk space) from EACCES/EPERM (permissions)","Free disk space on the volume holding the workspace temp directory if the cause is no-space","Check ownership/permissions of the <TempDir>/import directory and remove conflicting entries with the same name","Retry the request; if a specific server's filename is the problem, note the URL and inspect the extracted filename in <TempDir>/import"],"exampleFix":"// before\n$ df -h <workspace>/temp   # check space\n$ ls -la <workspace>/temp/import\n// after: free space or fix perms\n$ chmod u+w <workspace>/temp/import\n$ rm -rf <workspace>/temp/import/<conflicting-dir>","handlingStrategy":"validation","validationCode":"importDir := filepath.Join(util.TempDir, \"import\")\nif stat, err := os.Stat(importDir); err != nil || !stat.IsDir() {\n    return errors.New(\"import dir missing or not a directory\")\n}\n// check writability\nprobe := filepath.Join(importDir, \".write-probe\")\nif err := os.WriteFile(probe, nil, 0644); err != nil {\n    return fmt.Errorf(\"import dir not writable: %w\", err)\n}\nos.Remove(probe)","typeGuard":null,"tryCatchPattern":"status, ct, text, err := util.HTTPRequest(\"GET\", binaryURL, nil, \"\")\nif err != nil && strings.Contains(err.Error(), \"write file failed\") {\n    if strings.Contains(err.Error(), \"no space left\") {\n        return errors.New(\"free disk space before downloading binary responses\")\n    }\n    return fmt.Errorf(\"check permissions on %s: %w\", filepath.Join(util.TempDir, \"import\"), err)\n}","preventionTips":["Monitor free disk space on the workspace volume; binary responses can be up to 10 MB each","Keep the <TempDir>/import directory writable by the SiYuan process user","Clear stale/conflicting entries from the import directory periodically"],"tags":["filesystem","file-write","permissions","go"],"backgroundTag":"file-write-failed","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}