After DAGGER-133 made dagger serve fail-closed, every REST route — including POST /api/v1/execute — requires Authorization: Bearer $DAGGERAPITOKEN. The role wrapper exports a fresh random token, so dagger serve and the top-level dagger run client authenticate. The nested execute path used by the foreach/per-item node machinery builds its own HTTP request without that header. Serve logs httpauthdenied reason=missing path=/api/v1/execute and each item node returns
DAGGER_API_TOKENAfter DAGGER-133 made dagger serve fail-closed, every REST route — including POST /api/v1/execute — requires Authorization: Bearer $DAGGER_API_TOKEN. The role wrapper exports a fresh random token, so dagger serve and the top-level dagger run client authenticate. The nested execute path used by the foreach/per-item node machinery builds its own HTTP request without that header. Serve logs http_auth_denied reason=missing path=/api/v1/execute and each item node returns
execute: http://<ip-address>:11775 rejected the request (401): set DAGGER_API_TOKEN to the token `dagger serve` was started with
Because the sub-pipeline converts per-item failures into a null result, the parent pipeline still reports Passed: 4, Failed: 0 while exercising zero targets.
dagger serve is now fail-closed. The auth middleware (registered in cmd/dagger/main.go inside serve) rejects any request to /api/v1/* that lacks a valid bearer token:
http_auth_denied reason=missing path=/api/v1/executehttp_auth_denied reason=mismatchset DAGGER_API_TOKEN to the token \dagger serve` was started with`This is correct and must not be weakened.
~/.hermes/scripts/dagger-role-tick.sh exports a fresh token before starting dagger serve and before invoking the top-level dagger run, so those two processes authenticate. This is why the failure is intermittent/lane-specific rather than a total outage.
The per-item / sub-DAG execute path (foreach iteration, node sub-execution, or the MCP execute adapter) constructs a new http.Request — or uses a bare http.Post / http.DefaultClient — instead of reusing the authenticated client the run process already has. Concretely, the request is built along the lines of:
// nested / sub-execution path (simplified)
req, _ := http.NewRequestWithContext(ctx, http.MethodPost, serverURL+"/api/v1/execute", body)
req.Header.Set("Content-Type", "application/json")
// BUG: no Authorization header is copied from the run process / top-level client
resp, err := http.DefaultClient.Do(req)
The top-level path gets Authorization: Bearer <token> (e.g. via setAuthHeader/the shared API client), but the nested path does not. Since the token is a fresh random value per tick, it cannot be rediscovered from disk — it must travel down the call stack.
The 5-node sub-pipeline reports per-item failures as a null result rather than a hard node error. The parent's failure counter only increments on hard errors, so Failed: 0 and exercised=0 are consistent: the whole run no-oped.
serve.log: http_auth_denied reason=missing path=/api/v1/execute
wrapper: Passed: 4, Failed: 0
report: exercised=0 findings=0
There are two equivalent fixes. Prefer A (one authenticated client shared by all code paths) because it fixes every present and future nested path at once; B is the minimal, surgical patch if you only want to touch the one call site.
Add a transport that injects the bearer token from the run process environment. Env inheritance is the correct mechanism here: the wrapper already exports the token into the run process, so every in-process sub-execution inherits it for free, and no secret is written to disk.
// src/runner/authclient.go (new file; place in the package that owns nested execute)
package runner
import (
"net/http"
"os"
"time"
)
// daggerAuthTransport mirrors the top-level `dagger run` client's auth on
// every request: it injects `Authorization: Bearer $DAGGER_API_TOKEN` unless
// the caller already set one. This makes nested (foreach / sub-DAG / per-item)
// execute calls authenticate exactly like the top-level execute call.
type daggerAuthTransport struct {
base http.RoundTripper
token string
}
func (t *daggerAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
base := t.base
if base == nil {
base = http.DefaultTransport
}
if t.token == "" || req.Header.Get("Authorization") != "" {
return base.RoundTrip(req)
}
// Clone: never mutate the caller's request (RoundTripper contract).
clone := req.Clone(req.Context())
clone.Header.Set("Authorization", "Bearer "+t.token)
return base.RoundTrip(clone)
}
// NewServerClient returns the client every execute path must use. It reads
// DAGGER_API_TOKEN at construction time; call it after the wrapper exports it.
func NewServerClient(timeout time.Duration) *http.Client {
return &http.Client{
Transport: &daggerAuthTransport{
base: http.DefaultTransport,
token: os.Getenv("DAGGER_API_TOKEN"),
},
Timeout: timeout,
}
}
Then wire it in once, where the runner is constructed, and have all nested code use r.httpClient instead of http.DefaultClient / http.Post:
// where the Runner is built (setupEngine / RunFile / main)
r := &Runner{
// ...
httpClient: NewServerClient(serverExecuteTimeout),
}
// nested execute path
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
strings.TrimRight(serverURL, "/")+"/api/v1/execute", body)
if err != nil {
return fmt.Errorf("execute: %w", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := r.httpClient.Do(req) // <-- token injected by transport
This also automatically covers any sub-execution that shells back into the same serve instance, because they all share the client.
If you cannot restructure the runner, add the header directly where the nested request is built:
req, err := http.NewRequestWithContext(ctx, http.MethodPost, serverURL+"/api/v1/execute", body)
if err != nil {
return fmt.Errorf("execute: %w", err)
}
req.Header.Set("Content-Type", "application/json")
if tok := os.Getenv("DAGGER_API_TOKEN"); tok != "" {
req.Header.Set("Authorization", "Bearer "+tok)
}
Locate the exact call site with:
rg -n 'api/v1/execute' src cmd
# Inspect every `http.NewRequest|http.Post|http.DefaultClient` near those hits and
# ensure EACH one sets the Authorization header.
Independently of auth, make the sub-pipeline fail loudly. A 401 must never be reported as null/success:
// after executing the sub-pipeline, before returning the aggregate
if result.Exercised == 0 || result.Errors > 0 {
return nil, fmt.Errorf(
"sub-execution exercised %d targets with %d errors (last: %v)",
result.Exercised, result.Errors, result.LastErr,
)
}
If the aggregate shape does not carry counts, at minimum propagate any HTTP error whose status is not 2xx instead of converting it to nil. This makes the wrapper print red and non-zero, which is what would have surfaced DAGGER-133 on day one.
Do not disable the auth check in
serve, and do not persist the random token to a file. Both would reintroduce the fail-open hole DAGGER-133 closed.
// src/runner/authclient_test.go
package runner
import (
"net/http"
"net/http/httptest"
"testing"
"time"
)
func TestNestedExecuteSendsBearerToken(t *testing.T) {
t.Setenv("DAGGER_API_TOKEN", "test-token-133")
var gotAuth string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotAuth = r.Header.Get("Authorization")
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
c := NewServerClient(5 * time.Second)
req, _ := http.NewRequest(http.MethodPost, srv.URL+"/api/v1/execute", nil)
resp, err := c.Do(req)
if err != nil {
t.Fatalf("nested execute failed: %v", err)
}
defer resp.Body.Close()
if want := "Bearer test-token-133"; gotAuth != want {
t.Fatalf("Authorization header = %q, want %q", gotAuth, want)
}
}
func TestNestedExecuteRespectsCallerHeader(t *testing.T) {
t.Setenv("DAGGER_API_TOKEN", "env-token")
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if got := r.Header.Get("Authorization"); got != "Bearer explicit" {
t.Errorf("caller header overwritten: %q", got)
}
}))
defer srv.Close()
req, _ := http.NewRequest(http.MethodPost, srv.URL+"/api/v1/execute", nil)
req.Header.Set("Authorization", "Bearer explicit")
if _, err := NewServerClient(5 * time.Second).Do(req); err != nil {
t.Fatal(err)
}
}
Run:
go test ./src/runner/ -run 'NestedExecute' -v
export DAGGER_API_TOKEN="$(openssl rand -hex 32)"
rm -f /tmp/dagger-serve.log
# 1) serve fail-closed, with the token in its own process env
dagger serve --addr <ip-address>:11775 >/tmp/dagger-serve.log 2>&1 &
SERVE_PID=$!
sleep 1
# 2) negative control: no header must be rejected
curl -s -o /dev/null -w 'no-auth -> %{http_code}\n' \
-X POST http://<ip-address>:11775/api/v1/execute \
-H 'Content-Type: application/json' -d '{"code":"dag.node(\"a\", async () => 1)"}'
# expect: no-auth -> 401
# 3) authenticated control must succeed
curl -s -o /dev/null -w 'with-auth -> %{http_code}\n' \
-X POST http://<ip-address>:11775/api/v1/execute \
-H "Authorization: Bearer $DAGGER_API_TOKEN" \
-H 'Content-Type: application/json' -d '{"code":"dag.node(\"a\", async () => 1)"}'
# expect: with-auth -> 200
# 4) the real regression: a foreach pipeline, run without manually passing headers
dagger run --server http://<ip-address>:11775 /path/to/foreach.d5s
# 5) assert no auth denials were logged and targets were actually exercised
grep -c 'http_auth_denied' /tmp/dagger-serve.log # expect: 0
# and the report must show exercised > 0
kill $SERVE_PID
For every affected lane (dogfooding, qa, pm, bankai, plus-ultra), after the wrapper runs:
for log in /tmp/dagger-role-*/serve.log; do
denials=$(grep -c 'http_auth_denied reason=missing path=/api/v1/execute' "$log" 2>/dev/null || true)
echo "$log denials=$denials"
done
# expect 0 for all lanes
And confirm the report no longer lies:
grep -E 'exercised=[0-9]+' /tmp/dagger-role-*/report.txt
# exercised must be > 0; if it is 0, Fix C is missing
Add a CI test that boots a fail-closed httptest-style server requiring Authorization and runs a 2-item foreach against it, asserting the run succeeds and both items produced non-null output. This catches any future path that forgets the header — and any future change that silently converts a 401 into null.
| Area | File / symbol (repo github.com/Hermes-DAGger/<project>) |
Change |
|---|---|---|
| Auth middleware (do not change) | cmd/dagger/main.go → serve / signature middleware |
Keep fail-closed |
| Shared client (Fix A) | src/runner/ (new authclient.go) |
Inject Authorization: Bearer $DAGGER_API_TOKEN |
| Nested execute call site | src/runner/runner.go (execute*, sub-execution) and src/nodes/foreach.go |
Use r.httpClient, not http.DefaultClient |
| Failure propagation (Fix C) | sub-pipeline aggregation | Fail on errors > 0 or exercised == 0 |
| Wrapper (no change needed) | ~/.hermes/scripts/dagger-role-tick.sh |
Already exports DAGGER_API_TOKEN into the run process env |
Key invariant to preserve: the token is a per-tick random secret exported into the run process environment. Any code path that spawns a sub-request must either inherit that env (Fix A) or explicitly set the bearer header (Fix B). No path may use a bare HTTP client against /api/v1/execute.
# Evidence - Problem class: dagger-nested-execute-401 - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-12T20:43:02.343Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Root cause: since DAGGER-133 (2026-09-12) dagger serve is fail-closed and requires DAGGER_API_TOKEN on every REST route. Fleet role wrappers (dagger-role-tick.sh) export a fresh random DAGGER_API_TOKEN so serve + the top-level dagger run client authenticate fine. BUT the foreach/node execute machinery runs per-item sub-executions that call POST /api/v1/execute WITHOUT the Authorization header \u2014 serve logs http_auth_denied reason=missing on /api/v1/execute, the item node errors with: execute: http://<ip-address>:<port> rejected the request (401): set DAGGER_API_TOKEN to the token dagger serve was started with. The wrapper prints green (Passed: 4, Failed: 0) because the 5-node sub-pipeline reports per-item failures as a null result, so every role lane (dogfooding, qa) silently exercises 0 targets. Fix: the nested execute path must inherit DAGGER_API_TOKEN from the run process env (or the run client must pass its token into sub-executions the same way it authenticates its own top-level execute). Verified fleet-wide: /tmp/dagger-role-qa-* runs on 2026-09-12 13:32 and 14:21 show the same 401; dogfooding <project> runs 07:45 (pre-fix) worked, 13:08 and 15:29 (post-fix) noop.", "environment": "<project> v0.1.0 (dagger serve tier 2, DAGGER-133 fail-closed auth)", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "dagger-nested-execute-401", "provider": "openrouter", "solved_at": "2026-09-12T20:43:02.344Z", "version": ""}DAGGER_API_TOKENAfter DAGGER-133 made dagger serve fail-closed, every REST route — including POST /api/v1/execute — requires Authorization: Bearer $DAGGER_API_TOKEN. The role wrapper exports a fresh random token, so dagger serve and the top-level dagger run client authenticate. The nested execute path used by the foreach/per-item node machinery builds its own HTTP request without that header. Serve logs http_auth_denied reason=missing path=/api/v1/execute and each item node returns
execute: http://<ip-address>:11775 rejected the request (401): set DAGGER_API_TOKEN to the token `dagger serve` was started with
Because the sub-pipeline converts per-item failures into a null result, the parent pipeline still reports Passed: 4, Failed: 0 while exercising zero targets.
dagger serve is now fail-closed. The auth middleware (registered in cmd/dagger/main.go inside serve) rejects any request to /api/v1/* that lacks a valid bearer token:
http_auth_denied reason=missing path=/api/v1/executehttp_auth_denied reason=mismatchset DAGGER_API_TOKEN to the token \dagger serve` was started with`This is correct and must not be weakened.
~/.hermes/scripts/dagger-role-tick.sh exports a fresh token before starting dagger serve and before invoking the top-level dagger run, so those two processes authenticate. This is why the failure is intermittent/lane-specific rather than a total outage.
The per-item / sub-DAG execute path (foreach iteration, node sub-execution, or the MCP execute adapter) constructs a new http.Request — or uses a bare http.Post / http.DefaultClient — instead of reusing the authenticated client the run process already has. Concretely, the request is built along the lines of:
// nested / sub-execution path (simplified)
req, _ := http.NewRequestWithContext(ctx, http.MethodPost, serverURL+"/api/v1/execute", body)
req.Header.Set("Content-Type", "application/json")
// BUG: no Authorization header is copied from the run process / top-level client
resp, err := http.DefaultClient.Do(req)
The top-level path gets Authorization: Bearer <token> (e.g. via setAuthHeader/the shared API client), but the nested path does not. Since the token is a fresh random value per tick, it cannot be rediscovered from disk — it must travel down the call stack.
The 5-node sub-pipeline reports per-item failures as a null result rather than a hard node error. The parent's failure counter only increments on hard errors, so Failed: 0 and exercised=0 are consistent: the whole run no-oped.
serve.log: http_auth_denied reason=missing path=/api/v1/execute
wrapper: Passed: 4, Failed: 0
report: exercised=0 findings=0
There are two equivalent fixes. Prefer A (one authenticated client shared by all code paths) because it fixes every present and future nested path at once; B is the minimal, surgical patch if you only want to touch the one call site.
Add a transport that injects the bearer token from the run process environment. Env inheritance is the correct mechanism here: the wrapper already exports the token into the run process, so every in-process sub-execution inherits it for free, and no secret is written to disk.
// src/runner/authclient.go (new file; place in the package that owns nested execute)
package runner
import (
"net/http"
"os"
"time"
)
// daggerAuthTransport mirrors the top-level `dagger run` client's auth on
// every request: it injects `Authorization: Bearer $DAGGER_API_TOKEN` unless
// the caller already set one. This makes nested (foreach / sub-DAG / per-item)
// execute calls authenticate exactly like the top-level execute call.
type daggerAuthTransport struct {
base http.RoundTripper
token string
}
func (t *daggerAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
base := t.base
if base == nil {
base = http.DefaultTransport
}
if t.token == "" || req.Header.Get("Authorization") != "" {
return base.RoundTrip(req)
}
// Clone: never mutate the caller's request (RoundTripper contract).
clone := req.Clone(req.Context())
clone.Header.Set("Authorization", "Bearer "+t.token)
return base.RoundTrip(clone)
}
// NewServerClient returns the client every execute path must use. It reads
// DAGGER_API_TOKEN at construction time; call it after the wrapper exports it.
func NewServerClient(timeout time.Duration) *http.Client {
return &http.Client{
Transport: &daggerAuthTransport{
base: http.DefaultTransport,
token: os.Getenv("DAGGER_API_TOKEN"),
},
Timeout: timeout,
}
}
Then wire it in once, where the runner is constructed, and have all nested code use r.httpClient instead of http.DefaultClient / http.Post:
// where the Runner is built (setupEngine / RunFile / main)
r := &Runner{
// ...
httpClient: NewServerClient(serverExecuteTimeout),
}
// nested execute path
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
strings.TrimRight(serverURL, "/")+"/api/v1/execute", body)
if err != nil {
return fmt.Errorf("execute: %w", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := r.httpClient.Do(req) // <-- token injected by transport
This also automatically covers any sub-execution that shells back into the same serve instance, because they all share the client.
If you cannot restructure the runner, add the header directly where the nested request is built:
req, err := http.NewRequestWithContext(ctx, http.MethodPost, serverURL+"/api/v1/execute", body)
if err != nil {
return fmt.Errorf("execute: %w", err)
}
req.Header.Set("Content-Type", "application/json")
if tok := os.Getenv("DAGGER_API_TOKEN"); tok != "" {
req.Header.Set("Authorization", "Bearer "+tok)
}
Locate the exact call site with:
rg -n 'api/v1/execute' src cmd
# Inspect every `http.NewRequest|http.Post|http.DefaultClient` near those hits and
# ensure EACH one sets the Authorization header.
Independently of auth, make the sub-pipeline fail loudly. A 401 must never be reported as null/success:
// after executing the sub-pipeline, before returning the aggregate
if result.Exercised == 0 || result.Errors > 0 {
return nil, fmt.Errorf(
"sub-execution exercised %d targets with %d errors (last: %v)",
result.Exercised, result.Errors, result.LastErr,
)
}
If the aggregate shape does not carry counts, at minimum propagate any HTTP error whose status is not 2xx instead of converting it to nil. This makes the wrapper print red and non-zero, which is what would have surfaced DAGGER-133 on day one.
Do not disable the auth check in
serve, and do not persist the random token to a file. Both would reintroduce the fail-open hole DAGGER-133 closed.
// src/runner/authclient_test.go
package runner
import (
"net/http"
"net/http/httptest"
"testing"
"time"
)
func TestNestedExecuteSendsBearerToken(t *testing.T) {
t.Setenv("DAGGER_API_TOKEN", "test-token-133")
var gotAuth string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotAuth = r.Header.Get("Authorization")
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
c := NewServerClient(5 * time.Second)
req, _ := http.NewRequest(http.MethodPost, srv.URL+"/api/v1/execute", nil)
resp, err := c.Do(req)
if err != nil {
t.Fatalf("nested execute failed: %v", err)
}
defer resp.Body.Close()
if want := "Bearer test-token-133"; gotAuth != want {
t.Fatalf("Authorization header = %q, want %q", gotAuth, want)
}
}
func TestNestedExecuteRespectsCallerHeader(t *testing.T) {
t.Setenv("DAGGER_API_TOKEN", "env-token")
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if got := r.Header.Get("Authorization"); got != "Bearer explicit" {
t.Errorf("caller header overwritten: %q", got)
}
}))
defer srv.Close()
req, _ := http.NewRequest(http.MethodPost, srv.URL+"/api/v1/execute", nil)
req.Header.Set("Authorization", "Bearer explicit")
if _, err := NewServerClient(5 * time.Second).Do(req); err != nil {
t.Fatal(err)
}
}
Run:
go test ./src/runner/ -run 'NestedExecute' -v
export DAGGER_API_TOKEN="$(openssl rand -hex 32)"
rm -f /tmp/dagger-serve.log
# 1) serve fail-closed, with the token in its own process env
dagger serve --addr <ip-address>:11775 >/tmp/dagger-serve.log 2>&1 &
SERVE_PID=$!
sleep 1
# 2) negative control: no header must be rejected
curl -s -o /dev/null -w 'no-auth -> %{http_code}\n' \
-X POST http://<ip-address>:11775/api/v1/execute \
-H 'Content-Type: application/json' -d '{"code":"dag.node(\"a\", async () => 1)"}'
# expect: no-auth -> 401
# 3) authenticated control must succeed
curl -s -o /dev/null -w 'with-auth -> %{http_code}\n' \
-X POST http://<ip-address>:11775/api/v1/execute \
-H "Authorization: Bearer $DAGGER_API_TOKEN" \
-H 'Content-Type: application/json' -d '{"code":"dag.node(\"a\", async () => 1)"}'
# expect: with-auth -> 200
# 4) the real regression: a foreach pipeline, run without manually passing headers
dagger run --server http://<ip-address>:11775 /path/to/foreach.d5s
# 5) assert no auth denials were logged and targets were actually exercised
grep -c 'http_auth_denied' /tmp/dagger-serve.log # expect: 0
# and the report must show exercised > 0
kill $SERVE_PID
For every affected lane (dogfooding, qa, pm, bankai, plus-ultra), after the wrapper runs:
for log in /tmp/dagger-role-*/serve.log; do
denials=$(grep -c 'http_auth_denied reason=missing path=/api/v1/execute' "$log" 2>/dev/null || true)
echo "$log denials=$denials"
done
# expect 0 for all lanes
And confirm the report no longer lies:
grep -E 'exercised=[0-9]+' /tmp/dagger-role-*/report.txt
# exercised must be > 0; if it is 0, Fix C is missing
Add a CI test that boots a fail-closed httptest-style server requiring Authorization and runs a 2-item foreach against it, asserting the run succeeds and both items produced non-null output. This catches any future path that forgets the header — and any future change that silently converts a 401 into null.
| Area | File / symbol (repo github.com/Hermes-DAGger/<project>) |
Change |
|---|---|---|
| Auth middleware (do not change) | cmd/dagger/main.go → serve / signature middleware |
Keep fail-closed |
| Shared client (Fix A) | src/runner/ (new authclient.go) |
Inject Authorization: Bearer $DAGGER_API_TOKEN |
| Nested execute call site | src/runner/runner.go (execute*, sub-execution) and src/nodes/foreach.go |
Use r.httpClient, not http.DefaultClient |
| Failure propagation (Fix C) | sub-pipeline aggregation | Fail on errors > 0 or exercised == 0 |
| Wrapper (no change needed) | ~/.hermes/scripts/dagger-role-tick.sh |
Already exports DAGGER_API_TOKEN into the run process env |
Key invariant to preserve: the token is a per-tick random secret exported into the run process environment. Any code path that spawns a sub-request must either inherit that env (Fix A) or explicitly set the bearer header (Fix B). No path may use a bare HTTP client against /api/v1/execute.
# Evidence - Problem class: dagger-nested-execute-401 - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-12T20:43:02.343Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Root cause: since DAGGER-133 (2026-09-12) dagger serve is fail-closed and requires DAGGER_API_TOKEN on every REST route. Fleet role wrappers (dagger-role-tick.sh) export a fresh random DAGGER_API_TOKEN so serve + the top-level dagger run client authenticate fine. BUT the foreach/node execute machinery runs per-item sub-executions that call POST /api/v1/execute WITHOUT the Authorization header \u2014 serve logs http_auth_denied reason=missing on /api/v1/execute, the item node errors with: execute: http://<ip-address>:<port> rejected the request (401): set DAGGER_API_TOKEN to the token dagger serve was started with. The wrapper prints green (Passed: 4, Failed: 0) because the 5-node sub-pipeline reports per-item failures as a null result, so every role lane (dogfooding, qa) silently exercises 0 targets. Fix: the nested execute path must inherit DAGGER_API_TOKEN from the run process env (or the run client must pass its token into sub-executions the same way it authenticates its own top-level execute). Verified fleet-wide: /tmp/dagger-role-qa-* runs on 2026-09-12 13:32 and 14:21 show the same 401; dogfooding <project> runs 07:45 (pre-fix) worked, 13:08 and 15:29 (post-fix) noop.", "environment": "<project> v0.1.0 (dagger serve tier 2, DAGGER-133 fail-closed auth)", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "dagger-nested-execute-401", "provider": "openrouter", "solved_at": "2026-09-12T20:43:02.344Z", "version": ""}