Skip to content

macOS: os.UserConfigDir() != ~/.config/ — config write path broken, sandbox prevents fixing it #4148

@SvenZhao

Description

@SvenZhao

问题描述

一句话:macOS 上 os.UserConfigDir() 返回 ~/Library/Application Support/,但文档和渲染注释全部硬编码为 ~/.config/reasonix/config.toml,导致用户把配置放在 ~/.config/reasonix/ 后完全不生效。

并且沙箱 bash = "enforce" 阻止了从内部修复——形成鸡生蛋死锁。


根因

代码 internal/config/config.go 中:

func userConfigPath() string {
    dir, err := os.UserConfigDir()  // macOS → ~/Library/Application Support
    ...
    return filepath.Join(dir, "reasonix", "config.toml")
}

macOS 上 os.UserConfigDir() 返回 ~/Library/Application Support/,而非 ~/.config/。参见 Go 标准库文档。

连锁反应

1. 文档全部错误

所有以下位置硬编码了 ~/.config/reasonix/config.toml,macOS 上均为错误信息:

  • internal/config/config.go:2 — 注释
  • internal/config/render.go:43 — 渲染到用户配置文件顶部的注释
  • internal/config/config.go:1543UserConfigPath 文档注释
  • internal/config/config.go:1548UserCredentialsPath 文档注释
  • README.md / README.zh-CN.md / docs/MIGRATING.md / docs/SPEC.md

渲染出的 config.toml 顶部注释:

# Resolution order: flag > ./reasonix.toml > ~/.config/reasonix/config.toml > built-in defaults.

macOS 用户看到这个会以为配置放 ~/.config/reasonix/ 就行,但实际上放那里完全不会被读取。

2. 沙箱鸡生蛋死锁

  • 用户把配置放到 ~/.config/reasonix/config.toml(文档说的路径)—— reasonix 不读
  • reasonix 实际读的是 ~/Library/Application Support/reasonix/config.toml——但这个文件是自动生成的,没有 allow_write
  • 没有 allow_write → 沙箱阻止写入任何非 workspace 路径
  • 无法写入 → 无法添加 allow_write 到正确的配置文件

结果:用户根本无法通过自身工具修复沙箱写入权限。

3. 用户实际状态

macOS 上最终出现两个并行目录

~/.config/reasonix/                          ← 用户按文档放的,reasonix 不读
  ├── config.toml      ← 手动写的(有 allow_write ✅)
  ├── REASONIX.md      ← 全局指令 ✅
  └── credentials      ← 密钥 ✅

~/Library/Application Support/reasonix/     ← reasonix 实际读取
  ├── config.toml      ← 自动生成(无 allow_write ❌)
  ├── credentials      ← 密钥 ✅(可能和上面重复)
  ├── cache/
  ├── sessions/
  └── projects/

两个 credentials 文件可能不同步,config.toml 则完全不一致。

4. 跨项目覆盖问题

即使 ~/Library/Application Support/reasonix/config.toml 里写了 allow_write,如果某个项目的 reasonix.toml 有自己的 [sandbox] 段但没有 allow_write,它仍然会覆盖全局配置——因为加载顺序是后加载的项目配置覆盖前面的用户配置。


复现步骤

package main

import (
    "fmt"
    "os"
    "path/filepath"
)

func main() {
    cfgDir, _ := os.UserConfigDir()
    fmt.Println("os.UserConfigDir():", cfgDir)
    fmt.Println("Reasonix user config path used by code:",
        filepath.Join(cfgDir, "reasonix", "config.toml"))
    fmt.Println("Reasonix user config path per docs:",
        "~/.config/reasonix/config.toml")
}

macOS 输出:

os.UserConfigDir(): /Users/sven/Library/Application Support
Reasonix user config path used by code: /Users/sven/Library/Application Support/reasonix/config.toml
Reasonix user config path per docs: ~/.config/reasonix/config.toml

修复建议

方案 A(推荐):添加 ~/.config/reasonix/ fallback

userConfigPath() 中,如果 os.UserConfigDir() 返回的不是 ~/.config/ 路径,额外检查 $HOME/.config/reasonix/config.toml 是否存在:

func userConfigPath() string {
    dir, err := os.UserConfigDir()
    if err != nil {
        return ""
    }
    path := filepath.Join(dir, "reasonix", "config.toml")
    // macOS: os.UserConfigDir() returns ~/Library/Application Support
    // but users may have configured ~/.config/reasonix/ per the docs.
    // Check for it as a fallback so documented path actually works.
    if _, err := os.Stat(path); os.IsNotExist(err) {
        if home, _ := os.UserHomeDir(); home != "" {
            alt := filepath.Join(home, ".config", "reasonix", "config.toml")
            if _, err := os.Stat(alt); err == nil {
                return alt
            }
        }
    }
    return path
}

同样逻辑应该应用到 UserCredentialsPath()UserConfigPath()SessionDirForRoot()ProjectSessionDir() 等所有使用 os.UserConfigDir() 拼接路径的函数。

方案 B:只修文档

把所有硬编码的 ~/.config/reasonix/ 改为描述性语言,缺点是 macOS 用户仍然需要自己去猜正确的路径。

方案 C:支持 XDG_CONFIG_HOME 环境变量

XDG_CONFIG_HOME 环境变量优先于 os.UserConfigDir(),这样用户可以显式指定。


受影响的文件

  • internal/config/config.gouserConfigPath(), UserConfigPath(), UserCredentialsPath(), 注释
  • internal/config/render.go:43 — 渲染注释
  • README.md, README.zh-CN.md, docs/MIGRATING.md, docs/SPEC.md — 文档

Metadata

Metadata

Assignees

No one assigned

    Labels

    configConfiguration & setup (internal/config)macosmacOS-specific

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions