Compare commits

...

5 Commits

Author SHA1 Message Date
Dylan Hurd
ce9c4c8076 Release 0.102.0-alpha.1 2026-02-13 00:47:58 -08:00
Dylan Hurd
fca5629e34 fix(ci) lock rust toolchain at 1.93.0 to unblock (#11703)
## Summary
CI is broken on main because our CI toolchain is trying to run 1.93.1
while our rust toolchain is locked at 1.93.0. I'm sure it's likely safe
to upgrade, but let's keep things stable for now.

## Testing
- [x] CI should hopefully pass
2026-02-13 08:44:23 +00:00
Dylan Hurd
e6e4c5fa3a chore(core) Restrict model-suggested rules (#11671)
## Summary
If the model suggests a bad rule, don't show it to the user. This does
not impact the parsing of existing rules, just the ones we show.

## Testing
- [x] Added unit tests
- [x] Ran locally
2026-02-12 23:57:53 -08:00
Josh McKinney
1e75173ebd Point Codex App tooltip links to app landing page (#11515)
### Motivation
- Ensure the in-TUI Codex App call-to-action opens the app landing page
variant `https://chatgpt.com/codex?app-landing-page=true` so users reach
the intended landing experience.

### Description
- Update tooltip constants in `codex-rs/tui/src/tooltips.rs` to replace
`https://chatgpt.com/codex` with
`https://chatgpt.com/codex?app-landing-page=true` for the PAID and OTHER
tooltip variants.

### Testing
- Ran `just fmt` in `codex-rs` and `cargo test -p codex-tui`, and the
test suite completed successfully.

------
[Codex
Task](https://chatgpt.com/codex/tasks/task_i_698d20cf6f088329bb82b07d3ce76e61)
2026-02-12 23:35:57 -08:00
sayan-oai
abeafbdca1 fix: dont show NUX for upgrade-target models that are hidden (#11679)
dont show NUX for models marked with `visibility:hide`.

Tested locally
2026-02-12 20:29:22 -08:00
9 changed files with 198 additions and 19 deletions

View File

@@ -59,7 +59,7 @@ jobs:
working-directory: codex-rs
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@1.93
- uses: dtolnay/rust-toolchain@1.93.0
with:
components: rustfmt
- name: cargo fmt
@@ -75,7 +75,7 @@ jobs:
working-directory: codex-rs
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@1.93
- uses: dtolnay/rust-toolchain@1.93.0
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
with:
tool: cargo-shear
@@ -196,7 +196,7 @@ jobs:
fi
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "${packages[@]}"
fi
- uses: dtolnay/rust-toolchain@1.93
- uses: dtolnay/rust-toolchain@1.93.0
with:
targets: ${{ matrix.target }}
components: clippy
@@ -513,7 +513,7 @@ jobs:
- name: Install DotSlash
uses: facebook/install-dotslash@v2
- uses: dtolnay/rust-toolchain@1.93
- uses: dtolnay/rust-toolchain@1.93.0
with:
targets: ${{ matrix.target }}

View File

@@ -82,7 +82,7 @@ jobs:
Write-Host "Total RAM: $ramGiB GiB"
Write-Host "Disk usage:"
Get-PSDrive -PSProvider FileSystem | Format-Table -AutoSize Name, @{Name='Size(GB)';Expression={[math]::Round(($_.Used + $_.Free) / 1GB, 1)}}, @{Name='Free(GB)';Expression={[math]::Round($_.Free / 1GB, 1)}}
- uses: dtolnay/rust-toolchain@1.93
- uses: dtolnay/rust-toolchain@1.93.0
with:
targets: ${{ matrix.target }}

View File

@@ -123,7 +123,7 @@ jobs:
sudo apt-get update -y
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y libubsan1
fi
- uses: dtolnay/rust-toolchain@1.93
- uses: dtolnay/rust-toolchain@1.93.0
with:
targets: ${{ matrix.target }}

View File

@@ -31,7 +31,7 @@ jobs:
node-version: 22
cache: pnpm
- uses: dtolnay/rust-toolchain@1.93
- uses: dtolnay/rust-toolchain@1.93.0
- name: build codex
run: cargo build --bin codex

View File

@@ -105,7 +105,7 @@ jobs:
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y libubsan1
fi
- uses: dtolnay/rust-toolchain@1.93
- uses: dtolnay/rust-toolchain@1.93.0
with:
targets: ${{ matrix.target }}

View File

@@ -65,7 +65,7 @@ members = [
resolver = "2"
[workspace.package]
version = "0.0.0"
version = "0.102.0-alpha.1"
# Track the edition for all workspace crates in one place. Individual
# crates can still override this value, but keeping it here means new
# crates created with `cargo new -w ...` automatically inherit the 2024

View File

@@ -35,6 +35,54 @@ const PROMPT_CONFLICT_REASON: &str =
const RULES_DIR_NAME: &str = "rules";
const RULE_EXTENSION: &str = "rules";
const DEFAULT_POLICY_FILE: &str = "default.rules";
static BANNED_PREFIX_SUGGESTIONS: &[&[&str]] = &[
&["python3"],
&["python3", "-"],
&["python3", "-c"],
&["python"],
&["python", "-"],
&["python", "-c"],
&["py"],
&["py", "-3"],
&["pythonw"],
&["pyw"],
&["pypy"],
&["pypy3"],
&["git"],
&["bash"],
&["bash", "-lc"],
&["sh"],
&["sh", "-c"],
&["sh", "-lc"],
&["zsh"],
&["zsh", "-lc"],
&["/bin/zsh"],
&["/bin/zsh", "-lc"],
&["/bin/bash"],
&["/bin/bash", "-lc"],
&["pwsh"],
&["pwsh", "-Command"],
&["pwsh", "-c"],
&["powershell"],
&["powershell", "-Command"],
&["powershell", "-c"],
&["powershell.exe"],
&["powershell.exe", "-Command"],
&["powershell.exe", "-c"],
&["env"],
&["sudo"],
&["node"],
&["node", "-e"],
&["perl"],
&["perl", "-e"],
&["ruby"],
&["ruby", "-e"],
&["php"],
&["php", "-r"],
&["lua"],
&["lua", "-e"],
&["osascript"],
];
fn is_policy_match(rule_match: &RuleMatch) -> bool {
match rule_match {
@@ -240,6 +288,10 @@ pub async fn load_exec_policy(config_stack: &ConfigLayerStack) -> Result<Policy,
policy_paths.extend(layer_policy_paths);
}
}
tracing::trace!(
policy_paths = ?policy_paths,
"loaded exec policies"
);
let mut parser = PolicyParser::new();
for policy_path in &policy_paths {
@@ -261,6 +313,7 @@ pub async fn load_exec_policy(config_stack: &ConfigLayerStack) -> Result<Policy,
let policy = parser.build();
tracing::debug!("loaded rules from {} files", policy_paths.len());
tracing::trace!(rules = ?policy, "exec policy rules loaded");
let Some(requirements_policy) = config_stack.requirements().exec_policy.as_deref() else {
return Ok(policy);
@@ -418,6 +471,15 @@ fn derive_requested_execpolicy_amendment(
if prefix_rule.is_empty() {
return None;
}
if BANNED_PREFIX_SUGGESTIONS.iter().any(|banned| {
prefix_rule.len() == banned.len()
&& prefix_rule
.iter()
.map(String::as_str)
.eq(banned.iter().copied())
}) {
return None;
}
if matched_rules
.iter()
@@ -1384,6 +1446,102 @@ prefix_rule(
);
}
#[test]
fn derive_requested_execpolicy_amendment_returns_none_for_missing_prefix_rule() {
assert_eq!(None, derive_requested_execpolicy_amendment(None, &[]));
}
#[test]
fn derive_requested_execpolicy_amendment_returns_none_for_empty_prefix_rule() {
assert_eq!(
None,
derive_requested_execpolicy_amendment(Some(&Vec::new()), &[])
);
}
#[test]
fn derive_requested_execpolicy_amendment_returns_none_for_exact_banned_prefix_rule() {
assert_eq!(
None,
derive_requested_execpolicy_amendment(
Some(&vec!["python".to_string(), "-c".to_string()]),
&[],
)
);
}
#[test]
fn derive_requested_execpolicy_amendment_returns_none_for_windows_and_pypy_variants() {
for prefix_rule in [
vec!["py".to_string()],
vec!["py".to_string(), "-3".to_string()],
vec!["pythonw".to_string()],
vec!["pyw".to_string()],
vec!["pypy".to_string()],
vec!["pypy3".to_string()],
] {
assert_eq!(
None,
derive_requested_execpolicy_amendment(Some(&prefix_rule), &[])
);
}
}
#[test]
fn derive_requested_execpolicy_amendment_returns_none_for_shell_and_powershell_variants() {
for prefix_rule in [
vec!["bash".to_string(), "-lc".to_string()],
vec!["sh".to_string(), "-c".to_string()],
vec!["sh".to_string(), "-lc".to_string()],
vec!["zsh".to_string(), "-lc".to_string()],
vec!["/bin/bash".to_string(), "-lc".to_string()],
vec!["/bin/zsh".to_string(), "-lc".to_string()],
vec!["pwsh".to_string()],
vec!["pwsh".to_string(), "-Command".to_string()],
vec!["pwsh".to_string(), "-c".to_string()],
vec!["powershell".to_string()],
vec!["powershell".to_string(), "-Command".to_string()],
vec!["powershell".to_string(), "-c".to_string()],
vec!["powershell.exe".to_string()],
vec!["powershell.exe".to_string(), "-Command".to_string()],
vec!["powershell.exe".to_string(), "-c".to_string()],
] {
assert_eq!(
None,
derive_requested_execpolicy_amendment(Some(&prefix_rule), &[])
);
}
}
#[test]
fn derive_requested_execpolicy_amendment_allows_non_exact_banned_prefix_rule_match() {
let prefix_rule = vec![
"python".to_string(),
"-c".to_string(),
"print('hi')".to_string(),
];
assert_eq!(
Some(ExecPolicyAmendment::new(prefix_rule.clone())),
derive_requested_execpolicy_amendment(Some(&prefix_rule), &[])
);
}
#[test]
fn derive_requested_execpolicy_amendment_returns_none_when_policy_prompt_matches() {
let prefix_rule = vec!["cargo".to_string(), "build".to_string()];
let matched_rules = vec![RuleMatch::PrefixRuleMatch {
matched_prefix: vec!["cargo".to_string()],
decision: Decision::Prompt,
justification: None,
}];
assert_eq!(
None,
derive_requested_execpolicy_amendment(Some(&prefix_rule), &matched_rules)
);
}
#[tokio::test]
async fn dangerous_rm_rf_requires_approval_in_danger_full_access() {
let command = vec_str(&["rm", "-rf", "/tmp/nonexistent"]);

View File

@@ -365,6 +365,13 @@ fn should_show_model_migration_prompt(
return false;
}
if !available_models
.iter()
.any(|preset| preset.model == target_model && preset.show_in_picker)
{
return false;
}
if available_models
.iter()
.any(|preset| preset.model == current_model && preset.upgrade.is_some())
@@ -401,7 +408,7 @@ fn target_preset_for_upgrade<'a>(
) -> Option<&'a ModelPreset> {
available_models
.iter()
.find(|preset| preset.model == target_model)
.find(|preset| preset.model == target_model && preset.show_in_picker)
}
async fn handle_model_migration_prompt_if_needed(
@@ -3039,25 +3046,25 @@ mod tests {
let seen = BTreeMap::new();
assert!(should_show_model_migration_prompt(
"gpt-5",
"gpt-5.1",
"gpt-5.2-codex",
&seen,
&all_model_presets()
));
assert!(should_show_model_migration_prompt(
"gpt-5-codex",
"gpt-5.1-codex",
"gpt-5.2-codex",
&seen,
&all_model_presets()
));
assert!(should_show_model_migration_prompt(
"gpt-5-codex-mini",
"gpt-5.1-codex-mini",
"gpt-5.2-codex",
&seen,
&all_model_presets()
));
assert!(should_show_model_migration_prompt(
"gpt-5.1-codex",
"gpt-5.1-codex-max",
"gpt-5.2-codex",
&seen,
&all_model_presets()
));
@@ -3088,7 +3095,7 @@ mod tests {
}
#[tokio::test]
async fn model_migration_prompt_skips_when_target_missing() {
async fn model_migration_prompt_skips_when_target_missing_or_hidden() {
let mut available = all_model_presets();
let mut current = available
.iter()
@@ -3106,7 +3113,7 @@ mod tests {
available.retain(|preset| preset.model != "gpt-5-codex");
available.push(current.clone());
assert!(should_show_model_migration_prompt(
assert!(!should_show_model_migration_prompt(
&current.model,
"missing-target",
&BTreeMap::new(),
@@ -3114,6 +3121,21 @@ mod tests {
));
assert!(target_preset_for_upgrade(&available, "missing-target").is_none());
let mut with_hidden_target = all_model_presets();
let target = with_hidden_target
.iter_mut()
.find(|preset| preset.model == "gpt-5.2-codex")
.expect("target preset present");
target.show_in_picker = false;
assert!(!should_show_model_migration_prompt(
"gpt-5-codex",
"gpt-5.2-codex",
&BTreeMap::new(),
&with_hidden_target,
));
assert!(target_preset_for_upgrade(&with_hidden_target, "gpt-5.2-codex").is_none());
}
#[tokio::test]

View File

@@ -8,10 +8,9 @@ const ANNOUNCEMENT_TIP_URL: &str =
const IS_MACOS: bool = cfg!(target_os = "macos");
const PAID_TOOLTIP: &str = "*New* Try the **Codex App** with 2x rate limits until *April 2nd*. Run 'codex app' or visit https://chatgpt.com/codex";
const PAID_TOOLTIP: &str = "*New* Try the **Codex App** with 2x rate limits until *April 2nd*. Run 'codex app' or visit https://chatgpt.com/codex?app-landing-page=true";
const PAID_TOOLTIP_NON_MAC: &str = "*New* 2x rate limits until *April 2nd*.";
const OTHER_TOOLTIP: &str =
"*New* Build faster with the **Codex App**. Run 'codex app' or visit https://chatgpt.com/codex";
const OTHER_TOOLTIP: &str = "*New* Build faster with the **Codex App**. Run 'codex app' or visit https://chatgpt.com/codex?app-landing-page=true";
const OTHER_TOOLTIP_NON_MAC: &str = "*New* Build faster with Codex.";
const FREE_GO_TOOLTIP: &str =
"*New* Codex is included in your plan for free through *March 2nd* lets build together.";