chore: enable clippy::redundant_clone (#3489)
Created this PR by: - adding `redundant_clone` to `[workspace.lints.clippy]` in `cargo-rs/Cargol.toml` - running `cargo clippy --tests --fix` - running `just fmt` Though I had to clean up one instance of the following that resulted: ```rust let codex = codex; ```
This commit is contained in:
@@ -34,6 +34,7 @@ rust = {}
|
|||||||
|
|
||||||
[workspace.lints.clippy]
|
[workspace.lints.clippy]
|
||||||
expect_used = "deny"
|
expect_used = "deny"
|
||||||
|
redundant_clone = "deny"
|
||||||
uninlined_format_args = "deny"
|
uninlined_format_args = "deny"
|
||||||
unwrap_used = "deny"
|
unwrap_used = "deny"
|
||||||
|
|
||||||
|
|||||||
@@ -617,7 +617,7 @@ fn test_parse_patch_lenient() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_patch_text(&patch_text_in_double_quoted_heredoc, ParseMode::Lenient),
|
parse_patch_text(&patch_text_in_double_quoted_heredoc, ParseMode::Lenient),
|
||||||
Ok(ApplyPatchArgs {
|
Ok(ApplyPatchArgs {
|
||||||
hunks: expected_patch.clone(),
|
hunks: expected_patch,
|
||||||
patch: patch_text.to_string(),
|
patch: patch_text.to_string(),
|
||||||
workdir: None,
|
workdir: None,
|
||||||
})
|
})
|
||||||
@@ -637,7 +637,7 @@ fn test_parse_patch_lenient() {
|
|||||||
"<<EOF\n*** Begin Patch\n*** Update File: file2.py\nEOF\n".to_string();
|
"<<EOF\n*** Begin Patch\n*** Update File: file2.py\nEOF\n".to_string();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_patch_text(&patch_text_with_missing_closing_heredoc, ParseMode::Strict),
|
parse_patch_text(&patch_text_with_missing_closing_heredoc, ParseMode::Strict),
|
||||||
Err(expected_error.clone())
|
Err(expected_error)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_patch_text(&patch_text_with_missing_closing_heredoc, ParseMode::Lenient),
|
parse_patch_text(&patch_text_with_missing_closing_heredoc, ParseMode::Lenient),
|
||||||
|
|||||||
@@ -131,8 +131,7 @@ impl CodexAuth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_account_id(&self) -> Option<String> {
|
pub fn get_account_id(&self) -> Option<String> {
|
||||||
self.get_current_token_data()
|
self.get_current_token_data().and_then(|t| t.account_id)
|
||||||
.and_then(|t| t.account_id.clone())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_plan_type(&self) -> Option<String> {
|
pub fn get_plan_type(&self) -> Option<String> {
|
||||||
@@ -146,7 +145,7 @@ impl CodexAuth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_current_token_data(&self) -> Option<TokenData> {
|
fn get_current_token_data(&self) -> Option<TokenData> {
|
||||||
self.get_current_auth_json().and_then(|t| t.tokens.clone())
|
self.get_current_auth_json().and_then(|t| t.tokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consider this private to integration tests.
|
/// Consider this private to integration tests.
|
||||||
@@ -291,10 +290,10 @@ async fn update_tokens(
|
|||||||
let tokens = auth_dot_json.tokens.get_or_insert_with(TokenData::default);
|
let tokens = auth_dot_json.tokens.get_or_insert_with(TokenData::default);
|
||||||
tokens.id_token = parse_id_token(&id_token).map_err(std::io::Error::other)?;
|
tokens.id_token = parse_id_token(&id_token).map_err(std::io::Error::other)?;
|
||||||
if let Some(access_token) = access_token {
|
if let Some(access_token) = access_token {
|
||||||
tokens.access_token = access_token.to_string();
|
tokens.access_token = access_token;
|
||||||
}
|
}
|
||||||
if let Some(refresh_token) = refresh_token {
|
if let Some(refresh_token) = refresh_token {
|
||||||
tokens.refresh_token = refresh_token.to_string();
|
tokens.refresh_token = refresh_token;
|
||||||
}
|
}
|
||||||
auth_dot_json.last_refresh = Some(Utc::now());
|
auth_dot_json.last_refresh = Some(Utc::now());
|
||||||
write_auth_json(auth_file, &auth_dot_json)?;
|
write_auth_json(auth_file, &auth_dot_json)?;
|
||||||
|
|||||||
@@ -214,12 +214,7 @@ impl Codex {
|
|||||||
let conversation_id = session.conversation_id;
|
let conversation_id = session.conversation_id;
|
||||||
|
|
||||||
// This task will run until Op::Shutdown is received.
|
// This task will run until Op::Shutdown is received.
|
||||||
tokio::spawn(submission_loop(
|
tokio::spawn(submission_loop(session, turn_context, config, rx_sub));
|
||||||
session.clone(),
|
|
||||||
turn_context,
|
|
||||||
config,
|
|
||||||
rx_sub,
|
|
||||||
));
|
|
||||||
let codex = Codex {
|
let codex = Codex {
|
||||||
next_id: AtomicU64::new(0),
|
next_id: AtomicU64::new(0),
|
||||||
tx_sub,
|
tx_sub,
|
||||||
@@ -1074,7 +1069,7 @@ impl AgentTask {
|
|||||||
id: self.sub_id,
|
id: self.sub_id,
|
||||||
msg: EventMsg::TurnAborted(TurnAbortedEvent { reason }),
|
msg: EventMsg::TurnAborted(TurnAbortedEvent { reason }),
|
||||||
};
|
};
|
||||||
let sess = self.sess.clone();
|
let sess = self.sess;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
sess.send_event(event).await;
|
sess.send_event(event).await;
|
||||||
});
|
});
|
||||||
@@ -1772,7 +1767,7 @@ async fn try_run_turn(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(|call_id| ResponseItem::CustomToolCallOutput {
|
.map(|call_id| ResponseItem::CustomToolCallOutput {
|
||||||
call_id: call_id.clone(),
|
call_id,
|
||||||
output: "aborted".to_string(),
|
output: "aborted".to_string(),
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
@@ -1792,7 +1787,7 @@ async fn try_run_turn(
|
|||||||
cwd: turn_context.cwd.clone(),
|
cwd: turn_context.cwd.clone(),
|
||||||
approval_policy: turn_context.approval_policy,
|
approval_policy: turn_context.approval_policy,
|
||||||
sandbox_policy: turn_context.sandbox_policy.clone(),
|
sandbox_policy: turn_context.sandbox_policy.clone(),
|
||||||
model: turn_context.client.get_model().clone(),
|
model: turn_context.client.get_model(),
|
||||||
effort: turn_context.client.get_reasoning_effort(),
|
effort: turn_context.client.get_reasoning_effort(),
|
||||||
summary: turn_context.client.get_reasoning_summary(),
|
summary: turn_context.client.get_reasoning_summary(),
|
||||||
});
|
});
|
||||||
@@ -3154,7 +3149,7 @@ mod tests {
|
|||||||
exit_code: 0,
|
exit_code: 0,
|
||||||
stdout: StreamOutput::new(String::new()),
|
stdout: StreamOutput::new(String::new()),
|
||||||
stderr: StreamOutput::new(String::new()),
|
stderr: StreamOutput::new(String::new()),
|
||||||
aggregated_output: StreamOutput::new(full.clone()),
|
aggregated_output: StreamOutput::new(full),
|
||||||
duration: StdDuration::from_secs(1),
|
duration: StdDuration::from_secs(1),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -3188,7 +3183,7 @@ mod tests {
|
|||||||
fn model_truncation_respects_byte_budget() {
|
fn model_truncation_respects_byte_budget() {
|
||||||
// Construct a large output (about 100kB) so byte budget dominates
|
// Construct a large output (about 100kB) so byte budget dominates
|
||||||
let big_line = "x".repeat(100);
|
let big_line = "x".repeat(100);
|
||||||
let full = std::iter::repeat_n(big_line.clone(), 1000)
|
let full = std::iter::repeat_n(big_line, 1000)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ impl EnvironmentContext {
|
|||||||
if writable_roots.is_empty() {
|
if writable_roots.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(writable_roots.clone())
|
Some(writable_roots)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ mod tests {
|
|||||||
EventMsg::UserMessage(user) => {
|
EventMsg::UserMessage(user) => {
|
||||||
assert_eq!(user.message, "Hello world");
|
assert_eq!(user.message, "Hello world");
|
||||||
assert!(matches!(user.kind, Some(InputMessageKind::Plain)));
|
assert!(matches!(user.kind, Some(InputMessageKind::Plain)));
|
||||||
assert_eq!(user.images, Some(vec![img1.clone(), img2.clone()]));
|
assert_eq!(user.images, Some(vec![img1, img2]));
|
||||||
}
|
}
|
||||||
other => panic!("expected UserMessage, got {other:?}"),
|
other => panic!("expected UserMessage, got {other:?}"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -802,7 +802,7 @@ mod tests {
|
|||||||
async fn resolve_root_git_project_for_trust_regular_repo_returns_repo_root() {
|
async fn resolve_root_git_project_for_trust_regular_repo_returns_repo_root() {
|
||||||
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
||||||
let repo_path = create_test_git_repo(&temp_dir).await;
|
let repo_path = create_test_git_repo(&temp_dir).await;
|
||||||
let expected = std::fs::canonicalize(&repo_path).unwrap().to_path_buf();
|
let expected = std::fs::canonicalize(&repo_path).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resolve_root_git_project_for_trust(&repo_path),
|
resolve_root_git_project_for_trust(&repo_path),
|
||||||
@@ -810,10 +810,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
let nested = repo_path.join("sub/dir");
|
let nested = repo_path.join("sub/dir");
|
||||||
std::fs::create_dir_all(&nested).unwrap();
|
std::fs::create_dir_all(&nested).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(resolve_root_git_project_for_trust(&nested), Some(expected));
|
||||||
resolve_root_git_project_for_trust(&nested),
|
|
||||||
Some(expected.clone())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
@@ -868,7 +868,7 @@ pub fn parse_command_impl(command: &[String]) -> Vec<ParsedCommand> {
|
|||||||
let parts = if contains_connectors(&normalized) {
|
let parts = if contains_connectors(&normalized) {
|
||||||
split_on_connectors(&normalized)
|
split_on_connectors(&normalized)
|
||||||
} else {
|
} else {
|
||||||
vec![normalized.clone()]
|
vec![normalized]
|
||||||
};
|
};
|
||||||
|
|
||||||
// Preserve left-to-right execution order for all commands, including bash -c/-lc
|
// Preserve left-to-right execution order for all commands, including bash -c/-lc
|
||||||
@@ -1201,10 +1201,7 @@ fn parse_bash_lc_commands(original: &[String]) -> Option<Vec<ParsedCommand>> {
|
|||||||
name,
|
name,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand::Read {
|
ParsedCommand::Read { cmd, name }
|
||||||
cmd: cmd.clone(),
|
|
||||||
name,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand::Read {
|
ParsedCommand::Read {
|
||||||
@@ -1215,10 +1212,7 @@ fn parse_bash_lc_commands(original: &[String]) -> Option<Vec<ParsedCommand>> {
|
|||||||
}
|
}
|
||||||
ParsedCommand::ListFiles { path, cmd, .. } => {
|
ParsedCommand::ListFiles { path, cmd, .. } => {
|
||||||
if had_connectors {
|
if had_connectors {
|
||||||
ParsedCommand::ListFiles {
|
ParsedCommand::ListFiles { cmd, path }
|
||||||
cmd: cmd.clone(),
|
|
||||||
path,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand::ListFiles {
|
ParsedCommand::ListFiles {
|
||||||
cmd: shlex_join(&script_tokens),
|
cmd: shlex_join(&script_tokens),
|
||||||
@@ -1230,11 +1224,7 @@ fn parse_bash_lc_commands(original: &[String]) -> Option<Vec<ParsedCommand>> {
|
|||||||
query, path, cmd, ..
|
query, path, cmd, ..
|
||||||
} => {
|
} => {
|
||||||
if had_connectors {
|
if had_connectors {
|
||||||
ParsedCommand::Search {
|
ParsedCommand::Search { cmd, query, path }
|
||||||
cmd: cmd.clone(),
|
|
||||||
query,
|
|
||||||
path,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand::Search {
|
ParsedCommand::Search {
|
||||||
cmd: shlex_join(&script_tokens),
|
cmd: shlex_join(&script_tokens),
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ pub fn discover_project_doc_paths(config: &Config) -> std::io::Result<Vec<PathBu
|
|||||||
// Build chain from cwd upwards and detect git root.
|
// Build chain from cwd upwards and detect git root.
|
||||||
let mut chain: Vec<PathBuf> = vec![dir.clone()];
|
let mut chain: Vec<PathBuf> = vec![dir.clone()];
|
||||||
let mut git_root: Option<PathBuf> = None;
|
let mut git_root: Option<PathBuf> = None;
|
||||||
let mut cursor = dir.clone();
|
let mut cursor = dir;
|
||||||
while let Some(parent) = cursor.parent() {
|
while let Some(parent) = cursor.parent() {
|
||||||
let git_marker = cursor.join(".git");
|
let git_marker = cursor.join(".git");
|
||||||
let git_exists = match std::fs::metadata(&git_marker) {
|
let git_exists = match std::fs::metadata(&git_marker) {
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ async fn test_pagination_cursor() {
|
|||||||
path: p1,
|
path: p1,
|
||||||
head: head_1,
|
head: head_1,
|
||||||
}],
|
}],
|
||||||
next_cursor: Some(expected_cursor3.clone()),
|
next_cursor: Some(expected_cursor3),
|
||||||
num_scanned_files: 5, // scanned 05, 04 (anchor), 03, 02 (anchor), 01
|
num_scanned_files: 5, // scanned 05, 04 (anchor), 03, 02 (anchor), 01
|
||||||
reached_scan_cap: false,
|
reached_scan_cap: false,
|
||||||
};
|
};
|
||||||
@@ -344,7 +344,7 @@ async fn test_get_conversation_contents() {
|
|||||||
let expected_cursor: Cursor = serde_json::from_str(&format!("\"{ts}|{uuid}\"")).unwrap();
|
let expected_cursor: Cursor = serde_json::from_str(&format!("\"{ts}|{uuid}\"")).unwrap();
|
||||||
let expected_page = ConversationsPage {
|
let expected_page = ConversationsPage {
|
||||||
items: vec![ConversationItem {
|
items: vec![ConversationItem {
|
||||||
path: expected_path.clone(),
|
path: expected_path,
|
||||||
head: expected_head,
|
head: expected_head,
|
||||||
}],
|
}],
|
||||||
next_cursor: Some(expected_cursor),
|
next_cursor: Some(expected_cursor),
|
||||||
@@ -437,7 +437,7 @@ async fn test_stable_ordering_same_second_pagination() {
|
|||||||
path: p1,
|
path: p1,
|
||||||
head: head(u1),
|
head: head(u1),
|
||||||
}],
|
}],
|
||||||
next_cursor: Some(expected_cursor2.clone()),
|
next_cursor: Some(expected_cursor2),
|
||||||
num_scanned_files: 3, // scanned u3, u2 (anchor), u1
|
num_scanned_files: 3, // scanned u3, u2 (anchor), u1
|
||||||
reached_scan_cap: false,
|
reached_scan_cap: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ mod tests {
|
|||||||
// With the parent dir explicitly added as a writable root, the
|
// With the parent dir explicitly added as a writable root, the
|
||||||
// outside write should be permitted.
|
// outside write should be permitted.
|
||||||
let policy_with_parent = SandboxPolicy::WorkspaceWrite {
|
let policy_with_parent = SandboxPolicy::WorkspaceWrite {
|
||||||
writable_roots: vec![parent.clone()],
|
writable_roots: vec![parent],
|
||||||
network_access: false,
|
network_access: false,
|
||||||
exclude_tmpdir_env_var: true,
|
exclude_tmpdir_env_var: true,
|
||||||
exclude_slash_tmp: true,
|
exclude_slash_tmp: true,
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ mod tests {
|
|||||||
// Build a policy that only includes the two test roots as writable and
|
// Build a policy that only includes the two test roots as writable and
|
||||||
// does not automatically include defaults TMPDIR or /tmp.
|
// does not automatically include defaults TMPDIR or /tmp.
|
||||||
let policy = SandboxPolicy::WorkspaceWrite {
|
let policy = SandboxPolicy::WorkspaceWrite {
|
||||||
writable_roots: vec![root_with_git.clone(), root_without_git.clone()],
|
writable_roots: vec![root_with_git, root_without_git],
|
||||||
network_access: false,
|
network_access: false,
|
||||||
exclude_tmpdir_env_var: true,
|
exclude_tmpdir_env_var: true,
|
||||||
exclude_slash_tmp: true,
|
exclude_slash_tmp: true,
|
||||||
|
|||||||
@@ -326,10 +326,7 @@ mod tests {
|
|||||||
.format_default_shell_invocation(input.iter().map(|s| s.to_string()).collect());
|
.format_default_shell_invocation(input.iter().map(|s| s.to_string()).collect());
|
||||||
let expected_cmd = expected_cmd
|
let expected_cmd = expected_cmd
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| {
|
.map(|s| s.replace("BASHRC_PATH", bashrc_path.to_str().unwrap()))
|
||||||
s.replace("BASHRC_PATH", bashrc_path.to_str().unwrap())
|
|
||||||
.to_string()
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
assert_eq!(actual_cmd, Some(expected_cmd));
|
assert_eq!(actual_cmd, Some(expected_cmd));
|
||||||
@@ -435,10 +432,7 @@ mod macos_tests {
|
|||||||
.format_default_shell_invocation(input.iter().map(|s| s.to_string()).collect());
|
.format_default_shell_invocation(input.iter().map(|s| s.to_string()).collect());
|
||||||
let expected_cmd = expected_cmd
|
let expected_cmd = expected_cmd
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| {
|
.map(|s| s.replace("ZSHRC_PATH", zshrc_path.to_str().unwrap()))
|
||||||
s.replace("ZSHRC_PATH", zshrc_path.to_str().unwrap())
|
|
||||||
.to_string()
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
assert_eq!(actual_cmd, Some(expected_cmd));
|
assert_eq!(actual_cmd, Some(expected_cmd));
|
||||||
|
|||||||
@@ -678,7 +678,7 @@ index {left_oid}..{right_oid}
|
|||||||
let dest = dir.path().join("dest.txt");
|
let dest = dir.path().join("dest.txt");
|
||||||
let mut acc = TurnDiffTracker::new();
|
let mut acc = TurnDiffTracker::new();
|
||||||
let mv = HashMap::from([(
|
let mv = HashMap::from([(
|
||||||
src.clone(),
|
src,
|
||||||
FileChange::Update {
|
FileChange::Update {
|
||||||
unified_diff: "".into(),
|
unified_diff: "".into(),
|
||||||
move_path: Some(dest.clone()),
|
move_path: Some(dest.clone()),
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ impl EventProcessor for EventProcessorWithHumanOutput {
|
|||||||
parsed_cmd: _,
|
parsed_cmd: _,
|
||||||
}) => {
|
}) => {
|
||||||
self.call_id_to_command.insert(
|
self.call_id_to_command.insert(
|
||||||
call_id.clone(),
|
call_id,
|
||||||
ExecCommandBegin {
|
ExecCommandBegin {
|
||||||
command: command.clone(),
|
command: command.clone(),
|
||||||
},
|
},
|
||||||
@@ -382,7 +382,7 @@ impl EventProcessor for EventProcessorWithHumanOutput {
|
|||||||
// Store metadata so we can calculate duration later when we
|
// Store metadata so we can calculate duration later when we
|
||||||
// receive the corresponding PatchApplyEnd event.
|
// receive the corresponding PatchApplyEnd event.
|
||||||
self.call_id_to_patch.insert(
|
self.call_id_to_patch.insert(
|
||||||
call_id.clone(),
|
call_id,
|
||||||
PatchApplyBegin {
|
PatchApplyBegin {
|
||||||
start_time: Instant::now(),
|
start_time: Instant::now(),
|
||||||
auto_approved,
|
auto_approved,
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ pub(crate) async fn run_e2e_exec_test(cwd: &Path, response_streams: Vec<String>)
|
|||||||
.context("should find binary for codex-exec")
|
.context("should find binary for codex-exec")
|
||||||
.expect("should find binary for codex-exec")
|
.expect("should find binary for codex-exec")
|
||||||
.current_dir(cwd.clone())
|
.current_dir(cwd.clone())
|
||||||
.env("CODEX_HOME", cwd.clone())
|
.env("CODEX_HOME", cwd)
|
||||||
.env("OPENAI_API_KEY", "dummy")
|
.env("OPENAI_API_KEY", "dummy")
|
||||||
.env("OPENAI_BASE_URL", format!("{uri}/v1"))
|
.env("OPENAI_BASE_URL", format!("{uri}/v1"))
|
||||||
.arg("--skip-git-repo-check")
|
.arg("--skip-git-repo-check")
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ impl ExecvChecker {
|
|||||||
let mut program = valid_exec.program.to_string();
|
let mut program = valid_exec.program.to_string();
|
||||||
for system_path in valid_exec.system_path {
|
for system_path in valid_exec.system_path {
|
||||||
if is_executable_file(&system_path) {
|
if is_executable_file(&system_path) {
|
||||||
program = system_path.to_string();
|
program = system_path;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,7 @@ system_path=[{fake_cp:?}]
|
|||||||
let checker = setup(&fake_cp);
|
let checker = setup(&fake_cp);
|
||||||
let exec_call = ExecCall {
|
let exec_call = ExecCall {
|
||||||
program: "cp".into(),
|
program: "cp".into(),
|
||||||
args: vec![source.clone(), dest.clone()],
|
args: vec![source, dest.clone()],
|
||||||
};
|
};
|
||||||
let valid_exec = match checker.r#match(&exec_call)? {
|
let valid_exec = match checker.r#match(&exec_call)? {
|
||||||
MatchedExec::Match { exec } => exec,
|
MatchedExec::Match { exec } => exec,
|
||||||
@@ -207,7 +207,7 @@ system_path=[{fake_cp:?}]
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
checker.check(valid_exec.clone(), &cwd, &[], &[]),
|
checker.check(valid_exec.clone(), &cwd, &[], &[]),
|
||||||
Err(ReadablePathNotInReadableFolders {
|
Err(ReadablePathNotInReadableFolders {
|
||||||
file: source_path.clone(),
|
file: source_path,
|
||||||
folders: vec![]
|
folders: vec![]
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -229,7 +229,7 @@ system_path=[{fake_cp:?}]
|
|||||||
// Both readable and writeable folders specified.
|
// Both readable and writeable folders specified.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
checker.check(
|
checker.check(
|
||||||
valid_exec.clone(),
|
valid_exec,
|
||||||
&cwd,
|
&cwd,
|
||||||
std::slice::from_ref(&root_path),
|
std::slice::from_ref(&root_path),
|
||||||
std::slice::from_ref(&root_path)
|
std::slice::from_ref(&root_path)
|
||||||
@@ -241,7 +241,7 @@ system_path=[{fake_cp:?}]
|
|||||||
// folders.
|
// folders.
|
||||||
let exec_call_folders_as_args = ExecCall {
|
let exec_call_folders_as_args = ExecCall {
|
||||||
program: "cp".into(),
|
program: "cp".into(),
|
||||||
args: vec![root.clone(), root.clone()],
|
args: vec![root.clone(), root],
|
||||||
};
|
};
|
||||||
let valid_exec_call_folders_as_args = match checker.r#match(&exec_call_folders_as_args)? {
|
let valid_exec_call_folders_as_args = match checker.r#match(&exec_call_folders_as_args)? {
|
||||||
MatchedExec::Match { exec } => exec,
|
MatchedExec::Match { exec } => exec,
|
||||||
@@ -254,7 +254,7 @@ system_path=[{fake_cp:?}]
|
|||||||
std::slice::from_ref(&root_path),
|
std::slice::from_ref(&root_path),
|
||||||
std::slice::from_ref(&root_path)
|
std::slice::from_ref(&root_path)
|
||||||
),
|
),
|
||||||
Ok(cp.clone()),
|
Ok(cp),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Specify a parent of a readable folder as input.
|
// Specify a parent of a readable folder as input.
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ impl PolicyBuilder {
|
|||||||
info!("adding program spec: {program_spec:?}");
|
info!("adding program spec: {program_spec:?}");
|
||||||
let name = program_spec.program.clone();
|
let name = program_spec.program.clone();
|
||||||
let mut programs = self.programs.borrow_mut();
|
let mut programs = self.programs.borrow_mut();
|
||||||
programs.insert(name.clone(), program_spec);
|
programs.insert(name, program_spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_forbidden_substrings(&self, substrings: &[String]) {
|
fn add_forbidden_substrings(&self, substrings: &[String]) {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ impl ServerOptions {
|
|||||||
pub fn new(codex_home: PathBuf, client_id: String) -> Self {
|
pub fn new(codex_home: PathBuf, client_id: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
codex_home,
|
codex_home,
|
||||||
client_id: client_id.to_string(),
|
client_id,
|
||||||
issuer: DEFAULT_ISSUER.to_string(),
|
issuer: DEFAULT_ISSUER.to_string(),
|
||||||
port: DEFAULT_PORT,
|
port: DEFAULT_PORT,
|
||||||
open_browser: true,
|
open_browser: true,
|
||||||
@@ -126,7 +126,7 @@ pub fn run_login_server(opts: ServerOptions) -> io::Result<LoginServer> {
|
|||||||
let shutdown_notify = Arc::new(tokio::sync::Notify::new());
|
let shutdown_notify = Arc::new(tokio::sync::Notify::new());
|
||||||
let server_handle = {
|
let server_handle = {
|
||||||
let shutdown_notify = shutdown_notify.clone();
|
let shutdown_notify = shutdown_notify.clone();
|
||||||
let server = server.clone();
|
let server = server;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let result = loop {
|
let result = loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ async fn run_codex_tool_session_inner(
|
|||||||
}
|
}
|
||||||
EventMsg::TaskComplete(TaskCompleteEvent { last_agent_message }) => {
|
EventMsg::TaskComplete(TaskCompleteEvent { last_agent_message }) => {
|
||||||
let text = match last_agent_message {
|
let text = match last_agent_message {
|
||||||
Some(msg) => msg.clone(),
|
Some(msg) => msg,
|
||||||
None => "".to_string(),
|
None => "".to_string(),
|
||||||
};
|
};
|
||||||
let result = CallToolResult {
|
let result = CallToolResult {
|
||||||
|
|||||||
@@ -531,7 +531,6 @@ impl MessageProcessor {
|
|||||||
|
|
||||||
// Spawn the long-running reply handler.
|
// Spawn the long-running reply handler.
|
||||||
tokio::spawn({
|
tokio::spawn({
|
||||||
let codex = codex.clone();
|
|
||||||
let outgoing = outgoing.clone();
|
let outgoing = outgoing.clone();
|
||||||
let prompt = prompt.clone();
|
let prompt = prompt.clone();
|
||||||
let running_requests_id_to_codex_uuid = running_requests_id_to_codex_uuid.clone();
|
let running_requests_id_to_codex_uuid = running_requests_id_to_codex_uuid.clone();
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ mod tests {
|
|||||||
let Ok(expected_params) = serde_json::to_value(&event) else {
|
let Ok(expected_params) = serde_json::to_value(&event) else {
|
||||||
panic!("Event must serialize");
|
panic!("Event must serialize");
|
||||||
};
|
};
|
||||||
assert_eq!(params, Some(expected_params.clone()));
|
assert_eq!(params, Some(expected_params));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn token_usage(&self) -> codex_core::protocol::TokenUsage {
|
pub(crate) fn token_usage(&self) -> codex_core::protocol::TokenUsage {
|
||||||
self.chat_widget.token_usage().clone()
|
self.chat_widget.token_usage()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_key_event(&mut self, tui: &mut tui::Tui, key_event: KeyEvent) {
|
async fn handle_key_event(&mut self, tui: &mut tui::Tui, key_event: KeyEvent) {
|
||||||
|
|||||||
@@ -487,7 +487,7 @@ impl ChatComposer {
|
|||||||
} => {
|
} => {
|
||||||
// Hide popup without modifying text, remember token to avoid immediate reopen.
|
// Hide popup without modifying text, remember token to avoid immediate reopen.
|
||||||
if let Some(tok) = Self::current_at_token(&self.textarea) {
|
if let Some(tok) = Self::current_at_token(&self.textarea) {
|
||||||
self.dismissed_file_popup_token = Some(tok.to_string());
|
self.dismissed_file_popup_token = Some(tok);
|
||||||
}
|
}
|
||||||
self.active_popup = ActivePopup::None;
|
self.active_popup = ActivePopup::None;
|
||||||
(InputResult::None, true)
|
(InputResult::None, true)
|
||||||
@@ -546,7 +546,7 @@ impl ChatComposer {
|
|||||||
Some(ext) if ext == "jpg" || ext == "jpeg" => "JPEG",
|
Some(ext) if ext == "jpg" || ext == "jpeg" => "JPEG",
|
||||||
_ => "IMG",
|
_ => "IMG",
|
||||||
};
|
};
|
||||||
self.attach_image(path_buf.clone(), w, h, format_label);
|
self.attach_image(path_buf, w, h, format_label);
|
||||||
// Add a trailing space to keep typing fluid.
|
// Add a trailing space to keep typing fluid.
|
||||||
self.textarea.insert_str(" ");
|
self.textarea.insert_str(" ");
|
||||||
} else {
|
} else {
|
||||||
@@ -2123,7 +2123,7 @@ mod tests {
|
|||||||
|
|
||||||
// Re-add and test backspace in middle: should break the placeholder string
|
// Re-add and test backspace in middle: should break the placeholder string
|
||||||
// and drop the image mapping (same as text placeholder behavior).
|
// and drop the image mapping (same as text placeholder behavior).
|
||||||
composer.attach_image(path.clone(), 20, 10, "PNG");
|
composer.attach_image(path, 20, 10, "PNG");
|
||||||
let placeholder2 = composer.attached_images[0].placeholder.clone();
|
let placeholder2 = composer.attached_images[0].placeholder.clone();
|
||||||
// Move cursor to roughly middle of placeholder
|
// Move cursor to roughly middle of placeholder
|
||||||
if let Some(start_pos) = composer.textarea.text().find(&placeholder2) {
|
if let Some(start_pos) = composer.textarea.text().find(&placeholder2) {
|
||||||
@@ -2182,7 +2182,7 @@ mod tests {
|
|||||||
let path1 = PathBuf::from("/tmp/image_dup1.png");
|
let path1 = PathBuf::from("/tmp/image_dup1.png");
|
||||||
let path2 = PathBuf::from("/tmp/image_dup2.png");
|
let path2 = PathBuf::from("/tmp/image_dup2.png");
|
||||||
|
|
||||||
composer.attach_image(path1.clone(), 10, 5, "PNG");
|
composer.attach_image(path1, 10, 5, "PNG");
|
||||||
// separate placeholders with a space for clarity
|
// separate placeholders with a space for clarity
|
||||||
composer.handle_paste(" ".into());
|
composer.handle_paste(" ".into());
|
||||||
composer.attach_image(path2.clone(), 10, 5, "PNG");
|
composer.attach_image(path2.clone(), 10, 5, "PNG");
|
||||||
@@ -2231,7 +2231,7 @@ mod tests {
|
|||||||
assert!(composer.textarea.text().starts_with("[image 3x2 PNG] "));
|
assert!(composer.textarea.text().starts_with("[image 3x2 PNG] "));
|
||||||
|
|
||||||
let imgs = composer.take_recent_submission_images();
|
let imgs = composer.take_recent_submission_images();
|
||||||
assert_eq!(imgs, vec![tmp_path.clone()]);
|
assert_eq!(imgs, vec![tmp_path]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -564,7 +564,7 @@ mod tests {
|
|||||||
let (tx_raw, rx) = unbounded_channel::<AppEvent>();
|
let (tx_raw, rx) = unbounded_channel::<AppEvent>();
|
||||||
let tx = AppEventSender::new(tx_raw);
|
let tx = AppEventSender::new(tx_raw);
|
||||||
let mut pane = BottomPane::new(BottomPaneParams {
|
let mut pane = BottomPane::new(BottomPaneParams {
|
||||||
app_event_tx: tx.clone(),
|
app_event_tx: tx,
|
||||||
frame_requester: FrameRequester::test_dummy(),
|
frame_requester: FrameRequester::test_dummy(),
|
||||||
has_input_focus: true,
|
has_input_focus: true,
|
||||||
enhanced_keys_supported: false,
|
enhanced_keys_supported: false,
|
||||||
|
|||||||
@@ -649,9 +649,7 @@ impl TextArea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_element(&mut self, range: Range<usize>) {
|
fn add_element(&mut self, range: Range<usize>) {
|
||||||
let elem = TextElement {
|
let elem = TextElement { range };
|
||||||
range: range.clone(),
|
|
||||||
};
|
|
||||||
self.elements.push(elem);
|
self.elements.push(elem);
|
||||||
self.elements.sort_by_key(|e| e.range.start);
|
self.elements.sort_by_key(|e| e.range.start);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -574,14 +574,14 @@ impl ChatWidget {
|
|||||||
self.active_exec_cell = Some(history_cell::new_active_exec_command(
|
self.active_exec_cell = Some(history_cell::new_active_exec_command(
|
||||||
ev.call_id.clone(),
|
ev.call_id.clone(),
|
||||||
ev.command.clone(),
|
ev.command.clone(),
|
||||||
ev.parsed_cmd.clone(),
|
ev.parsed_cmd,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.active_exec_cell = Some(history_cell::new_active_exec_command(
|
self.active_exec_cell = Some(history_cell::new_active_exec_command(
|
||||||
ev.call_id.clone(),
|
ev.call_id.clone(),
|
||||||
ev.command.clone(),
|
ev.command.clone(),
|
||||||
ev.parsed_cmd.clone(),
|
ev.parsed_cmd,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -804,7 +804,7 @@ impl ChatWidget {
|
|||||||
"attach_image path={path:?} width={width} height={height} format={format_label}",
|
"attach_image path={path:?} width={width} height={height} format={format_label}",
|
||||||
);
|
);
|
||||||
self.bottom_pane
|
self.bottom_pane
|
||||||
.attach_image(path.clone(), width, height, format_label);
|
.attach_image(path, width, height, format_label);
|
||||||
self.request_redraw();
|
self.request_redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -986,7 +986,7 @@ impl ChatWidget {
|
|||||||
|
|
||||||
// Only show the text portion in conversation history.
|
// Only show the text portion in conversation history.
|
||||||
if !text.is_empty() {
|
if !text.is_empty() {
|
||||||
self.add_to_history(history_cell::new_user_prompt(text.clone()));
|
self.add_to_history(history_cell::new_user_prompt(text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1055,10 +1055,10 @@ impl ChatWidget {
|
|||||||
EventMsg::PlanUpdate(update) => self.on_plan_update(update),
|
EventMsg::PlanUpdate(update) => self.on_plan_update(update),
|
||||||
EventMsg::ExecApprovalRequest(ev) => {
|
EventMsg::ExecApprovalRequest(ev) => {
|
||||||
// For replayed events, synthesize an empty id (these should not occur).
|
// For replayed events, synthesize an empty id (these should not occur).
|
||||||
self.on_exec_approval_request(id.clone().unwrap_or_default(), ev)
|
self.on_exec_approval_request(id.unwrap_or_default(), ev)
|
||||||
}
|
}
|
||||||
EventMsg::ApplyPatchApprovalRequest(ev) => {
|
EventMsg::ApplyPatchApprovalRequest(ev) => {
|
||||||
self.on_apply_patch_approval_request(id.clone().unwrap_or_default(), ev)
|
self.on_apply_patch_approval_request(id.unwrap_or_default(), ev)
|
||||||
}
|
}
|
||||||
EventMsg::ExecCommandBegin(ev) => self.on_exec_command_begin(ev),
|
EventMsg::ExecCommandBegin(ev) => self.on_exec_command_begin(ev),
|
||||||
EventMsg::ExecCommandOutputDelta(delta) => self.on_exec_command_output_delta(delta),
|
EventMsg::ExecCommandOutputDelta(delta) => self.on_exec_command_output_delta(delta),
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ pub(crate) fn spawn_agent(
|
|||||||
) -> UnboundedSender<Op> {
|
) -> UnboundedSender<Op> {
|
||||||
let (codex_op_tx, mut codex_op_rx) = unbounded_channel::<Op>();
|
let (codex_op_tx, mut codex_op_rx) = unbounded_channel::<Op>();
|
||||||
|
|
||||||
let app_event_tx_clone = app_event_tx.clone();
|
let app_event_tx_clone = app_event_tx;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let NewConversation {
|
let NewConversation {
|
||||||
conversation_id: _,
|
conversation_id: _,
|
||||||
@@ -71,7 +71,7 @@ pub(crate) fn spawn_agent_from_existing(
|
|||||||
) -> UnboundedSender<Op> {
|
) -> UnboundedSender<Op> {
|
||||||
let (codex_op_tx, mut codex_op_rx) = unbounded_channel::<Op>();
|
let (codex_op_tx, mut codex_op_rx) = unbounded_channel::<Op>();
|
||||||
|
|
||||||
let app_event_tx_clone = app_event_tx.clone();
|
let app_event_tx_clone = app_event_tx;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
// Forward the captured `SessionConfigured` event so it can be rendered in the UI.
|
// Forward the captured `SessionConfigured` event so it can be rendered in the UI.
|
||||||
let ev = codex_core::protocol::Event {
|
let ev = codex_core::protocol::Event {
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ fn exec_approval_decision_truncates_multiline_and_long_commands() {
|
|||||||
let long = format!("echo {}", "a".repeat(200));
|
let long = format!("echo {}", "a".repeat(200));
|
||||||
let ev_long = ExecApprovalRequestEvent {
|
let ev_long = ExecApprovalRequestEvent {
|
||||||
call_id: "call-long".into(),
|
call_id: "call-long".into(),
|
||||||
command: vec!["bash".into(), "-lc".into(), long.clone()],
|
command: vec!["bash".into(), "-lc".into(), long],
|
||||||
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
|
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
|
||||||
reason: None,
|
reason: None,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -737,10 +737,10 @@ mod tests {
|
|||||||
|
|
||||||
let mut changes: HashMap<PathBuf, FileChange> = HashMap::new();
|
let mut changes: HashMap<PathBuf, FileChange> = HashMap::new();
|
||||||
changes.insert(
|
changes.insert(
|
||||||
abs_old.clone(),
|
abs_old,
|
||||||
FileChange::Update {
|
FileChange::Update {
|
||||||
unified_diff: patch,
|
unified_diff: patch,
|
||||||
move_path: Some(abs_new.clone()),
|
move_path: Some(abs_new),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -697,7 +697,7 @@ fn spinner(start_time: Option<Instant>) -> Span<'static> {
|
|||||||
|
|
||||||
pub(crate) fn new_active_mcp_tool_call(invocation: McpInvocation) -> PlainHistoryCell {
|
pub(crate) fn new_active_mcp_tool_call(invocation: McpInvocation) -> PlainHistoryCell {
|
||||||
let title_line = Line::from(vec!["tool".magenta(), " running...".dim()]);
|
let title_line = Line::from(vec!["tool".magenta(), " running...".dim()]);
|
||||||
let lines: Vec<Line> = vec![title_line, format_mcp_invocation(invocation.clone())];
|
let lines: Vec<Line> = vec![title_line, format_mcp_invocation(invocation)];
|
||||||
|
|
||||||
PlainHistoryCell { lines }
|
PlainHistoryCell { lines }
|
||||||
}
|
}
|
||||||
@@ -1324,7 +1324,7 @@ fn format_mcp_invocation<'a>(invocation: McpInvocation) -> Line<'a> {
|
|||||||
let invocation_spans = vec![
|
let invocation_spans = vec![
|
||||||
invocation.server.clone().cyan(),
|
invocation.server.clone().cyan(),
|
||||||
".".into(),
|
".".into(),
|
||||||
invocation.tool.clone().cyan(),
|
invocation.tool.cyan(),
|
||||||
"(".into(),
|
"(".into(),
|
||||||
args_str.dim(),
|
args_str.dim(),
|
||||||
")".into(),
|
")".into(),
|
||||||
|
|||||||
@@ -432,7 +432,7 @@ impl AuthModeWidget {
|
|||||||
match &mut *guard {
|
match &mut *guard {
|
||||||
SignInState::ApiKeyEntry(state) => {
|
SignInState::ApiKeyEntry(state) => {
|
||||||
if state.value.is_empty() {
|
if state.value.is_empty() {
|
||||||
if let Some(prefill) = prefill_from_env.clone() {
|
if let Some(prefill) = prefill_from_env {
|
||||||
state.value = prefill;
|
state.value = prefill;
|
||||||
state.prepopulated_from_env = true;
|
state.prepopulated_from_env = true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ impl OnboardingScreen {
|
|||||||
config,
|
config,
|
||||||
} = args;
|
} = args;
|
||||||
let cwd = config.cwd.clone();
|
let cwd = config.cwd.clone();
|
||||||
let codex_home = config.codex_home.clone();
|
let codex_home = config.codex_home;
|
||||||
let mut steps: Vec<Step> = vec![Step::Welcome(WelcomeWidget {
|
let mut steps: Vec<Step> = vec![Step::Welcome(WelcomeWidget {
|
||||||
is_logged_in: !matches!(login_status, LoginStatus::NotAuthenticated),
|
is_logged_in: !matches!(login_status, LoginStatus::NotAuthenticated),
|
||||||
})];
|
})];
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ impl UserApprovalWidget {
|
|||||||
"You ".into(),
|
"You ".into(),
|
||||||
"approved".bold(),
|
"approved".bold(),
|
||||||
" codex to run ".into(),
|
" codex to run ".into(),
|
||||||
snippet.clone().dim(),
|
snippet.dim(),
|
||||||
" this time".bold(),
|
" this time".bold(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -254,7 +254,7 @@ impl UserApprovalWidget {
|
|||||||
"You ".into(),
|
"You ".into(),
|
||||||
"approved".bold(),
|
"approved".bold(),
|
||||||
" codex to run ".into(),
|
" codex to run ".into(),
|
||||||
snippet.clone().dim(),
|
snippet.dim(),
|
||||||
" every time this session".bold(),
|
" every time this session".bold(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -264,7 +264,7 @@ impl UserApprovalWidget {
|
|||||||
"You ".into(),
|
"You ".into(),
|
||||||
"did not approve".bold(),
|
"did not approve".bold(),
|
||||||
" codex to run ".into(),
|
" codex to run ".into(),
|
||||||
snippet.clone().dim(),
|
snippet.dim(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
ReviewDecision::Abort => {
|
ReviewDecision::Abort => {
|
||||||
@@ -273,7 +273,7 @@ impl UserApprovalWidget {
|
|||||||
"You ".into(),
|
"You ".into(),
|
||||||
"canceled".bold(),
|
"canceled".bold(),
|
||||||
" the request to run ".into(),
|
" the request to run ".into(),
|
||||||
snippet.clone().dim(),
|
snippet.dim(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user