Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
130 commits
Select commit Hold shift + click to select a range
ae392a2
platform: add multi-tenant contracts
XnLemon Jul 8, 2026
0cddda9
platform/gateway: add text loop
XnLemon Jul 8, 2026
38636fd
platform/toolpolicy: add governance bridge
XnLemon Jul 8, 2026
e39271c
platform/channeladapter: add adapter outbox skeleton
XnLemon Jul 8, 2026
f053160
platform/gateway: enqueue outbound handoff
XnLemon Jul 8, 2026
c5e55e9
platform/storagerouter: add storage routing contracts
XnLemon Jul 8, 2026
5488171
platform/gateway: add session lease
XnLemon Jul 8, 2026
64d0a9a
platform: add gray routing helpers
XnLemon Jul 8, 2026
cf5af61
platform: add tenant budget helpers
XnLemon Jul 8, 2026
90072df
platform: validate storage migration modes
XnLemon Jul 8, 2026
2b86679
platform: validate audit policies
XnLemon Jul 8, 2026
08fbc12
platform: validate audit records
XnLemon Jul 8, 2026
b63b970
platform: add capacity estimator
XnLemon Jul 8, 2026
4193569
platform/channeladapter: replay dead letters
XnLemon Jul 8, 2026
78675ef
platform: populate audit record ids
XnLemon Jul 8, 2026
41b6b97
platform: validate audit sink writes
XnLemon Jul 8, 2026
b72383f
platform: add usage record contracts
XnLemon Jul 8, 2026
260b3a5
platform: add usage sink contracts
XnLemon Jul 8, 2026
c62e214
platform: add config version contracts
XnLemon Jul 8, 2026
a74ee58
platform: select config version by session gray bucket
XnLemon Jul 8, 2026
75d1af7
platform: add config version lifecycle helpers
XnLemon Jul 8, 2026
1a12b0f
platform: add usage summary contracts
XnLemon Jul 8, 2026
c44f477
platform: add audit query contracts
XnLemon Jul 8, 2026
8eefe55
platform: add config version diff contracts
XnLemon Jul 8, 2026
6b8b00c
platform: add config gray status summary
XnLemon Jul 8, 2026
c59acc4
platform: add operational action audit contracts
XnLemon Jul 8, 2026
644a03c
platform: add config cache invalidation contracts
XnLemon Jul 8, 2026
8219105
platform: add secret rotation status contracts
XnLemon Jul 8, 2026
e66bf9c
platform: add backend migration status contracts
XnLemon Jul 8, 2026
274747c
platform: add storage router status summary
XnLemon Jul 8, 2026
826a718
platform: add config operation summary contracts
XnLemon Jul 8, 2026
ce636fe
platform/toolpolicy: add approval summary contract
XnLemon Jul 8, 2026
bc201e0
fix(platform): enforce binding ACL and redact outbox errors
XnLemon Jul 9, 2026
3d57ce8
fix(platform): redact gateway audit error reasons
XnLemon Jul 9, 2026
597b46c
fix(toolpolicy): enforce auditable policy identity
XnLemon Jul 9, 2026
b8b802e
platform: harden identity and idempotency contracts
xianingawa-wq Jul 9, 2026
6d45186
Merge pull request #16 from XnLemon/feat/usage-record-contracts
XnLemon Jul 9, 2026
db7f629
platform: tighten routing identity contracts
xianingawa-wq Jul 9, 2026
8c34131
fix(platform): reconcile hardened contracts with gateway
XnLemon Jul 9, 2026
2db2997
Merge pull request #32 from XnLemon/feat/platform-contracts
XnLemon Jul 9, 2026
8c6ffc5
platform: add budget decision audit contracts
XnLemon Jul 8, 2026
1c775a1
Merge pull request #33 from XnLemon/feat/tool-approval-summary
XnLemon Jul 9, 2026
e29fb65
platform/gateway: add minimum loop acceptance test
XnLemon Jul 8, 2026
ac3a2d4
Merge pull request #34 from XnLemon/feat/budget-decision-audit
XnLemon Jul 9, 2026
b9bc7e3
platform/gateway: add outbound dispatch acceptance test
XnLemon Jul 9, 2026
9ea340f
Merge pull request #35 from XnLemon/feat/platform-minimum-loop-accept…
XnLemon Jul 9, 2026
3c337c6
Merge pull request #36 from XnLemon/feat/platform-outbound-dispatch-a…
XnLemon Jul 9, 2026
232e3bd
platform/gateway: correlate audit trace ids
XnLemon Jul 9, 2026
b2d5aff
platform/gateway: add trace skeleton spans
XnLemon Jul 9, 2026
eb4ec76
Merge pull request #37 from XnLemon/feat/platform-trace-audit-correla…
XnLemon Jul 9, 2026
36deb82
Merge pull request #38 from XnLemon/feat/platform-trace-skeleton-cont…
XnLemon Jul 9, 2026
6d42695
platform/gateway: enable runner session trace
XnLemon Jul 9, 2026
054d83b
platform/gateway: add message event trace contract
XnLemon Jul 9, 2026
3fa9c56
Merge pull request #40 from XnLemon/feat/platform-runner-session-trace
XnLemon Jul 9, 2026
94415bf
Merge pull request #41 from XnLemon/feat/platform-message-event-trace
XnLemon Jul 9, 2026
f05fa12
feat(platform): mark tool call trace spans
XnLemon Jul 9, 2026
5c52f3b
feat(platform): trace memory search spans
XnLemon Jul 9, 2026
19f09a0
feat(platform): trace memory write spans
XnLemon Jul 9, 2026
f919d4d
feat(platform): mark summary create trace spans
XnLemon Jul 9, 2026
dc28c70
fix(platform): address CI checks
XnLemon Jul 9, 2026
3daa913
fix(platform): address CodeRabbit review feedback
XnLemon Jul 9, 2026
6fb7ee8
fix(platform): bound in-memory sink records
XnLemon Jul 9, 2026
36896aa
storage: add tenant-aware backend router
XnLemon Jul 10, 2026
1b0147e
memory: add tenant-scoped memory knowledge abstraction
XnLemon Jul 10, 2026
177d609
artifact: add tenant-scoped object store
XnLemon Jul 10, 2026
fd77bf6
feat(platform): wire tenant storage into runtimes
xianingawa-wq Jul 10, 2026
f9a509b
feat(platform): enforce runtime tool governance
xianingawa-wq Jul 11, 2026
541dd09
fix lint failures in phase2 tool surface stack
xianingawa-wq Jul 11, 2026
a8099e4
fix remaining user tool tracking ineffassign
xianingawa-wq Jul 11, 2026
4cd8329
preserve runtime builder API compatibility
xianingawa-wq Jul 11, 2026
f1f4548
fix base lint issues in tool execution
xianingawa-wq Jul 11, 2026
dc0aa47
fix toolsurface lint ineffassign
xianingawa-wq Jul 11, 2026
133a545
feat(gateway): add budget preflight gate
xianingawa-wq Jul 11, 2026
3b80b0e
feat(gateway): record runner usage
xianingawa-wq Jul 11, 2026
76a1475
feat(gateway): apply model cost policy
xianingawa-wq Jul 11, 2026
d6cf2ab
feat(platform): add budget usage snapshots
xianingawa-wq Jul 11, 2026
86cabbb
reduce budget snapshot validation complexity
xianingawa-wq Jul 11, 2026
4755f64
feat(gateway): enforce text length channel limit
xianingawa-wq Jul 11, 2026
63d7f0f
feat(gateway): enforce file size channel limit
xianingawa-wq Jul 11, 2026
53ddb3a
feat(gateway): enforce channel rate limits
xianingawa-wq Jul 11, 2026
06cf699
feat(gateway): enforce mime type channel limit
xianingawa-wq Jul 11, 2026
b930618
feat(gateway): enforce user concurrency limits
xianingawa-wq Jul 11, 2026
38232d9
feat(gateway): enrich reject audit context
xianingawa-wq Jul 11, 2026
5afad20
feat(toolpolicy): enrich audit runtime context
xianingawa-wq Jul 11, 2026
5e64885
feat(platform): expand audit query dimensions
xianingawa-wq Jul 11, 2026
04f34db
reduce audit query filter complexity
xianingawa-wq Jul 11, 2026
7d40a54
feat(toolpolicy): audit budget remaining summary
xianingawa-wq Jul 11, 2026
762411d
feat(platform): query redacted audit details
xianingawa-wq Jul 11, 2026
ae9a3a8
feat(gateway): enrich budget audit runtime context
xianingawa-wq Jul 11, 2026
cbdb32d
feat(gateway): record redaction failed audit
xianingawa-wq Jul 11, 2026
4552555
feat(platform): add tool approval audit records
xianingawa-wq Jul 11, 2026
6849a5c
feat(approval): audit tool approval runtime decisions
xianingawa-wq Jul 11, 2026
c050a3a
feat(gateway): propagate approval audit context
xianingawa-wq Jul 11, 2026
1b9d19c
feat(worker): wire approval guardrail plugin
xianingawa-wq Jul 11, 2026
2d3787a
feat(approval): include tool metadata in approval context
xianingawa-wq Jul 11, 2026
7f543cd
feat(approval): return approval denied result
xianingawa-wq Jul 11, 2026
bfe5e93
feat(approval): record approval required metrics
xianingawa-wq Jul 11, 2026
f3a97fd
feat(audit): record audit write failure metrics
xianingawa-wq Jul 11, 2026
75fb239
fix runtime builder options calls in approval tests
xianingawa-wq Jul 11, 2026
f3b3ab1
feat(tool): record permission denied metrics
xianingawa-wq Jul 11, 2026
97a5fa9
feat(gateway): record budget denied metrics
xianingawa-wq Jul 11, 2026
fd732fa
feat(gateway): record rate limit metrics
xianingawa-wq Jul 11, 2026
359a9f4
feat(gateway): record idempotency hit metrics
xianingawa-wq Jul 11, 2026
a8819d3
fix(approval): redact reviewer decision text
xianingawa-wq Jul 11, 2026
fb85850
fix(platform): redact spaced secret assignments
xianingawa-wq Jul 11, 2026
75002b5
test(toolpolicy): cover deny audit records
xianingawa-wq Jul 11, 2026
83e8ed4
test(gateway): cover trace id tieouts
xianingawa-wq Jul 11, 2026
f9ff772
fix(langfuse): redact observation exports
xianingawa-wq Jul 11, 2026
ddb6fd8
fix(platform): redact config diff secret refs
xianingawa-wq Jul 11, 2026
50d976d
fix(artifactstore): redact storage errors
xianingawa-wq Jul 11, 2026
402953b
fix(audit): reject unsafe tenant queries
xianingawa-wq Jul 11, 2026
782305b
fix(usage): reject unsafe summary filters
xianingawa-wq Jul 11, 2026
960c075
fix(trace): redact endpoint config errors
xianingawa-wq Jul 11, 2026
2862417
fix(secret): reject unsafe rotation tenants
xianingawa-wq Jul 11, 2026
e163f51
fix(tool): redact permission result reasons
xianingawa-wq Jul 11, 2026
4cbcd6e
fix(processor): redact code execution errors
xianingawa-wq Jul 11, 2026
cc081c0
fix(awaitreply): redact await reply responses
xianingawa-wq Jul 11, 2026
7e52085
fix(agent): redact error event messages
xianingawa-wq Jul 11, 2026
796d6d3
fix(a2a): redact error event messages
xianingawa-wq Jul 11, 2026
15ac566
fix(dify): redact error event messages
xianingawa-wq Jul 11, 2026
f9efda8
fix(cliagent): redact command error messages
xianingawa-wq Jul 11, 2026
860a8ae
fix(trpcagent): redact server error messages
xianingawa-wq Jul 11, 2026
94f7143
fix(trpcagent): redact runner http errors
xianingawa-wq Jul 11, 2026
9188a9c
fix(model): redact provider error responses
xianingawa-wq Jul 11, 2026
d1662df
fix(model): redact remaining provider error responses
xianingawa-wq Jul 11, 2026
cca7b08
fix(graph): redact runtime error events
xianingawa-wq Jul 11, 2026
b8a1f25
fix(gateway): redact public error responses
xianingawa-wq Jul 11, 2026
7b0e57c
fix(a2a): redact structured error metadata
xianingawa-wq Jul 11, 2026
229c572
fix(flow): redact processor error events (#107)
XnLemon Jul 12, 2026
da9b182
fix(redaction): close authorization scrubber gaps
xianingawa-wq Jul 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions agent/a2aagent/a2a_agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
itrace "trpc.group/trpc-go/trpc-agent-go/internal/trace"
"trpc.group/trpc-go/trpc-agent-go/log"
"trpc.group/trpc-go/trpc-agent-go/model"
"trpc.group/trpc-go/trpc-agent-go/platform"
semconvtrace "trpc.group/trpc-go/trpc-agent-go/telemetry/semconv/trace"
"trpc.group/trpc-go/trpc-agent-go/tool"
)
Expand Down Expand Up @@ -155,6 +156,7 @@ func (r *A2AAgent) sendErrorEvent(
err error,
) *model.ResponseError {
respErr := model.ResponseErrorFromError(err, model.ErrorTypeRunError)
redactAgentResponseError(respErr)
agent.EmitEvent(ctx, invocation, eventChan, event.New(
invocation.InvocationID,
r.name,
Expand All @@ -166,6 +168,17 @@ func (r *A2AAgent) sendErrorEvent(
return respErr
}

func redactAgentResponseError(respErr *model.ResponseError) {
if respErr == nil {
return
}
redactor, err := platform.NewRedactor()
if err != nil {
return
}
respErr.Message = redactor.Redact(respErr.Message)
}

// validateA2ARequestOptions validates that all A2A request options are of the correct type
func (r *A2AAgent) validateA2ARequestOptions(invocation *agent.Invocation) error {
if invocation.RunOptions.A2ARequestOptions == nil {
Expand Down
27 changes: 27 additions & 0 deletions agent/a2aagent/a2a_agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2914,6 +2914,33 @@ func TestA2AAgent_sendErrorEvent_UsesRunErrorType(t *testing.T) {
require.Equal(t, evt.Response.Error, respErr)
}

func TestA2AAgent_sendErrorEvent_RedactsSensitiveMessage(t *testing.T) {
a := &A2AAgent{name: "remote-agent"}
eventCh := make(chan *event.Event, 1)
invocation := &agent.Invocation{InvocationID: "inv-test"}

respErr := a.sendErrorEvent(
context.Background(),
eventCh,
invocation,
fmt.Errorf("request failed Authorization: Bearer raw-token\napi_key=sk-1234567890abcdef\nCookie: session=abc; sid=def"),
)

require.NotNil(t, respErr)
require.Equal(t, model.ErrorTypeRunError, respErr.Type)
for _, secret := range []string{"raw-token", "sk-1234567890abcdef", "session=abc", "sid=def"} {
require.NotContains(t, respErr.Message, secret)
}
for _, redacted := range []string{"Authorization: ****", "api_key=****", "Cookie: ****"} {
require.Contains(t, respErr.Message, redacted)
}

evt := <-eventCh
require.NotNil(t, evt)
require.NotNil(t, evt.Response)
require.Equal(t, respErr, evt.Response.Error)
}

func TestA2AAgent_aggregateEventContent_IgnoresErrorResponses(t *testing.T) {
a := &A2AAgent{name: "remote-agent"}
builder := &strings.Builder{}
Expand Down
59 changes: 56 additions & 3 deletions agent/claudecode/claude_agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"trpc.group/trpc-go/trpc-agent-go/event"
"trpc.group/trpc-go/trpc-agent-go/log"
"trpc.group/trpc-go/trpc-agent-go/model"
"trpc.group/trpc-go/trpc-agent-go/platform"
"trpc.group/trpc-go/trpc-agent-go/session"
"trpc.group/trpc-go/trpc-agent-go/tool"
)
Expand Down Expand Up @@ -115,6 +116,7 @@ func (a *claudeCodeAgent) runInvocation(ctx context.Context, invocation *agent.I
if len(combined) == 0 {
msg = runErr.Error()
}
msg = redactAgentErrorMessage(msg)
rsp := &model.Response{
Object: model.ObjectTypeError,
Done: true,
Expand Down Expand Up @@ -187,11 +189,52 @@ func (a *claudeCodeAgent) handleRawOutputHook(
CLISessionID: cliSessionID,
Prompt: invocation.Message.Content,
Stdout: stdout,
Stderr: stderr,
Stderr: sanitizeRawOutputHookStderr(stderr),
Error: runErr,
})
}

func sanitizeRawOutputHookStderr(stderr []byte) []byte {
lines := bytes.SplitAfter(stderr, []byte("\n"))
filtered := make([]byte, 0, len(stderr))
for _, line := range lines {
sensitiveAt := sensitiveCLIOutputIndex(line)
if sensitiveAt == 0 {
continue
}
if sensitiveAt > 0 {
line = bytes.TrimRight(line[:sensitiveAt], " \t\r\n")
if len(line) == 0 {
continue
}
line = append(line, '\n')
}
filtered = append(filtered, line...)
}
return filtered
}

func sensitiveCLIOutputIndex(line []byte) int {
lower := bytes.ToLower(line)
sensitiveMarkers := [][]byte{
[]byte("authorization:"),
[]byte("authorization="),
[]byte("api_key"),
[]byte("apikey"),
[]byte("api-key"),
[]byte("cookie:"),
[]byte("cookie="),
}
first := -1
for _, marker := range sensitiveMarkers {
idx := bytes.Index(lower, marker)
if idx >= 0 && (first < 0 || idx < first) {
first = idx
}
}
return first
}

// emitFlowError emits an error response event and stops further invocation processing.
func (a *claudeCodeAgent) emitFlowError(
ctx context.Context,
Expand All @@ -200,6 +243,8 @@ func (a *claudeCodeAgent) emitFlowError(
combined []byte,
flowErr error,
) {
content := redactAgentErrorMessage(string(combined))
message := redactAgentErrorMessage(flowErr.Error())
rsp := &model.Response{
Object: model.ObjectTypeError,
Done: true,
Expand All @@ -208,18 +253,26 @@ func (a *claudeCodeAgent) emitFlowError(
Index: 0,
Message: model.Message{
Role: model.RoleAssistant,
Content: string(combined),
Content: content,
},
},
},
Error: &model.ResponseError{
Type: model.ErrorTypeFlowError,
Message: flowErr.Error(),
Message: message,
},
}
a.emitEvent(ctx, invocation, out, event.NewResponseEvent(invocation.InvocationID, a.name, rsp))
}

func redactAgentErrorMessage(message string) string {
redactor, err := platform.NewRedactor()
if err != nil {
return message
}
return redactor.Redact(message)
}

// runWithSession executes the CLI with resume-first semantics and returns stdout/stderr.
func (a *claudeCodeAgent) runWithSession(ctx context.Context, sessionID, prompt string) ([]byte, []byte, error) {
// Copy base args to avoid mutating shared backing arrays across concurrent invocations.
Expand Down
28 changes: 22 additions & 6 deletions agent/claudecode/claude_agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func TestClaudeCodeAgent_Run_CommandError(t *testing.T) {

runner := &scriptedRunner{
run: func(cmd command) ([]byte, []byte, error) {
return nil, []byte("boom\n"), errors.New("exit 2")
return nil, []byte("boom Authorization: Bearer raw-token\napi_key=sk-1234567890abcdef\nCookie: session=abc; sid=def"), errors.New("exit 2")
},
}

Expand All @@ -214,8 +214,10 @@ func TestClaudeCodeAgent_Run_CommandError(t *testing.T) {
require.Len(t, events, 1)
require.True(t, events[0].IsFinalResponse())
require.NotNil(t, events[0].Error)
require.Equal(t, "boom", events[0].Error.Message)
require.Equal(t, "boom", events[0].Choices[0].Message.Content)
require.Contains(t, events[0].Error.Message, "boom")
require.Contains(t, events[0].Choices[0].Message.Content, "boom")
assertRedactedAgentErrorMessage(t, events[0].Error.Message)
assertRedactedAgentErrorMessage(t, events[0].Choices[0].Message.Content)
}

func TestClaudeCodeAgent_Run_RawOutputHook(t *testing.T) {
Expand All @@ -233,7 +235,7 @@ func TestClaudeCodeAgent_Run_RawOutputHook(t *testing.T) {
transcript := `[{"type":"result","result":"hello"}]`
runner := &scriptedRunner{
run: func(cmd command) ([]byte, []byte, error) {
return []byte(transcript), []byte("warn\n"), nil
return []byte(transcript), []byte("warn Authorization: Bearer raw-token\napi_key=sk-1234567890abcdef\nX-Api-Key: header-secret\nCookie: session=abc; sid=def"), nil
},
}

Expand Down Expand Up @@ -262,7 +264,7 @@ func TestClaudeCodeAgent_Run_RawOutputHook(t *testing.T) {
require.Equal(t, cliSessionID(sess), got.CLISessionID)
require.Equal(t, "Hi.", got.Prompt)
require.Equal(t, transcript, string(got.Stdout))
require.Equal(t, "warn\n", string(got.Stderr))
require.Equal(t, "warn\nX-\n", string(got.Stderr))
require.NoError(t, got.Error)
}

Expand All @@ -285,7 +287,7 @@ func TestClaudeCodeAgent_Run_RawOutputHookError(t *testing.T) {
},
}

hookErr := errors.New("hook failed")
hookErr := errors.New("hook failed Authorization: Bearer raw-token\napi_key=sk-1234567890abcdef\nCookie: session=abc; sid=def")
var called bool
ag, err := New(
WithBin("claude"),
Expand All @@ -309,8 +311,22 @@ func TestClaudeCodeAgent_Run_RawOutputHookError(t *testing.T) {
require.Equal(t, model.ErrorTypeFlowError, events[0].Error.Type)
require.Contains(t, events[0].Error.Message, "raw output hook")
require.Contains(t, events[0].Error.Message, "hook failed")
assertRedactedAgentErrorMessage(t, events[0].Error.Message)
require.Contains(t, events[0].Choices[0].Message.Content, transcript)
require.Contains(t, events[0].Choices[0].Message.Content, "warn")
for _, secret := range []string{"raw-token", "sk-1234567890abcdef", "session=abc", "sid=def"} {
require.NotContains(t, events[0].Choices[0].Message.Content, secret)
}
}

func assertRedactedAgentErrorMessage(t *testing.T, message string) {
t.Helper()
for _, secret := range []string{"raw-token", "sk-1234567890abcdef", "session=abc", "sid=def"} {
require.NotContains(t, message, secret)
}
for _, redacted := range []string{"Authorization: ****", "api_key=****", "Cookie: ****"} {
require.Contains(t, message, redacted)
}
}

func TestClaudeCodeAgent_InfoAndRunnerArgs(t *testing.T) {
Expand Down
16 changes: 14 additions & 2 deletions agent/codex/codex_agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"trpc.group/trpc-go/trpc-agent-go/event"
"trpc.group/trpc-go/trpc-agent-go/log"
"trpc.group/trpc-go/trpc-agent-go/model"
"trpc.group/trpc-go/trpc-agent-go/platform"
"trpc.group/trpc-go/trpc-agent-go/session"
"trpc.group/trpc-go/trpc-agent-go/tool"
)
Expand Down Expand Up @@ -189,6 +190,7 @@ func (a *codexAgent) emitRunError(ctx context.Context, invocation *agent.Invocat
if len(combined) == 0 {
msg = runErr.Error()
}
msg = redactAgentErrorMessage(msg)
rsp := &model.Response{
Object: model.ObjectTypeError,
Done: true,
Expand All @@ -211,6 +213,8 @@ func (a *codexAgent) emitRunError(ctx context.Context, invocation *agent.Invocat

// emitFlowError emits an error response event and stops further invocation processing.
func (a *codexAgent) emitFlowError(ctx context.Context, invocation *agent.Invocation, out chan<- *event.Event, combined []byte, flowErr error) {
content := redactAgentErrorMessage(string(combined))
message := redactAgentErrorMessage(flowErr.Error())
rsp := &model.Response{
Object: model.ObjectTypeError,
Done: true,
Expand All @@ -219,18 +223,26 @@ func (a *codexAgent) emitFlowError(ctx context.Context, invocation *agent.Invoca
Index: 0,
Message: model.Message{
Role: model.RoleAssistant,
Content: string(combined),
Content: content,
},
},
},
Error: &model.ResponseError{
Type: model.ErrorTypeFlowError,
Message: flowErr.Error(),
Message: message,
},
}
a.emitEvent(ctx, invocation, out, event.NewResponseEvent(invocation.InvocationID, a.name, rsp))
}

func redactAgentErrorMessage(message string) string {
redactor, err := platform.NewRedactor()
if err != nil {
return message
}
return redactor.Redact(message)
}

// runWithSession executes the CLI with resume-first semantics and returns stdout/stderr.
func (a *codexAgent) runWithSession(ctx context.Context, threadID, prompt string) ([]byte, []byte, error) {
if strings.TrimSpace(threadID) != "" {
Expand Down
39 changes: 31 additions & 8 deletions agent/codex/codex_agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ func TestCodexAgent_Run_ResumeAndCreateErrorsReturnRunError(t *testing.T) {
if len(cmd.args) > 1 && cmd.args[1] == "resume" {
return nil, []byte("resume unavailable"), errors.New("resume exit 1")
}
return nil, []byte("create unavailable"), errors.New("create exit 1")
return nil, []byte("create failed Authorization: Bearer raw-token\napi_key=sk-1234567890abcdef\nCookie: session=abc; sid=def"), errors.New("create exit 1")
},
}
ag, err := New(withCommandRunner(runner))
Expand All @@ -196,7 +196,10 @@ func TestCodexAgent_Run_ResumeAndCreateErrorsReturnRunError(t *testing.T) {
require.Len(t, events, 1)
require.NotNil(t, events[0].Error)
require.Equal(t, model.ErrorTypeRunError, events[0].Error.Type)
require.Equal(t, "create unavailable", events[0].Error.Message)
require.Contains(t, events[0].Error.Message, "create failed")
require.Contains(t, events[0].Choices[0].Message.Content, "create failed")
assertRedactedAgentErrorMessage(t, events[0].Error.Message)
assertRedactedAgentErrorMessage(t, events[0].Choices[0].Message.Content)
calls := runner.Calls()
require.Len(t, calls, 2)
require.Equal(t, []string{"exec", "resume", "--json", "thread-1"}, calls[0].args)
Expand Down Expand Up @@ -250,7 +253,7 @@ func TestCodexAgent_Run_RawOutputHookReceivesCommandError(t *testing.T) {
runErr := errors.New("exit 1")
runner := &scriptedRunner{
run: func(cmd command) ([]byte, []byte, error) {
return []byte("stdout text"), []byte("stderr text"), runErr
return []byte("stdout text"), []byte("stderr Authorization: Bearer raw-token\napi_key=sk-1234567890abcdef\nCookie: session=abc; sid=def"), runErr
},
}
var got RawOutputHookArgs
Expand All @@ -269,11 +272,15 @@ func TestCodexAgent_Run_RawOutputHookReceivesCommandError(t *testing.T) {
require.ErrorIs(t, got.Error, runErr)
require.Equal(t, "--help", got.Prompt)
require.Equal(t, "stdout text", string(got.Stdout))
require.Equal(t, "stderr text", string(got.Stderr))
require.Equal(t, "stderr Authorization: Bearer raw-token\napi_key=sk-1234567890abcdef\nCookie: session=abc; sid=def", string(got.Stderr))
require.NotNil(t, events[0].Error)
require.Equal(t, model.ErrorTypeRunError, events[0].Error.Type)
require.Equal(t, "stdout text\nstderr text", events[0].Error.Message)
require.Equal(t, "stdout text\nstderr text", events[0].Choices[0].Message.Content)
require.Contains(t, events[0].Error.Message, "stdout text")
require.Contains(t, events[0].Error.Message, "stderr")
require.Contains(t, events[0].Choices[0].Message.Content, "stdout text")
require.Contains(t, events[0].Choices[0].Message.Content, "stderr")
assertRedactedAgentErrorMessage(t, events[0].Error.Message)
assertRedactedAgentErrorMessage(t, events[0].Choices[0].Message.Content)
calls := runner.Calls()
require.Len(t, calls, 1)
require.Equal(t, []string{"exec", "--json"}, calls[0].args)
Expand All @@ -284,10 +291,10 @@ func TestCodexAgent_Run_RawOutputHookError(t *testing.T) {
ctx := context.Background()
sess := session.NewSession("app", "user", "sess-hook-2")
inv := newTestInvocation("inv-hook-2", sess, "Hi.")
hookErr := errors.New("hook failed")
hookErr := errors.New("hook failed Authorization: Bearer raw-token\napi_key=sk-1234567890abcdef\nCookie: session=abc; sid=def")
runner := &scriptedRunner{
run: func(cmd command) ([]byte, []byte, error) {
return []byte(codexTranscript("thread-hook-2", "hello")), []byte("warn\n"), nil
return []byte(codexTranscript("thread-hook-2", "hello")), []byte("warn\napi_key=sk-1234567890abcdef\nCookie: session=abc; sid=def"), nil
},
}
ag, err := New(
Expand All @@ -307,8 +314,24 @@ func TestCodexAgent_Run_RawOutputHookError(t *testing.T) {
require.Equal(t, model.ErrorTypeFlowError, events[0].Error.Type)
require.Contains(t, events[0].Error.Message, "raw output hook")
require.Contains(t, events[0].Error.Message, "hook failed")
assertRedactedAgentErrorMessage(t, events[0].Error.Message)
require.Contains(t, events[0].Choices[0].Message.Content, "thread-hook-2")
require.Contains(t, events[0].Choices[0].Message.Content, "warn")
require.NotContains(t, events[0].Choices[0].Message.Content, "sk-1234567890abcdef")
require.NotContains(t, events[0].Choices[0].Message.Content, "session=abc")
require.NotContains(t, events[0].Choices[0].Message.Content, "sid=def")
require.Contains(t, events[0].Choices[0].Message.Content, "api_key=****")
require.Contains(t, events[0].Choices[0].Message.Content, "Cookie: ****")
}

func assertRedactedAgentErrorMessage(t *testing.T, message string) {
t.Helper()
for _, secret := range []string{"raw-token", "sk-1234567890abcdef", "session=abc", "sid=def"} {
require.NotContains(t, message, secret)
}
for _, redacted := range []string{"Authorization: ****", "api_key=****", "Cookie: ****"} {
require.Contains(t, message, redacted)
}
}

func TestCodexAgent_InfoAndRunnerArgs(t *testing.T) {
Expand Down
Loading
Loading