mirror of
https://github.com/openai/codex.git
synced 2026-02-01 22:47:52 +00:00
Fix remote fork cwd + enforce shared ownership
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
use std::ffi::OsStr;
|
||||
use std::ffi::OsString;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
@@ -201,6 +204,7 @@ pub async fn download_rollout_if_available(
|
||||
) -> anyhow::Result<Option<PathBuf>> {
|
||||
let store = SessionObjectStore::new(base_url).await?;
|
||||
let key = object_key(session_id);
|
||||
let meta_key = meta_key(session_id);
|
||||
let Some(data) = store.get_object_bytes(&key).await? else {
|
||||
return Ok(None);
|
||||
};
|
||||
@@ -214,6 +218,21 @@ pub async fn download_rollout_if_available(
|
||||
tokio::fs::write(&path, data)
|
||||
.await
|
||||
.with_context(|| format!("failed to write rollout file {}", path.display()))?;
|
||||
let meta_path = share_meta_path_for_rollout_path(&path);
|
||||
match fetch_meta(&store, &meta_key).await? {
|
||||
Some(meta) => {
|
||||
let payload =
|
||||
serde_json::to_vec(&meta).with_context(|| "failed to serialize metadata")?;
|
||||
tokio::fs::write(&meta_path, payload)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("failed to write share metadata {}", meta_path.display())
|
||||
})?;
|
||||
}
|
||||
None => {
|
||||
let _ = tokio::fs::remove_file(&meta_path).await;
|
||||
}
|
||||
}
|
||||
Ok(Some(path))
|
||||
}
|
||||
|
||||
@@ -225,6 +244,21 @@ fn meta_key(id: ThreadId) -> String {
|
||||
format!("{SHARE_OBJECT_PREFIX}/{id}{SHARE_META_SUFFIX}")
|
||||
}
|
||||
|
||||
pub fn local_share_owner(rollout_path: &Path) -> anyhow::Result<Option<String>> {
|
||||
let meta_path = share_meta_path_for_rollout_path(rollout_path);
|
||||
let bytes = match std::fs::read(&meta_path) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(None),
|
||||
Err(err) => {
|
||||
return Err(err)
|
||||
.with_context(|| format!("failed to read share metadata {}", meta_path.display()));
|
||||
}
|
||||
};
|
||||
let meta: SessionShareMeta =
|
||||
serde_json::from_slice(&bytes).with_context(|| "failed to parse session share metadata")?;
|
||||
Ok(Some(meta.owner))
|
||||
}
|
||||
|
||||
async fn fetch_meta(
|
||||
store: &SessionObjectStore,
|
||||
key: &str,
|
||||
@@ -264,6 +298,13 @@ fn build_rollout_download_path(codex_home: &Path, session_id: ThreadId) -> anyho
|
||||
Ok(dir.join(filename))
|
||||
}
|
||||
|
||||
fn share_meta_path_for_rollout_path(path: &Path) -> PathBuf {
|
||||
let file_name = path.file_name().unwrap_or_else(|| OsStr::new("session"));
|
||||
let mut name = OsString::from(file_name);
|
||||
name.push(".share-meta.json");
|
||||
path.with_file_name(name)
|
||||
}
|
||||
|
||||
impl HttpObjectStore {
|
||||
fn object_url(&self, key: &str) -> anyhow::Result<Url> {
|
||||
let mut base = self.base_url.clone();
|
||||
|
||||
@@ -30,6 +30,7 @@ use codex_core::path_utils;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::read_session_meta_line;
|
||||
use codex_core::session_share::download_rollout_if_available;
|
||||
use codex_core::session_share::local_share_owner;
|
||||
use codex_core::terminal::Multiplexer;
|
||||
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
|
||||
use codex_protocol::ThreadId;
|
||||
@@ -621,6 +622,36 @@ async fn run_ratatui_app(
|
||||
resume_picker::SessionSelection::StartFresh
|
||||
};
|
||||
|
||||
if let resume_picker::SessionSelection::Resume(path) = &session_selection {
|
||||
match local_share_owner(path) {
|
||||
Ok(Some(owner)) => {
|
||||
let current_owner = auth_manager
|
||||
.auth_cached()
|
||||
.and_then(|auth| auth.get_account_email());
|
||||
if current_owner.as_deref() != Some(owner.as_str()) {
|
||||
let session_id = read_session_meta_line(path)
|
||||
.await
|
||||
.ok()
|
||||
.map(|meta| meta.meta.id.to_string());
|
||||
let (id_display, fork_hint) = match session_id {
|
||||
Some(id) => (id.clone(), format!("Use `codex fork {id}` instead.")),
|
||||
None => (
|
||||
"this session".to_string(),
|
||||
"Use `codex fork` to select it instead.".to_string(),
|
||||
),
|
||||
};
|
||||
return fatal_exit(format!(
|
||||
"Cannot resume shared session {id_display} owned by {owner}. {fork_hint}"
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(err) => {
|
||||
return fatal_exit(format!("Failed to read shared session metadata: {err}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let current_cwd = config.cwd.clone();
|
||||
let allow_prompt = cli.cwd.is_none();
|
||||
let action_and_path_if_resume_or_fork = match &session_selection {
|
||||
@@ -755,6 +786,9 @@ pub(crate) async fn resolve_cwd_for_resume_or_fork(
|
||||
let Some(history_cwd) = read_session_cwd(path).await else {
|
||||
return Ok(None);
|
||||
};
|
||||
if action == CwdPromptAction::Fork && !history_cwd.exists() {
|
||||
return Ok(Some(current_cwd.to_path_buf()));
|
||||
}
|
||||
if allow_prompt && cwds_differ(current_cwd, &history_cwd) {
|
||||
let selection =
|
||||
cwd_prompt::run_cwd_selection_prompt(tui, action, current_cwd, &history_cwd).await?;
|
||||
|
||||
Reference in New Issue
Block a user