You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
AWF can stop an agent when it exceeds its AI Credit limit, but after cleanup
the caller cannot reliably prove that this specific limit caused the failure.
The local AWF rejection and a real provider authorization error can both appear
as the same generic HTTP 403 message. The API proxy knows the difference while
it is running, but that state disappears when the proxy container is removed.
Why this matters
A caller such as gh-aw needs to know whether it should:
report a budget stop;
retry a provider failure; or
treat the result as unknown.
It cannot safely make that decision from agent output, exit codes, or files
the agent can modify.
Root cause
The authoritative evidence exists only inside the API proxy:
proxy detects credit limit
-> proxy rejects request
-> agent exits
-> AWF removes proxy container
-> caller receives only exit code and generic error text
The obvious alternatives are unsafe:
The agent can choose any exit code.
The agent controls stdout and stderr.
The agent can modify files under the workspace and /tmp/gh-aw.
A file output path can be replaced with a symlink.
The live proxy status endpoint is gone after cleanup.
Missing or conflicting evidence must therefore remain an ordinary failure, not
a confirmed budget stop.
Proposed fix
Use one optional anonymous pipe created by the trusted caller:
The caller creates the pipe and keeps its read end.
AWF validates the write end before starting the agent.
AWF removes the descriptor from the agent environment.
After the agent exits, AWF reads two matching proxy snapshots while the
proxy is still running.
AWF cleans up and verifies that the containers are gone.
AWF writes one small JSON result to the caller-owned pipe.
There is no result file for the agent to forge or redirect.
If the pipe is absent, AWF behaves exactly as it does today. If the pipe is
invalid, AWF fails before the agent starts.
Result contents
The result should contain only the facts needed to classify the failure:
schema version;
AWF invocation and proxy IDs;
classification and reason;
agent exit code;
cleanup result;
authoritative credit total and limit;
final proxy event;
timestamps and freshness;
count of local limit rejections and upstream 403 responses.
It must not contain prompts, credentials, headers, bodies, or model content.
When a caller may trust the result
A budget stop is confirmed only when:
exactly one complete result is received;
invocation and proxy IDs match;
two proxy snapshots match and have no active requests;
the final event is the local AI Credit limit;
the total is at or above the configured limit;
the agent exit is nonzero;
cleanup succeeded;
the separately observed AWF exit agrees with the result.
Anything stale, malformed, incomplete, conflicting, still active, or
cleanup-failed remains unconfirmed.
Locally preserved implementation
Branch: danielmeppiel-expose-awf-guard-result
Commit: e458ff3d501e95a90fd848f772eb5fa99348d563
The implementation is intentionally described here rather than submitted as a
pull request.
Proof so far
API-proxy tests: 81 suites / 1,639 tests passed
Tests cover local limit versus upstream 403, HTTP and WebSocket completion,
malformed/stale results, active requests, partial writes, closed pipes,
invalid descriptors, and secret exclusion
TypeScript syntax and workflow YAML checks passed
Independent security review: READY
Architecture and test review findings were addressed
The final real-container CI test has not run. This issue must not be considered
complete until that test and the repository build/type/lint checks pass.
Done when
The agent cannot see or write the result pipe.
Agent-controlled files, output, symlinks, and exit codes cannot confirm
a budget stop.
Snapshot happens before proxy cleanup; result emission happens after
verified cleanup.
Local credit-limit rejection is distinguishable from a real upstream
403 for HTTP and WebSocket traffic.
Stale, malformed, active, conflicting, or cleanup-failed evidence is
rejected.
Existing behavior is unchanged when no result pipe is provided.
Build, type, lint, unit, and CodeQL checks pass.
The strict real-container test passes with malicious-write, stale retry,
upstream 403, exact framing, closed-pipe, teardown, and cleanup cases.
The descriptor must be a writable anonymous pipe, not a file, directory,
socket, terminal, named pipe, closed descriptor, or read end. The JSON record
must be one newline-terminated write no larger than 4096 bytes.
Docker must also be frozen before agent execution:
canonical absolute Docker executable;
canonical local Unix socket only;
no agent-controlled Docker context, config, plugin, or Compose override;
control files outside agent mounts;
exclusive no-follow Compose-file creation;
revalidation after stale agent containers are removed.
The proxy must track every HTTP and WebSocket request until its final response,
accounting, retry, or close path completes. Snapshot capture fails while any
request remains active.
Problem
AWF can stop an agent when it exceeds its AI Credit limit, but after cleanup
the caller cannot reliably prove that this specific limit caused the failure.
The local AWF rejection and a real provider authorization error can both appear
as the same generic HTTP 403 message. The API proxy knows the difference while
it is running, but that state disappears when the proxy container is removed.
Why this matters
A caller such as gh-aw needs to know whether it should:
It cannot safely make that decision from agent output, exit codes, or files
the agent can modify.
Root cause
The authoritative evidence exists only inside the API proxy:
The obvious alternatives are unsafe:
/tmp/gh-aw.Missing or conflicting evidence must therefore remain an ordinary failure, not
a confirmed budget stop.
Proposed fix
Use one optional anonymous pipe created by the trusted caller:
proxy is still running.
There is no result file for the agent to forge or redirect.
If the pipe is absent, AWF behaves exactly as it does today. If the pipe is
invalid, AWF fails before the agent starts.
Result contents
The result should contain only the facts needed to classify the failure:
It must not contain prompts, credentials, headers, bodies, or model content.
When a caller may trust the result
A budget stop is confirmed only when:
Anything stale, malformed, incomplete, conflicting, still active, or
cleanup-failed remains unconfirmed.
Locally preserved implementation
danielmeppiel-expose-awf-guard-resulte458ff3d501e95a90fd848f772eb5fa99348d563The implementation is intentionally described here rather than submitted as a
pull request.
Proof so far
malformed/stale results, active requests, partial writes, closed pipes,
invalid descriptors, and secret exclusion
The final real-container CI test has not run. This issue must not be considered
complete until that test and the repository build/type/lint checks pass.
Done when
a budget stop.
verified cleanup.
403 for HTTP and WebSocket traffic.
rejected.
upstream 403, exact framing, closed-pipe, teardown, and cleanup cases.
Maintainer implementation notes
High-level host flow:
The descriptor must be a writable anonymous pipe, not a file, directory,
socket, terminal, named pipe, closed descriptor, or read end. The JSON record
must be one newline-terminated write no larger than 4096 bytes.
Docker must also be frozen before agent execution:
The proxy must track every HTTP and WebSocket request until its final response,
accounting, retry, or close path completes. Snapshot capture fails while any
request remains active.
Main files:
containers/api-proxy/guard-result-state.jscontainers/api-proxy/guards/ai-credits-guard.jssrc/guard-result.tssrc/guard-result-writer.tssrc/cli-workflow.tstests/integration/api-proxy-guard-result.test.ts.github/workflows/test-guard-result.yml