[Bug] Session flagged when processing very simple R (bioinformatics data conversion) script
Open 💬 2 comments Opened Jun 11, 2026 by ndaniel
Fable 5 safety classifier false-positive for bioinformatics
Bug Description
Telling to Fable 5 (Claude Code) to read one simple R script that converts data between different bioinformatic data types, makes it to flag the session and switch to Opus 4.8.
Environment Info
- Platform: linux
- Terminal: gnome-terminal
- Version: 2.1.173
- Feedback ID: 786f037f-4e36-4373-a4eb-9a141f36082b
Command to Claude Code (Fable 5)
read the R sript
Errors
This model has measures that flagged something in this session. This sometimes happens with safe, normal conversations. These measures let us bring you Mythos-level capability in other areas sooner, and we're working to refine them. Switched to
Opus 4.8. Send feedback with /feedback or learn more
R script
#!/usr/bin/env Rscript
# =============================================================================
# 01--export_to_anndata.R (zeus, task T0.1)
#
# Export the latest delivery (data/) to a single AnnData .h5ad for the
# downstream squidpy neighbourhood analysis.
#
# Verified upstream (do not re-derive): rownames(metadata) == colnames(linear)
# == metadata$seurat_cell_id, bit-identical order. So the two files assemble
# directly with NO key-matching.
#
# Output:
# - results01/zeus_cohort.h5ad
# X = linear intensities (cells x 29 features)
# obs = full 92-col metadata + *_clean label columns
# var = 29 features + channel_type (protein / Hoechst)
# obsm['spatial'] = centroids in MICRONS (px * 0.325)
# uns = pixel size, provenance
#
# Notes:
# - Pixel size 0.325 um/px is from Samuel (consistent across all 7 TMA slides).
# - Area / Eccentricity are from Samuel (Centroids email, 3 Jun ); Area units
# unstated -> assumed px^2. Segmentation QC (T0.1b) is left to the squidpy side.
# =============================================================================
suppressMessages({
library(qs2)
library(Matrix)
library(SingleCellExperiment)
library(zellkonverter)
})
PX_UM <- 0.325 # microns per pixel (Samuel)
DATA_DIR <- "data/0604"
OUT_DIR <- "results01"
OUT_H5AD <- file.path(OUT_DIR, "zeus_cohort.h5ad")
META_QS <- file.path(DATA_DIR, "0523_Metadata_with_centroids.qs2")
LINEAR_QS <- file.path(DATA_DIR, "Linear_Intensity_Values_Merged_Cohort.qs2")
dir.create(OUT_DIR, recursive = TRUE, showWarnings = FALSE)
# ----------------------------------------------------------------------------
# 1. Load
# ----------------------------------------------------------------------------
message("Loading metadata: ", META_QS)
meta <- qs_read(META_QS)
stopifnot(is.data.frame(meta))
message("Loading linear intensities: ", LINEAR_QS)
linear <- qs_read(LINEAR_QS) # features x cells (dgCMatrix)
stopifnot(nrow(linear) == 29)
# ----------------------------------------------------------------------------
# 2. Assert alignment (cells in identical order) -- hard fail if not
# ----------------------------------------------------------------------------
if (!identical(rownames(meta), colnames(linear))) {
stop("Cell order mismatch between metadata rownames and linear-intensity colnames; ",
"aborting (the upstream invariant no longer holds).")
}
message("Alignment OK: ", format(nrow(meta), big.mark = ","), " cells, identical order.")
# ----------------------------------------------------------------------------
# 3. Fix upstream TRIPOD typos IN PLACE, + harmonise Stroma labels to ASCII
# Typos are objective spelling errors and are corrected in the actual
# annotation columns so downstream uses clean labels by default. The raw
# (typo'd) labels remain in the source qs2 under data/0604/, so nothing
# is lost and provenance is intact.
# Machrophages -> Macrophages
# Proliferative_cancerous_epthelium -> Proliferative_cancerous_epithelium
# ----------------------------------------------------------------------------
fix_typos <- function(x) {
x <- gsub("Machrophages", "Macrophages", x, fixed = TRUE)
x <- gsub("Proliferative_cancerous_epthelium",
"Proliferative_cancerous_epithelium", x, fixed = TRUE)
x
}
ann_cols <- c("Tumor_Annotation", "Global",
"Immune_Wide_Annotation", "Immune_Deep_Annotation",
"Immune_Active_Annotation", "Immune_Checkpoint_Annotation",
"Stroma_Annotation")
n_fixed <- 0L
for (col in ann_cols) {
if (col %in% names(meta)) {
before <- as.character(meta[[col]])
after <- fix_typos(before)
n_fixed <- n_fixed + sum(before != after, na.rm = TRUE)
meta[[col]] <- after
}
}
message("Typos fixed in place: ", n_fixed, " cell labels across annotation columns.")
# Stroma_Annotation: Greek alpha + literal '+' are NOT typos but break R
# formulas / filenames -> keep the (typo-fixed) original AND add an ASCII copy
# matching Tumor_Annotation's encoding (a / underscores, no '+').
ascii_stroma <- function(x) {
x <- gsub("α", "a", x) # Greek small alpha -> a
x <- gsub("+", "", x, fixed = TRUE)
x <- gsub("__+", "_", x) # collapse any doubled underscores
x
}
if ("Stroma_Annotation" %in% names(meta))
meta[["Stroma_Annotation_clean"]] <- ascii_stroma(as.character(meta[["Stroma_Annotation"]]))
# ----------------------------------------------------------------------------
# 4. colData hygiene: AnnData/HDF5 cannot store Date/POSIXct -> coerce to character
# ----------------------------------------------------------------------------
for (col in names(meta)) {
if (inherits(meta[[col]], c("Date", "POSIXct", "POSIXt")))
meta[[col]] <- as.character(meta[[col]])
}
# ----------------------------------------------------------------------------
# 5. Spatial coordinates in microns -> reducedDim "spatial" (becomes obsm['spatial'])
# Keep raw px in obs (X_centroid/Y_centroid) and add explicit *_um as backup.
# ----------------------------------------------------------------------------
stopifnot(all(c("X_centroid", "Y_centroid") %in% names(meta)))
spatial_um <- cbind(X = meta$X_centroid * PX_UM,
Y = meta$Y_centroid * PX_UM)
rownames(spatial_um) <- rownames(meta)
meta$X_um <- spatial_um[, "X"]
meta$Y_um <- spatial_um[, "Y"]
# ----------------------------------------------------------------------------
# 6. var: 29 features + channel_type
# ----------------------------------------------------------------------------
feat <- rownames(linear)
rowdata <- DataFrame(
feature = feat,
channel_type = ifelse(grepl("^Hoechst", feat), "Hoechst", "protein"),
is_protein = !grepl("^Hoechst", feat),
row.names = feat
)
# ----------------------------------------------------------------------------
# 7. Assemble SingleCellExperiment
# ----------------------------------------------------------------------------
sce <- SingleCellExperiment(
assays = list(linear = linear), # features x cells; X_name = "linear"
colData = DataFrame(meta, check.names = FALSE),
rowData = rowdata
)
reducedDim(sce, "spatial") <- spatial_um
metadata(sce) <- list(
pixel_size_um = PX_UM,
spatial_units = "micron",
X_layer = "linear",
source_meta = basename(META_QS),
source_linear = basename(LINEAR_QS),
built = as.character(Sys.time()),
note = "X = linear (un-logged) intensities; spatial in microns; TRIPOD typos fixed in place (Machrophages, epthelium); Stroma_Annotation_clean = ASCII"
)
message("SCE assembled: ", nrow(sce), " features x ", ncol(sce), " cells; ",
ncol(colData(sce)), " obs columns.")
# ----------------------------------------------------------------------------
# 8. Write .h5ad (native R HDF5 writer first; fall back to basilisk if needed)
# ----------------------------------------------------------------------------
message("Writing ", OUT_H5AD, " ...")
ok <- tryCatch({
writeH5AD(sce, OUT_H5AD, X_name = "linear", use_native = TRUE, verbose = TRUE)
TRUE
}, error = function(e) {
message(" native writer failed (", conditionMessage(e), "); retrying via basilisk")
writeH5AD(sce, OUT_H5AD, X_name = "linear")
TRUE
})
stopifnot(isTRUE(ok))
message("DONE -> ", OUT_H5AD,
" (", round(file.info(OUT_H5AD)$size / 1e6, 1), " MB)")
message("In Python: adata.X = linear intensities, adata.obsm['spatial'] in microns, ",
"library key = 'All_ID'.")
```
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗