
Beadsbd serve运维手册深度解读从部署、认证到楔入检测与连接预算的完整运营指南【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beads本技术指南以 Beads 开源仓库中的 SERVE_RUNBOOK.md 为骨架结合bd serve的源码实现cmd/bd/serve.go、internal/httpapi/server.go、internal/httpapi/auth.go、internal/httpapi/events_watch.go逐项展开系统讲解如何把 Beads 的 v0 HTTP 服务面bd serve可靠地部署到生产环境。你将掌握显式端口与互斥模型的正确姿势、基于 token 文件的免重启轮换认证、liveness/readiness 探针的正确接法、数据库楔入wedge的三种可告警信号、每个进程约 22 条连接的预算算法以及优雅关停下的歧义 claim 恢复策略——全部以当前仓库实际编译进二进制的常量和行为为准。bd serve向自动化客户端与编排器暴露的是 CLI 同一个工作面只不过以 HTTP 呈现免去了自动化程序每次调用 fork 一个 bd 子进程的启动开销与 stdout 文本解析成本详见 design/bd-serve-v0.md。本手册的每个数字除非另行说明都是 internal/httpapi/server.go 中的编译期常量——它们目前还不是命令行参数但即便未来成为参数线上的 wire 契约也不会变。一、部署端口、互斥与进程形态1.1 显式端口TCP bind 就是唯一的互斥机制默认地址是--addr 127.0.0.1:0即绑定一个临时端口。这适合临时和测试场景——调用方从 stdout 上读一行就能拿到已绑定地址。但要注意临时端口不携带任何互斥两个bd serve进程指向同一个 workspace、都用:0会各自绑定到不同的临时端口、并行运行没有任何枚举手段也没有任何失败发生固定端口则相反第二个进程会在 bind 时直接失败操作系统自带的 address-in-use 错误这正是设计意图也是该命令唯一的互斥机制。bd serve --addr 127.0.0.1:7777无论哪种方式并发 serve 都是数据安全的——claim 的仲裁发生在 SQL server 里而不是 HTTP 进程里。失去固定端口真正失去的是无法知道当前跑着几个 server以及客户端无法找到你本来想让它连的那一个。这一点在 internal/httpapi/server.go 的Listen注释中明确记载没有锁文件、没有 pid 文件、没有发现文件bd serve是运维显式调用的固定端口上的 TCP bind 即互斥客户端是配置地址而不是发现地址。另外--addr的主机部分必须是数字 IP 字面量DNS 名称会被拒绝。对应实现见ValidateBindAddrinternal/httpapi/server.gonet.ParseIP(host)解析失败即报错host must be a numeric IP literal, not a name — use 127.0.0.1 rather than localhost且 Unix socket 完全不支持。1.2 标准流stdout 一行其余全走 stderrstdout 在绑定时刻只输出一行别无他物bd serve: listening on http://127.0.0.1:7777这就是申请临时端口的调用方发现端口的方式——读一行就停。包括请求日志在内的一切其余输出都走 stderr因此两者可以分别重定向。对应实现见 internal/httpapi/server.gofmt.Fprintf(s.stdout, bd serve: listening on http://%s\n, s.Addr())日志则用log.New(cfg.Stderr, bd serve: , ...)创建同文件 server.go。1.3 放在 supervisor 下运行bd serve在前台运行并在SIGHUP、SIGINT、SIGTERM 三者下都能优雅关停。SIGHUP 被特意纳入这一集合关闭一个前台bd serve的终端就能把它停掉而不是留下一个孤儿进程继续占着端口和数据库连接池。因此一个 double-fork 并期望子进程脱离控制终端的 supervisor必须自己把进程从终端脱离或者把它留在 supervisor 下的前台。这对应根命令的信号上下文设计cmd/bd/serve.go中serveListen调用srv.Serve(rootCtx)见 cmd/bd/serve.go。二、认证token 文件的免重启轮换2.1 默认关闭关闭即回环姿态默认无认证而无认证本身就是回环loopback姿态不带任何 auth 标志的bd serve与它一直以来的样子逐字节相同。回环的信任模型就是回环边界本身——与它背后数据库已经依赖的边界是同一个。bd serve --addr 127.0.0.1:7777 --auth-token-file /run/secrets/bd-tokens开启后除GET /healthz外的每个操作都要求Authorization: Bearer token。GET /v0/beads/context并不豁免——它会报告仓库根、beads 目录和数据库名属于敏感信息。/healthz 是唯一豁免因为 kubelet 探针不携带凭据一个对 liveness 返回 401 的端点意味着 pod 无限重启。2.2 文件格式与轮换机制文件每行一个 token所有非空行均被接受。刻意没有--auth-token标志命令行参数对本地所有用户都可通过ps读到。BEADS_SERVE_TOKEN_FILE是环境变量兜底且仅在未传 flag 时生效——该变量在 cmd/bd/serve.go 定义为常量serveTokenFileEnv BEADS_SERVE_TOKEN_FILE并在 resolveServeConfig 中读取internal/httpapi从不读取环境变量因此库、测试和嵌入调用方无法被某个人的 shell 环境变量重配置。轮换就是一次文件重写无需重启新 token 与旧 token 并存写入滚动客户端再删除旧行——增删两者都在约一秒钟内生效。服务器运行期间持续重读该文件全进程 gate 在最多每秒一次stat(2)且接受路径与不匹配路径都会触发检查——这正是撤销revocation能工作而不仅是轮换能工作的原因见 internal/httpapi/auth.go 的Verifyfreshness 在比较之前检查且在每次调用时检查不只在不匹配时检查——否则被删除的 token 永远匹配缓存集合、永不触发重载、永久有效。写入必须原子化临时文件 renameKubernetes secret mount 天然如此。重读失败或读到空文件时保留最后一份有效集合并记录eventauth_reload_error——这样先 truncate 再写的写作者不会把所有客户端锁在门外。对应实现见 auth.go 的reload失败或解析出零 token 时保留 last-good 集合并返回错误stat 缓存只在成功时更新因此下一次越过 gate 的尝试会自动重读并自行恢复。2.3 token 的存储与比较SHA-256 摘要 常量时间token 以SHA-256 摘要形式持有和比较常量时间因此进程不持有任何明文凭据堆转储也不会泄露。实现见 internal/httpapi/auth.goaccepts对每个缓存摘要做subtle.ConstantTimeCompare且无提前退出——耗时不透露哪个 token 最接近。启动时文件就会被打散成摘要集合NewTokenFileAuth是 eager 且严格的不可读、超大、无 token 的文件在启动时即报错而不是让服务器照样绑定。文件大小上限为 1 MiBmaxTokenFileBytes 1 20auth.go超大文件被判定为路径指错了而不是 token 文件。2.4 401 的语义与可观测性拒绝是401code: unauthenticatedWWW-Authenticate: Bearer。detail是固定字符串从不回显所出示的内容缺失 header、错误 scheme、未知 token 三种情况刻意统一成一个 code。每种情况分别记录eventauth_refused且带reasonmissing、reasonmalformed或reasonunknown_token——这正是运维区分配置错误的客户端与过期 token的依据。认证检查运行在数据库信号量之前internal/httpapi/server.go 的route中凭据检查先于 semaphore 获取因此一场拒绝风暴每次只花费一次 SHA-256永远不可能占据已认证客户端在等待的槽位。2.5 token 不是身份token 是授予整个表面的共享秘密。它不是身份、不携带任何 scope因此永远不会让actor变成已认证主体——actor始终是审计线索的调用方自述来源caller-asserted provenance任何被 token 准入的客户端都可以冒充任意名字。这一点在 CLI 上同理任何本地进程都能传任意--actor。claim 的 compare-and-set 因此是并发正确性的栅栏而不是授权边界它保证两个竞争 claimer 不能同时获胜但不保证获胜者究竟是谁。部署后应从启动行确认姿态而不是从没看到 flag推断eventstartup携带authnone或authbearer (path)server.go 的logStartup。三、绑定到回环之外allow-non-loopback 与 Host 白名单3.1 非回环绑定强制认证--allow-non-loopback要求--auth-token-file。回环之外可达地址本身就是全部授权——每个能触达该地址的对端都获得完整的读与 claim 权限。bd serve --addr 0.0.0.0:7777 --auth-token-file /run/secrets/bd-tokens \ --allowed-host bd.internal.example--insecure-no-auth是显式、可审计的我就是要无凭据绑定非回环方式。它只在--allow-non-loopback旁边生效回环上没什么可豁免的且与--auth-token-file互斥。仅应在你已信任的网络边界在做这件事时使用。两种非回环绑定都会在启动时向 stderr 打印警告且是两种不同句式——一个点名缺失的凭据一个点名缺失的 TLSbd serve: WARNING: --insecure-no-auth binds 0.0.0.0:7777 beyond loopback with no authentication. Any peer that can reach it can read every issue and claim work as any actor. bd serve: WARNING: 0.0.0.0:7777 is bound beyond loopback with bearer authentication but NO TLS. Tokens and issue data travel in plaintext; deploy it inside a trusted network boundary.实现见 cmd/bd/serve.go 的warnServePosture仅在AllowNonLoopback时打印回环默认什么都不打印。而姿态校验ValidateAuthPostureinternal/httpapi/auth.go在 CLIresolveServeConfig与库层Listen各执行一次确保第二个调用方无法组装出一个把整个表面无凭据地暴露给网络的 Config。3.2 仍然没有 TLS即便有 token凭据和每个 issue 正文都明文传输所以回环之外的部署必须自己提供机密性——service mesh 或可信网络边界。前置一个带认证的反向代理仍是合理形态token 文件的作用是代理被绕过时源端origin不至于裸奔。3.3 Host 白名单服务化部署最容易踩的坑Host allowlist 是服务化部署最先绊倒的地方。DNS-rebinding 检查只应答回环拼写和绑定地址本身因此 dialing 一个服务 DNS 名的客户端会在每一个请求上得到 400。用--allowed-host枚举它 dial 的名字可重复精确匹配无通配符。eventstartup行会打印生效的 allowlist。实现细节见 internal/httpapi/server.go 的newHostPolicy回环拼写127.0.0.1、::1、localhost永远被允许绑定地址自身也被加入包括 127.0.0.2 这类替代回环绑定通配符绑定0.0.0.0、::没有单一配置地址因此允许任意数字 IP 字面量但仍然拒绝外来 DNS 名——被 rebind 的页面无法产生 IP 字面量的Host浏览器发送的是攻击者 URL 里的主机名。匹配基于解析后的地址net.IP.Equal所以一个允许地址的每种拼写都被允许。3.4 行为差异limit0 被拒--allow-non-loopback还带来一个行为差异limit0无限在两个 list 操作上被 400invalid_argument/reason: invalid_value拒绝无论是否配置了 token。无限读取会把整个活动集合及其 JSON 编码缓冲进一个共享进程绝不能允许任意网络对端触达。客户端必须分页。四、探针liveness 与 readiness 的正确接法探针路由绿的含义LivenessGET /healthz进程在运行。ReadinessGET /v0/beads/ready?limit1进程在运行且数据库应答了。/healthz从进程回答从不触碰数据库。数据库不可达、卡死或 idle 停止时它依然保持绿色——这正是它作为正确 liveness 探针、无用 readiness 探针的原因。不要把它接到负载均衡器的 readiness 检查上。GET /v0/beads/ready?limit1是带一行上界的真实查询。200 表示就绪503 表示活着但未就绪其code说明是哪一种db_unavailable连接失败或busy争用或槽位饱和。两者都携带Retry-After探针应把任一情况都视为未就绪、重试而不是硬失败。建议的探针设置readiness 探针最坏情况继承服务器的 60s 整请求截止时间因此给它远低于此的超时2–5 秒让探针自身的失败阈值来做平滑。在带--auth-token-file启动的服务器上readiness 探针需要 tokenliveness 不需要。/healthz是唯一 auth 豁免路由GET /v0/beads/ready不是——未认证的 readiness 探针得到401永远不会是 503只看状态类的负载均衡器会把健康服务器记为永久未就绪。给 readiness 探针加Authorization: Bearerheader并对eventauth_refused且oplistReadyWork告警——这正是你忘了的信号。五、检测楔入wedge进程健康而数据库不回答需要预案的失败模式是进程保持健康而数据库停止应答。/healthz看不见它。三个信号可以eventsemaphore_saturated—— 请求等待数据库槽位达到 1 秒以上。这是楔入检测信号没有流量就没有饱和事件因此一连串饱和事件把楔住了和安静的区分开来。outcomeacquired表示最终拿到了槽位outcomeabandoned表示客户端在排队期间挂断或请求截止时间到期——这是从活得不够久、没来得及被抛弃的请求看到的同一个楔入。实现见 internal/httpapi/server.go 的acquire/noteSaturationsaturationWarn time.Secondsemaphore_saturated事件仅在等待超阈值时记录。eventsemaphore_timeout—— 请求等待满 10 秒semAcquireTimeout 10 * time.Second后被作为 503busy抛弃server.go。eventconn_cap_saturated—— 已接受连接达到 64 条连接上限。每次跨越该上限记录一次edge-triggered见 server.go 的connStateconnCapWarned用CompareAndSwap保证只记一次连接数回落后才复位。对 readiness 探针告警并用这三个事件区分楔住的数据库与慢客户端。六、连接预算为共享 dolt sql-server 精确配容在把若干个bd serve进程指向一个共享的外部dolt sql-server之前先算好容量。每个bd serve进程在稳态下占用的连接消费者连接数Handler 池最大打开数maxInflight 420根命令的DoltStoreserver / external-server / shared-server 模式1–2 空闲每进程最坏情况约 22数字背后的形态maxInflight 16约束触碰数据库的 handler。每个工作单元unit of work钉住一条 SQL 连接因此这也是负载下的稳态连接数。池尺寸为maxInflight 4 20打开、16 空闲servePoolLimits见 server.go因为信号量约束的是handler而非连接被失败 ROLLBACK 毒化的连接会被替换、提交事务的每次重试都会在一条全新钉住的连接上打开新工作单元、任何豁免信号量的 handler 之后触碰数据库时完全逃逸信号量——四个备用槽位吸收这些。在 server、external-server、shared-server 模式下根命令已经打开了一个bd serve永远不会使用的DoltStore。它在进程生命周期内保持打开对着这个即将再池化二十条的服务器持有 1–2 条空闲连接见 cmd/bd/serve.go 的注释。打开与关闭它是根命令的事务——但它不是免费的必须计入算术。因此共享服务器上的max_connections必须覆盖大约22 × (bd serve 进程数)再加上指向同一服务器的每个其他 bd 进程各自占用的份额。空闲连接 5 分钟后回收ConnMaxIdleTime: 5 * time.Minute、一小时后复用ConnMaxLifetime: time.Hour所以突发性部署会回落到最坏情况以下——但请按最坏情况配容。池限制仅在 unit-of-work provider 暴露该旋钮时应用。若不暴露bd serve在启动时用eventpool_limits_unavailable明说并以无界池运行而不是悄悄假装见 server.go。信任上述算术之前先找这一行。这套算术对已注册后端registered backend完全不适用——它从根命令打开的 store 提供而不是从 unit-of-work provider 提供启动行上表现为dbroles而每个 Dolt 拓扑都是dbprovider。池属于那个后端bd serve既不拥有也无法触及且不会发出pool_limits_unavailable行。在那个后端配置处为它的池配容。6.1 连接上限、流上限与 TCP keepalivemaxConns 64约束的是已接受 TCP 连接——这是客户端侧限制不占数据库预算。它存在是因为信号量不约束连接Go 每个连接起一个 goroutine一个停在满信号量上的连接仍持有 goroutine、文件描述符和缓冲区。超出的连接在内核 accept backlog里等待而不是占着 Go 内存。maxWatchStreams 48约束GET /v0/beads/events:watch——唯一一个请求可持续数小时的操作——并且刻意比maxConns低 16 条连接。LimitListener只在连接关闭时归还连接槽位——在 handler 返回之后因此也在流计数器已经降下来之后——所以流上限若等于连接上限将永远不可达本应赢得503的那次连接永远不会被接受被指示回退的 poll 也永远不被接受。这个余量让拒绝可以送达也让流槽位全占时 poll、mutation 和新连接的/healthz仍然可应答理由详见 internal/httpapi/events_watch.go。看仪表而不是看悬崖eventevents_watch_admitted在每次连接时携带streamsN max_streams48所以累积在第一次拒绝之前就可见eventevents_watch_saturated才是拒绝本身。maxConns处的连接饱和是更糟的悬崖而且仍然可达——64 个普通客户端或 48 条流加 16 条其他连接。它天然沉默LimitListener只是停止调用Accept后续连接在内核 backlog 里等待stderr 上什么都没有/healthz也拿不到新连接。conn_cap_saturated是唯一的警告且是边沿触发。流没有读截止时间SSE 客户端合法地数小时不发送任何东西读截止时间会杀死健康流这引出问题对没有 FIN/RST 就消失的消费者拔线、VM 被杀靠什么回收TCP keepalive 已经做了且默认开启Go 的 listener 在每个已接受连接上启用SO_KEEPALIVE用自己的 15s 覆盖系统空闲和间隔probe 次数沿用系统值。本仓库实测2026-08-09SO_KEEPALIVE1、TCP_KEEPIDLE15、TCP_KEEPINTVL15、TCP_KEEPCNT9——对空闲连接约150 秒回收而裸系统默认7200/75/9要约 2 小时 11 分。对端消失时仍在积极写入的流改由重传回收基于tcp_retries2此处为 15约15 分钟——因为数据未确认时 keepalive 不运行。因此被消失对端占用的流槽位最坏情况是分钟级而非小时级——流上限正是按分钟级设计的。流在两次读取之间不消耗数据库预算handler 围绕每次一秒的 poll 拿一个信号量槽位并立刻归还events_watch.go 的readWatchBatch所以 48 条打开的流不占据 16 个 handler 槽位。它们也因同样的原因豁免 60s 整请求截止时间每次读取由自己独立的 15s 截止时间约束eventsWatchReadDeadline 15 * time.Second——这个短值还约束了流能多久不发心跳、能多久无视关停。要累积的消费者改用 pollGET /v0/beads/events它永不被容量拒绝。七、关停与歧义 claim恢复即重新 claimSIGINT、SIGTERM 或 SIGHUP 时服务器停止接受连接并最多排水 20 秒drainTimeout 20 * time.Second见 server.go 的Serve。排水刻意不取消进行中的 handler 上下文预算覆盖一个 claim 在其序列化重试预算内 其提交因为过早杀死这样的连接会让客户端无法判断自己的写入是否落地。Journal 流是例外且必须如此一个一直打开的响应会在整个预算期间占位然后照样被杀死。排水开始时它们收到显式关闭信号在一个 poll 间隔内自行结束——或者若处于读取中或积压中在一个 15s 读取内结束——因此打开的流不会把干净停止变成 20 秒的停止s.http.RegisterOnShutdown(s.closeStreams)server.go。它们的客户端重连并从已达的 id 续读。有一个情况仍会强制关闭且是被接受而非修复的客户端停止读取的流阻塞在写操作上而解除它的写停顿截止时间是 30 秒writeStallTimeout 30 * time.Second大于 20 秒排水。这样的流会被强制关闭shutdown_forced行是正确的——那条连接本来就已歧义。无损失流不携带写入其客户端从自己的最后 id 续读。若超出排水预算剩余连接被关闭并记录eventshutdown_forced及计数。这就是歧义情况且对客户端可见一个死在提交中途的 claim 可能已落地也可能未落地客户端看到的是连接断开而不是响应。恢复就是重新 claim且构造上安全。claim 是 compare-and-set同一 actor 的重新 claim 是幂等的若第一次尝试已落地第二次会发现 actor 已持有它、返回 200、不写任何提交若第一次未落地第二次正常 claim若中间有别的 actor 获胜第二次得到带该 actor 的assignee的类型化 409already_claimed——那是真实答案不是需要重试的错误。所以歧义关停的恢复是用同一 actor 重新发出 claim 并读取结果。绝不从断开的连接推断结果绝不退回去读 issue 猜测。同理覆盖客户端超时、挂断、claim 中途重启。客户端挂断不算服务器故障eventrequest记录codeclient_closed且不发出request_error——急躁的调用方不会推高运维告警的信号server.go 的failErrerrors.Is(err, context.Canceled)时仅改记 code不触发 request_error。八、请求日志结构化的逐请求诊断每个请求在stderr上输出一行结构化日志前缀bd serve:加标准 logger 的 UTC 时间戳bd serve: 2026/08/01 05:17:42 eventrequest request_id8f3a1c07-000042 oplistReadyWork methodGET path/v0/beads/ready status200 code duration_ms12.480 sem_wait_ms0.031 uow_ms11.902 conns1 remote_addr127.0.0.1:54321字段按序字段含义request_id关联 id每进程随机前缀-序号。任何 problem body 的request_id成员都会回显它。opspec 的operationId。method、path、status按发送与应答原样记录。codeproblemcode成功时为。duration_ms整请求毫秒三位小数。sem_wait_ms等待数据库槽位的时间。uow_ms持有 unit of work 的时间。conns当前存活的已接受连接数——注意它爬向 64。remote_addr哪个客户端回环上端口标识本地进程。refused仅当请求因某个具体值被拒时出现那个违规值。每进程随机前缀newIDPrefix4 字节随机 hexserver.go保证两个服务器或同一服务器的两次运行产生的 id 在共享日志中永不碰撞。值包含空格、、或任意控制字符时加引号空值渲染为logValueserver.go。这不是装饰没有它调用方提供的值——Hostheader、被拒参数、错误消息——就能伪造字段甚至整行未加引号的 C1 控制字符如 U009B 是 CSI 引导符会驱动运维的终端。同一流上的其他事件事件何时出现startup绑定地址、模式、workspace、数据库、host allowlist、能力以及auth——none或bearer (token 文件路径)。部署后检查auth而不是从进程列表推断。auth_refused一次401。携带request_id、op、remote_addr与reasonmissing、malformed、unknown_token。来自同一对端的unknown_token突发 客户端还留在已轮换掉的 token 上missing 从未配置过 token 的客户端。auth_reload_errortoken 文件无法重读。不是拒绝——last-good 集合仍然生效——但运维正在轮换的文件不可读且没有别的东西会说出来。limits本构建编译进的运行包络operating envelope。记录它然后与本手册对比。request_error伴随 ≥500携带 body 刻意隐藏的真实错误。用request_id关联。客户端挂断时不发出。pool_limits_unavailableunit-of-work provider 不暴露池旋钮因此下述限制未被应用、池无界。值得告警它改变连接预算。panichandler panic值、栈以及同一个request_id。semaphore_saturated、semaphore_timeout、conn_cap_saturated见检测楔入。shutdown_start、shutdown_complete、shutdown_forced排水过程。events_watch_admitted打开了一条 journal 流携带streamsN max_streams48。每条流一行而非每条记录一行这是显示触顶前累积的仪表。events_watch_saturated因本进程已持有其上限而拒绝了一条 journal 流。携带存活数与上限。流在消费者离开时结束所以一串此类事件意味着消费者在累积而非服务器慢。events_watch_failed一条打开的 journal 流以无法报告给客户端因 200 已发出的失败结束读取失败携带已达的 checkpoint或存储的 payload 无法编码携带其seq。客户端自行重连重复的读取失败点名数据库问题重复的seq点名一条将终止每条到达它的流的不可编码行。events_watch_unflushable因 response writer 无法 flush 而拒绝了一条 journal 流。无法通过bd serve自己的服务器触达意味着有什么东西在包装这个 handler。5xx body 刻意携带固定的静态 detail因此request_id是客户端拿到唯一包含真实错误的那一行的唯一把手。用户报告 500 时索取request_id并 grep——request行给出形状request_error行给出原因。九、启动时预期会遇到的拒绝消息原因bd serve requires a Dolt SQL server; this workspace uses embedded Dolt永久。嵌入式后端在独立连接上、SQL 事务之外提交因此该服务器声称的逐请求原子性在那里会是谎言。由serveDatabaseSource拒绝且是唯一拒绝它的地方见 cmd/bd/serve.go 与errServeEmbeddedcmd/bd/serve.go以及 design/bd-serve-v0.md 的 Workspace modes 一节。bd serve is unavailable under strict readonly--readonly或 config 中的readonly。该命令绑定的每个服务器都发布 issue-claim 操作而广告的能力集是构建的属性、不是启动它的进程上标志的属性——所以替代方案要么是一个广告着它永远失败的 claim 的服务器要么是一个悄悄什么都没买到的--readonly。去掉该标志才能 serveerrServeReadonlycmd/bd/serve.go。--addr localhost:7777: host must be a numeric IP literal, not a name — use 127.0.0.1 rather than localhost--addr给了 DNS 名称。--addr 0.0.0.0:7777 binds beyond loopback, which requires --allow-non-loopback (and, with it, --auth-token-file)非回环--addr没有该标志。--allow-non-loopback requires --auth-token-file (or the explicit --insecure-no-auth): every peer that can reach the address gets full read and claim access无凭据且无豁免地绑定回环之外。--insecure-no-auth applies only to a bind beyond loopback; on loopback there is nothing to waive, so pass --allow-non-loopback or drop the flag豁免出现在它所豁免的绑定之前。--insecure-no-auth contradicts --auth-token-file; pass one or the other同一个 auth 决定的两种写法同时出现。--auth-token-file: token file path: open path: no such file or directorytoken 文件不可读。还有is a directory、contains no tokens、is larger than 1048576 bytes; that is a mis-pointed path, not a token file。四种都是启动时拒绝绝不会出现照样绑定的服务器。address already in use固定端口互斥按预期工作已有另一个服务器在该端口上。上表中五个 auth/bind 拒绝由resolveServeConfig在 serve 打开数据库源或 listener之前抛出与 workspace 无关因此在每个 workspace 模式下读起来一样。根命令的PersistentPreRunE仍然先解析 workspace所以没有 beads 数据库的目录会在上述任何一条之前回答no beads database found。此外httpapi.Listen会重查同样的规则internal/httpapi/server.go所以包的第二个调用方无法组装一个把整个表面无凭据暴露给网络的 Config。十、结语把 runbook 的每个数字变成可验证的检查项bd serve的运维面被刻意设计成数字即常量、行为即代码运行包络全部集中在 internal/httpapi/server.go 的常量块认证规则集中在 internal/httpapi/auth.go流行为集中在 internal/httpapi/events_watch.go命令装配与姿态校验集中在 cmd/bd/serve.go。运维实践的检查清单可以浓缩为显式端口上线用address already in use做互斥stdout 只读一行其余全看 stderr部署后读eventstartup的auth与host_allowlist而非推断/healthz只接 livenessreadiness 用GET /v0/beads/ready?limit1且带 token超时 2–5s对semaphore_saturated、semaphore_timeout、conn_cap_saturated三个事件告警以区分楔住的数据库与慢客户端共享 dolt sql-server 按22 × 进程数 其他 bd 进程份额配max_connections并确认启动时没有pool_limits_unavailable歧义关停后重新 claim 并读结果绝不从断开的连接推断用户报 500 时索取request_id并 greprequest_error。在此基础上若需要了解该 HTTP 表面所提供的操作全集、错误码词汇、游标契约与 loopback 姿态的设计决策可直接阅读 design/bd-serve-v0.md线上契约的单一事实来源则是internal/httpapi/spec/openapi.v0.yaml与 internal/httpapi/routes.go二者由TestSpecRouteParity以双向集合相等焊死——操作面永远以 spec 文档、路由表或一台运行中的服务器为准。【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beads创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考