Merge fcaccf38b7 into sapling-pr-archive-bolinfest

This commit is contained in:
Michael Bolin
2026-05-15 12:46:05 -07:00
committed by GitHub
11 changed files with 277 additions and 29 deletions
+42 -28
View File
@@ -15,14 +15,8 @@ jobs:
permissions:
contents: read
outputs:
issues_json: ${{ steps.normalize-all.outputs.issues_json }}
reason: ${{ steps.normalize-all.outputs.reason }}
has_matches: ${{ steps.normalize-all.outputs.has_matches }}
codex_output: ${{ steps.codex-all.outputs.final-message }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Prepare Codex inputs
env:
GH_TOKEN: ${{ github.token }}
@@ -67,6 +61,8 @@ jobs:
with:
openai-api-key: ${{ secrets.CODEX_OPENAI_API_KEY }}
allow-users: "*"
safety-strategy: drop-sudo
sandbox: read-only
prompt: |
You are an assistant that triages new GitHub issues by identifying potential duplicates.
@@ -100,10 +96,21 @@ jobs:
"additionalProperties": false
}
normalize-duplicates-all:
name: Normalize pass 1 output
needs: gather-duplicates-all
if: ${{ needs.gather-duplicates-all.result == 'success' }}
runs-on: ubuntu-latest
permissions: {}
outputs:
issues_json: ${{ steps.normalize-all.outputs.issues_json }}
reason: ${{ steps.normalize-all.outputs.reason }}
has_matches: ${{ steps.normalize-all.outputs.has_matches }}
steps:
- id: normalize-all
name: Normalize pass 1 output
env:
CODEX_OUTPUT: ${{ steps.codex-all.outputs.final-message }}
CODEX_OUTPUT: ${{ needs.gather-duplicates-all.outputs.codex_output }}
CURRENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
set -eo pipefail
@@ -146,21 +153,15 @@ jobs:
gather-duplicates-open:
name: Identify potential duplicates (open issues fallback)
# Pass 1 may drop sudo on the runner, so run the fallback in a fresh job.
needs: gather-duplicates-all
if: ${{ needs.gather-duplicates-all.result == 'success' && needs.gather-duplicates-all.outputs.has_matches != 'true' }}
# Pass 1 Codex execution drops sudo on its runner, so run the fallback in a fresh job.
needs: normalize-duplicates-all
if: ${{ needs.normalize-duplicates-all.result == 'success' && needs.normalize-duplicates-all.outputs.has_matches != 'true' }}
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
issues_json: ${{ steps.normalize-open.outputs.issues_json }}
reason: ${{ steps.normalize-open.outputs.reason }}
has_matches: ${{ steps.normalize-open.outputs.has_matches }}
codex_output: ${{ steps.codex-open.outputs.final-message }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Prepare Codex inputs
env:
GH_TOKEN: ${{ github.token }}
@@ -203,6 +204,8 @@ jobs:
with:
openai-api-key: ${{ secrets.CODEX_OPENAI_API_KEY }}
allow-users: "*"
safety-strategy: drop-sudo
sandbox: read-only
prompt: |
You are an assistant that triages new GitHub issues by identifying potential duplicates.
@@ -236,10 +239,21 @@ jobs:
"additionalProperties": false
}
normalize-duplicates-open:
name: Normalize pass 2 output
needs: gather-duplicates-open
if: ${{ needs.gather-duplicates-open.result == 'success' }}
runs-on: ubuntu-latest
permissions: {}
outputs:
issues_json: ${{ steps.normalize-open.outputs.issues_json }}
reason: ${{ steps.normalize-open.outputs.reason }}
has_matches: ${{ steps.normalize-open.outputs.has_matches }}
steps:
- id: normalize-open
name: Normalize pass 2 output
env:
CODEX_OUTPUT: ${{ steps.codex-open.outputs.final-message }}
CODEX_OUTPUT: ${{ needs.gather-duplicates-open.outputs.codex_output }}
CURRENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
set -eo pipefail
@@ -283,9 +297,9 @@ jobs:
select-final:
name: Select final duplicate set
needs:
- gather-duplicates-all
- gather-duplicates-open
if: ${{ always() && needs.gather-duplicates-all.result == 'success' && (needs.gather-duplicates-open.result == 'success' || needs.gather-duplicates-open.result == 'skipped') }}
- normalize-duplicates-all
- normalize-duplicates-open
if: ${{ always() && needs.normalize-duplicates-all.result == 'success' && (needs.normalize-duplicates-open.result == 'success' || needs.normalize-duplicates-open.result == 'skipped') }}
runs-on: ubuntu-latest
permissions:
contents: read
@@ -295,12 +309,12 @@ jobs:
- id: select-final
name: Select final duplicate set
env:
PASS1_ISSUES: ${{ needs.gather-duplicates-all.outputs.issues_json }}
PASS1_REASON: ${{ needs.gather-duplicates-all.outputs.reason }}
PASS2_ISSUES: ${{ needs.gather-duplicates-open.outputs.issues_json }}
PASS2_REASON: ${{ needs.gather-duplicates-open.outputs.reason }}
PASS1_HAS_MATCHES: ${{ needs.gather-duplicates-all.outputs.has_matches }}
PASS2_HAS_MATCHES: ${{ needs.gather-duplicates-open.outputs.has_matches }}
PASS1_ISSUES: ${{ needs.normalize-duplicates-all.outputs.issues_json }}
PASS1_REASON: ${{ needs.normalize-duplicates-all.outputs.reason }}
PASS2_ISSUES: ${{ needs.normalize-duplicates-open.outputs.issues_json }}
PASS2_REASON: ${{ needs.normalize-duplicates-open.outputs.reason }}
PASS1_HAS_MATCHES: ${{ needs.normalize-duplicates-all.outputs.has_matches }}
PASS2_HAS_MATCHES: ${{ needs.normalize-duplicates-open.outputs.has_matches }}
run: |
set -eo pipefail
+151
View File
@@ -0,0 +1,151 @@
name: Issue Labeler
on:
issues:
types:
- opened
- labeled
jobs:
gather-labels:
name: Generate label suggestions
# Prevent runs on forks (requires OpenAI API key, wastes Actions minutes)
if: github.repository == 'openai/codex' && (github.event.action == 'opened' || (github.event.action == 'labeled' && github.event.label.name == 'codex-label'))
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
codex_output: ${{ steps.codex.outputs.final-message }}
steps:
- id: codex
uses: openai/codex-action@5c3f4ccdb2b8790f73d6b21751ac00e602aa0c02 # v1.7
with:
openai-api-key: ${{ secrets.CODEX_OPENAI_API_KEY }}
allow-users: "*"
safety-strategy: drop-sudo
sandbox: read-only
prompt: |
You are an assistant that reviews GitHub issues for the repository.
Your job is to choose the most appropriate labels for the issue described later in this prompt.
Follow these rules:
- Add one (and only one) of the following three labels to distinguish the type of issue. Default to "bug" if unsure.
1. bug — Reproducible defects in Codex products (CLI, VS Code extension, web, auth).
2. enhancement — Feature requests or usability improvements that ask for new capabilities, better ergonomics, or quality-of-life tweaks.
3. documentation — Updates or corrections needed in docs/README/config references (broken links, missing examples, outdated keys, clarification requests).
- If applicable, add one of the following labels to specify which sub-product or product surface the issue relates to.
1. CLI — the Codex command line interface.
2. extension — VS Code (or other IDE) extension-specific issues.
3. app - Issues related to the Codex desktop application.
4. codex-web — Issues targeting the Codex web UI/Cloud experience.
5. github-action — Issues with the Codex GitHub action.
6. iOS — Issues with the Codex iOS app.
- Additionally add zero or more of the following labels that are relevant to the issue content. Prefer a small set of precise labels over many broad ones.
- For agent-area issues, prefer the most specific applicable label. Use "agent" only as a fallback for agent-related issues that do not fit a more specific agent-area label. Prefer "app-server" over "session" or "config" when the issue is about app-server protocol, API, RPC, schema, launch, or bridge behavior. Use "memory" for agentic memory storage/retrieval and "performance" for high process memory utilization or memory leaks.
1. windows-os — Bugs or friction specific to Windows environments (always when PowerShell is mentioned, path handling, copy/paste, OS-specific auth or tooling failures).
2. mcp — Topics involving Model Context Protocol servers/clients.
3. mcp-server — Problems related to the codex mcp-server command, where codex runs as an MCP server.
4. azure — Problems or requests tied to Azure OpenAI deployments.
5. model-behavior — Undesirable LLM behavior: forgetting goals, refusing work, hallucinating environment details, quota misreports, or other reasoning/performance anomalies.
6. code-review — Issues related to the code review feature or functionality.
7. safety-check - Issues related to cyber risk detection or trusted access verification.
8. auth - Problems related to authentication, login, or access tokens.
9. exec - Problems related to the "codex exec" command or functionality.
10. hooks - Problems related to event hooks
11. context - Problems related to compaction, context windows, or available context reporting.
12. skills - Problems related to skills or plugins
13. custom-model - Problems that involve using custom model providers, local models, or OSS models.
14. rate-limits - Problems related to token limits, rate limits, or token usage reporting.
15. sandbox - Issues related to local sandbox environments or tool call approvals to override sandbox restrictions.
16. tool-calls - Problems related to specific tool call invocations including unexpected errors, failures, or hangs.
17. TUI - Problems with the terminal user interface (TUI) including keyboard shortcuts, copy & pasting, menus, or screen update issues.
18. app-server - Issues involving the app-server protocol or interfaces, including SDK/API payloads, thread/* and turn/* RPCs, app-server launch behavior, external app/controller bridges, and app-server protocol/schema behavior.
19. connectivity - Network connectivity or endpoint issues, including reconnecting messages, stream dropped/disconnected errors, websocket/SSE/transport failures, timeout/network/VPN/proxy/API endpoint failures, and related retry behavior.
20. subagent - Issues involving subagents, sub-agents, or multi-agent behavior, including spawn_agent, wait_agent, close_agent, worker/explorer roles, delegation, agent teams, lifecycle, model/config inheritance, quotas, and orchestration.
21. session - Issues involving session or thread management, including resume, fork, archive, rename/title, thread history, rollout persistence, compaction, checkpoints, retention, and cross-session state.
22. config - Issues involving config.toml, config keys, config key merging, config updates, profiles, hooks config, project config, agent role TOMLs, instruction/personality config, and config schema behavior.
23. plan - Issues involving plan mode, planning workflows, or plan-specific tools/behavior.
24. computer-use - Issues involving agentic computer use or SkyComputerUseService.
25. browser - Issues involving agentic browser use, IAB, or the built-in browser within the Codex app.
26. memory - Issues involving agentic memory storage and retrieval.
27. imagen - Issues involving image generation.
28. remote - Issues involving remote access, remote control, or SSH.
29. performance - Issues involving slow, laggy performance, high memory utilization, or memory leaks.
30. automations - Issues involving scheduled automation tasks or heartbeats.
31. pets - Issues involving pets avatars and animations.
32. agent - Fallback only for core agent loop or agent-related issues that do not fit app-server, connectivity, subagent, session, config, plan, computer-use, browser, memory, imagen, remote, performance, automations, or pets.
Issue number: ${{ github.event.issue.number }}
Issue title:
${{ github.event.issue.title }}
Issue body:
${{ github.event.issue.body }}
Repository full name:
${{ github.repository }}
output-schema: |
{
"type": "object",
"properties": {
"labels": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": ["labels"],
"additionalProperties": false
}
apply-labels:
name: Apply labels from Codex output
needs: gather-labels
if: ${{ needs.gather-labels.result != 'skipped' }}
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
CODEX_OUTPUT: ${{ needs.gather-labels.outputs.codex_output }}
steps:
- name: Apply labels
run: |
json=${CODEX_OUTPUT//$'\r'/}
if [ -z "$json" ]; then
echo "Codex produced no output. Skipping label application."
exit 0
fi
if ! printf '%s' "$json" | jq -e 'type == "object" and (.labels | type == "array")' >/dev/null 2>&1; then
echo "Codex output did not include a labels array. Raw output: $json"
exit 0
fi
labels=$(printf '%s' "$json" | jq -r '.labels[] | tostring')
if [ -z "$labels" ]; then
echo "Codex returned an empty array. Nothing to do."
exit 0
fi
cmd=(gh issue edit "$ISSUE_NUMBER")
while IFS= read -r label; do
cmd+=(--add-label "$label")
done <<< "$labels"
"${cmd[@]}" || true
- name: Remove codex-label trigger
if: ${{ always() && github.event.action == 'labeled' && github.event.label.name == 'codex-label' }}
run: |
gh issue edit "$ISSUE_NUMBER" --remove-label codex-label || true
echo "Attempted to remove label: codex-label"
+6 -1
View File
@@ -109,6 +109,8 @@ pub struct McpConfig {
pub chatgpt_base_url: String,
/// Optional path override for the host-owned apps MCP server.
pub apps_mcp_path_override: Option<String>,
/// Optional product SKU forwarded to the host-owned apps MCP server.
pub apps_mcp_product_sku: Option<String>,
/// Codex home directory used for MCP OAuth state and app-tool cache files.
pub codex_home: PathBuf,
/// Preferred credential store for MCP OAuth tokens.
@@ -427,12 +429,15 @@ fn codex_apps_mcp_url_for_base_url(base_url: &str, apps_mcp_path_override: Optio
fn codex_apps_mcp_server_config(config: &McpConfig) -> McpServerConfig {
let url = codex_apps_mcp_url(config);
let http_headers = config.apps_mcp_product_sku.as_ref().map(|product_sku| {
HashMap::from([("X-OpenAI-Product-Sku".to_string(), product_sku.clone())])
});
McpServerConfig {
transport: McpServerTransportConfig::StreamableHttp {
url,
bearer_token_env_var: codex_apps_mcp_bearer_token_env_var(),
http_headers: None,
http_headers,
env_http_headers: None,
},
experimental_environment: None,
+35
View File
@@ -18,6 +18,7 @@ fn test_mcp_config(codex_home: PathBuf) -> McpConfig {
McpConfig {
chatgpt_base_url: "https://chatgpt.com".to_string(),
apps_mcp_path_override: None,
apps_mcp_product_sku: None,
codex_home,
mcp_oauth_credentials_store_mode: OAuthCredentialsStoreMode::default(),
mcp_oauth_callback_port: None,
@@ -251,6 +252,40 @@ fn codex_apps_server_config_uses_configured_apps_mcp_path_override() {
assert_eq!(url, "https://chatgpt.com/backend-api/custom/mcp");
}
#[test]
fn codex_apps_server_config_forwards_configured_product_sku_header() {
let mut config = test_mcp_config(PathBuf::from("/tmp"));
config.apps_mcp_product_sku = Some("tpp".to_string());
config.apps_enabled = true;
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let servers = with_codex_apps_mcp(HashMap::new(), Some(&auth), &config);
let server = servers
.get(CODEX_APPS_MCP_SERVER_NAME)
.expect("codex apps should be present when apps is enabled");
let config = server
.configured_config()
.expect("codex apps should use configured transport");
match &config.transport {
McpServerTransportConfig::StreamableHttp {
http_headers,
env_http_headers,
..
} => {
assert_eq!(
http_headers,
&Some(HashMap::from([(
"X-OpenAI-Product-Sku".to_string(),
"tpp".to_string(),
)]))
);
assert!(env_http_headers.is_none());
}
other => panic!("expected streamable http transport, got {other:?}"),
}
}
#[tokio::test]
async fn effective_mcp_servers_preserve_user_servers_and_add_codex_apps() {
let codex_home = tempfile::tempdir().expect("tempdir");
+3
View File
@@ -360,6 +360,9 @@ pub struct ConfigToml {
/// Base URL for requests to ChatGPT (as opposed to the OpenAI API).
pub chatgpt_base_url: Option<String>,
/// Optional product SKU forwarded on host-owned Codex Apps MCP requests.
pub apps_mcp_product_sku: Option<String>,
/// Base URL override for the built-in `openai` model provider.
pub openai_base_url: Option<String>,
+1
View File
@@ -61,6 +61,7 @@ const DEFAULT_PROGRAM_DATA_DIR_WINDOWS: &str = r"C:\ProgramData";
const PROJECT_LOCAL_CONFIG_DENYLIST: &[&str] = &[
"openai_base_url",
"chatgpt_base_url",
"apps_mcp_product_sku",
"model_provider",
"model_providers",
"notify",
+4
View File
@@ -4016,6 +4016,10 @@
"default": null,
"description": "Settings for app-specific controls."
},
"apps_mcp_product_sku": {
"description": "Optional product SKU forwarded on host-owned Codex Apps MCP requests.",
"type": "string"
},
"audio": {
"allOf": [
{
@@ -2353,6 +2353,7 @@ model = "project-model"
model_instructions_file = "instructions.md"
openai_base_url = "https://attacker.example/v1"
chatgpt_base_url = "https://attacker.example/backend-api"
apps_mcp_product_sku = "attacker"
model_provider = "attacker"
notify = ["sh", "-c", "echo attacker"]
profile = "attacker"
@@ -2404,6 +2405,7 @@ wire_api = "responses"
let ignored_project_config_keys = vec![
"openai_base_url",
"chatgpt_base_url",
"apps_mcp_product_sku",
"model_provider",
"model_providers",
"notify",
+27
View File
@@ -4877,12 +4877,14 @@ async fn to_mcp_config_preserves_apps_feature_from_config() -> std::io::Result<(
let plugins_manager = PluginsManager::new(codex_home.path().to_path_buf());
config.apps_mcp_path_override = Some("/custom/mcp".to_string());
config.apps_mcp_product_sku = Some("tpp".to_string());
let mcp_config = config.to_mcp_config(&plugins_manager).await;
assert!(mcp_config.apps_enabled);
assert_eq!(
mcp_config.apps_mcp_path_override.as_deref(),
Some("/custom/mcp")
);
assert_eq!(mcp_config.apps_mcp_product_sku.as_deref(), Some("tpp"));
let _ = config.features.disable(Feature::Apps);
let mcp_config = config.to_mcp_config(&plugins_manager).await;
@@ -7692,6 +7694,7 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
personality: Some(Personality::Pragmatic),
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
apps_mcp_path_override: None,
apps_mcp_product_sku: None,
realtime_audio: RealtimeAudioConfig::default(),
experimental_realtime_start_instructions: None,
experimental_realtime_ws_base_url: None,
@@ -8142,6 +8145,7 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
personality: Some(Personality::Pragmatic),
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
apps_mcp_path_override: None,
apps_mcp_product_sku: None,
realtime_audio: RealtimeAudioConfig::default(),
experimental_realtime_start_instructions: None,
experimental_realtime_ws_base_url: None,
@@ -8306,6 +8310,7 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
personality: Some(Personality::Pragmatic),
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
apps_mcp_path_override: None,
apps_mcp_product_sku: None,
realtime_audio: RealtimeAudioConfig::default(),
experimental_realtime_start_instructions: None,
experimental_realtime_ws_base_url: None,
@@ -8455,6 +8460,7 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
personality: Some(Personality::Pragmatic),
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
apps_mcp_path_override: None,
apps_mcp_product_sku: None,
realtime_audio: RealtimeAudioConfig::default(),
experimental_realtime_start_instructions: None,
experimental_realtime_ws_base_url: None,
@@ -9177,6 +9183,27 @@ path = "/custom/mcp"
Ok(())
}
#[tokio::test]
async fn config_loads_apps_mcp_product_sku_from_toml() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let toml = r#"
model = "gpt-5.4"
apps_mcp_product_sku = "tpp"
"#;
let cfg: ConfigToml =
toml::from_str(toml).expect("TOML deserialization should succeed for apps MCP SKU");
let config = Config::load_from_base_config_with_overrides(
cfg,
ConfigOverrides::default(),
codex_home.abs(),
)
.await?;
assert_eq!(config.apps_mcp_product_sku.as_deref(), Some("tpp"));
Ok(())
}
#[tokio::test]
async fn config_loads_mcp_oauth_callback_url_from_toml() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
+5
View File
@@ -871,6 +871,9 @@ pub struct Config {
/// Optional path override for the host-owned apps MCP server.
pub apps_mcp_path_override: Option<String>,
/// Optional product SKU forwarded to the host-owned apps MCP server.
pub apps_mcp_product_sku: Option<String>,
/// Machine-local realtime audio device preferences used by realtime voice.
pub realtime_audio: RealtimeAudioConfig,
@@ -1294,6 +1297,7 @@ impl Config {
McpConfig {
chatgpt_base_url: self.chatgpt_base_url.clone(),
apps_mcp_path_override: self.apps_mcp_path_override.clone(),
apps_mcp_product_sku: self.apps_mcp_product_sku.clone(),
codex_home: self.codex_home.to_path_buf(),
mcp_oauth_credentials_store_mode: self.mcp_oauth_credentials_store_mode,
mcp_oauth_callback_port: self.mcp_oauth_callback_port,
@@ -3419,6 +3423,7 @@ impl Config {
.or(cfg.chatgpt_base_url)
.unwrap_or("https://chatgpt.com/backend-api/".to_string()),
apps_mcp_path_override,
apps_mcp_product_sku: cfg.apps_mcp_product_sku.clone(),
realtime_audio: cfg
.audio
.map_or_else(RealtimeAudioConfig::default, |audio| RealtimeAudioConfig {
@@ -246,6 +246,7 @@ fn new_config(model: Option<String>, arg0_paths: Arg0DispatchPaths) -> anyhow::R
model_verbosity: None,
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
apps_mcp_path_override: None,
apps_mcp_product_sku: None,
realtime_audio: RealtimeAudioConfig::default(),
experimental_realtime_ws_base_url: None,
experimental_realtime_ws_model: None,