A hung workstation no longer costs the resident model its budget (V-581)
Pair.Complete handed the caller's context to the workstation unchanged, so a remote that accepted the connection and then hung spent the whole turn budget. The fallback then ran on an expired context and the floor returned the deadline error instead of an answer, which broke the turn on the workstation being slow. docs/offload.md rules that out. The remote now gets at most half of a deadline that exists, and a context without a deadline is left to the configured workstation timeout. Pair.Stop and worker.Server.Close both closed their channel after a check-then-close, so two concurrent callers could race and the second close panics. Both are sync.Once now, which is what the doc comments already claimed. config.Load names the environment variables it could not resolve. An unset variable still expands to the empty string, because every block reads that as not configured and CI parses deploy/mavend.json with no secrets present. What was missing is the line telling the operator which capability a forgotten env file just turned off. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@@ -329,7 +330,15 @@ func Load(path string) (*Config, error) {
|
||||
// Expand ${VAR} or $VAR patterns from environment variables. This lets
|
||||
// secrets live in env (docker-compose env_file) rather than the config
|
||||
// file committed to git.
|
||||
expanded := os.ExpandEnv(string(b))
|
||||
expanded, missing := expandEnv(string(b))
|
||||
if len(missing) > 0 {
|
||||
// An unset variable expands to "", which every block reads as "not
|
||||
// configured" and none of them complains about. That is the intended
|
||||
// behaviour and it stays: CI parses this same file with no secrets
|
||||
// present. What was missing is the line telling the operator which
|
||||
// capability he just turned off by forgetting an env file.
|
||||
log.Printf("config: %s references unset environment variables %v — those settings are empty, so whatever they configure is off", path, missing)
|
||||
}
|
||||
var c Config
|
||||
if err := json.Unmarshal([]byte(expanded), &c); err != nil {
|
||||
return nil, fmt.Errorf("config: parse %s: %w", path, err)
|
||||
@@ -341,6 +350,23 @@ func Load(path string) (*Config, error) {
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
// expandEnv is os.ExpandEnv plus the names it could not resolve, each reported
|
||||
// once and in the order the file mentions them. A variable set to the empty
|
||||
// string counts as set: the operator wrote it down, so he meant it.
|
||||
func expandEnv(s string) (string, []string) {
|
||||
var missing []string
|
||||
seen := map[string]bool{}
|
||||
out := os.Expand(s, func(name string) string {
|
||||
v, ok := os.LookupEnv(name)
|
||||
if !ok && !seen[name] {
|
||||
seen[name] = true
|
||||
missing = append(missing, name)
|
||||
}
|
||||
return v
|
||||
})
|
||||
return out, missing
|
||||
}
|
||||
|
||||
func (c *Config) applyDefaults() {
|
||||
if c.IntakeJournal == 0 {
|
||||
c.IntakeJournal = DefaultIntakeJournal
|
||||
|
||||
Reference in New Issue
Block a user