Skip to content

[RAPTOR-19524] Track executable bit for files - #864

Draft
taras-pokornyy wants to merge 5 commits into
datarobot-oss:mainfrom
taras-pokornyy:RAPTOR-19524_executable_bit
Draft

[RAPTOR-19524] Track executable bit for files#864
taras-pokornyy wants to merge 5 commits into
datarobot-oss:mainfrom
taras-pokornyy:RAPTOR-19524_executable_bit

Conversation

@taras-pokornyy

@taras-pokornyy taras-pokornyy commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

NOTES

This PR is CLI-only. The mode is captured, forwarded, and restored wherever this repo can do so,
but nothing here can make the server (Files API / workload-api / IBS) actually apply or return it
-- that's tracked separately; see the handoff spec below for what still needs to happen elsewhere.

Warning

RAPTOR-19524-other-services-agent-brief.md (commit 2d18245) is a planning document for the
Files API / workload-api / IBS follow-up work, not part of this repo's shipped code. Squash it
into the branch history or drop it before merge
-- it's included here only so reviewers and
whoever picks up the cross-service work have it; it should not land on main.

RATIONALE

RAPTOR-19524 (Critical): dr workload up
and dr artifact code sync upload the project directory as the server-side image build context,
but the sync pipeline never tracked Unix file mode. An executable synced as 0755 always lands
server-side (and round-trips back via dr artifact code checkout) as 0644. A Dockerfile that
COPYs the file without an explicit --chmod=755 then produces a container that dies at startup
with exec: "/path": permission denied -- a failure that names the runtime, not the sync, so it's
expensive to diagnose. Confirmed reproducible locally: Docker preserves whatever mode the build
context handed it, so the bit is already gone by the time the server has the file.

Root cause: the manifest (wapi.FileMeta) only ever tracked hash/size; the file walk/hash
step already os.Stats every file (for size) but threw away info.Mode(); neither upload
transport carried mode (the zip path has a natural carrier -- zip.FileHeader.SetMode/Mode() --
but never called it; the per-file multipart path had no carrier at all); and both download paths
hardcoded os.Create with no mode restoration.

Scope of this PR: everything fixable within this CLI repo, end to end and backward-compatible.
Whether the mode actually survives in the built image also depends on server-side (Files API /
workload-api / IBS) support that doesn't exist yet -- this repo can't implement or verify that
half, so a sync-time warning stays in place as an interim mitigation until the server side ships
(see the companion agent instructions below).

CHANGES

  • Capture + plumb mode through the diff engine and local manifest (9ab15d9): fileops.HashFile
    now also returns the already-stat'd os.FileMode; threaded as a new Mode uint32 field through
    wapi.FileMeta (the local .datarobot/workload/manifest.json), sync.FileEntry/FileAction,
    and filesapi.FileMeta/AllFilesItem (server-unpopulated for now, decodes as the "mode unknown"
    zero sentinel). No visible behavior change on its own -- pure plumbing.
  • Zip uploads carry mode (df5c0a4): the ZipUploader path had a natural carrier -- zip
    external attributes -- but never used it. addToZip now calls zip.FileHeader.SetMode().
  • Stage uploads carry mode (ea2a400): the per-file multipart StageUploader path had no
    carrier at all. Adds a mode param to filesapi.Client.UploadToStage, sent as an optional
    ?mode=0755-style query param -- omitted entirely (not mode=0) when unknown, so an old CLI or
    a server that doesn't understand the param yet sees exactly what it saw before this existed.
  • Rollback preserves mode (761126e, includes the download-side restore): both download paths
    (dr artifact code checkout and the sync engine's own pull-remote-only-files step) hardcoded
    os.Create with no mode restoration -- fixed via a new shared fileops.ApplyMode helper (a
    no-op when mode is unknown). Same bug also existed in Rollback.Backup/restoreFromDir (pure
    local-to-local copies, same class of bug, zero server dependency) -- fixed with the same helper.
  • Sync-time warning (dee1f7b): since the full round trip can't be verified end-to-end yet,
    warns once per sync (naming an example file) when locally-executable files are uploaded, so the
    failure is diagnosable at sync time instead of surfacing later as an opaque container start
    failure. Mirrors the existing shadow-ignore-file/lockfile warning shapes in the same phase.

Mode is represented as uint32 holding os.FileMode.Perm() (0-511 / 0000-0777) everywhere.
Zero is the sentinel for "unknown" (pre-feature manifest, or a server response without the field
yet) -- unambiguous, since a file that was successfully opened and hashed can never legitimately
have Perm() == 0.

On Windows, fileops.ApplyMode is a safe no-op beyond the read-only attribute: NTFS has no
owner/group/execute concept, so os.Chmod there only honors the 0200 (owner-write) bit.

PR Automation

Comment-Commands: Trigger CI by commenting on the PR:

  • /trigger-smoke-test or /trigger-test-smoke - Run smoke tests
  • /trigger-install-test or /trigger-test-install - Run installation tests

Labels: Apply labels to trigger workflows:

  • run-smoke-tests or go - Run smoke tests on demand (only works for non-forked PRs)

Important

For Forked PRs: The run-smoke-tests label won't work. A required Smoke Tests check will block merge until a maintainer acts:

  • A maintainer uses /approve-smoke-tests to run smoke tests (results will set the check)
  • A maintainer uses /skip-smoke-tests to bypass the check without running tests

Please comment requesting a maintainer review if you need smoke tests to run.

The code-sync manifest only ever tracked hash/size per file, so the
executable bit (and any non-default mode) was silently dropped on every
sync -- files always land server-side, and round-trip back via checkout,
as 0644. Captures mode at the point files are already stat'd for hashing
and threads it through the local manifest, the diff engine, and the
filesapi wire types (currently server-unpopulated, decodes as the "mode
unknown" zero sentinel until the server ships its half).

Includes the filesapi.FileMeta/AllFilesItem mode fields since
FromFilesAPI's conversion depends on them to compile.

Part of a multi-commit fix; upload/download/rollback wiring follows.
The ZipUploader path had a natural carrier for file mode -- zip.FileHeader
external attributes -- but never used it, so archived files lost their
permission bits before the upload even left the client. addToZip now sets
it from the already-captured FileAction.LocalMode.
The per-file multipart upload had no carrier for file mode at all. Adds
a mode parameter to filesapi.Client.UploadToStage, forwarded from the
already-captured FileAction.LocalMode as an optional `mode` query param
(a zero value is omitted entirely rather than sent as `mode=0`, so a
server that doesn't understand the param yet sees exactly what it saw
before this field existed).
Rollback.Backup and restoreFromDir are pure local-to-local file copies
(snapshot before a risky overwrite, restore on failure) that silently
normalized mode via os.Create, same bug class as the sync/download paths
but with zero server dependency since the true mode is always available
via a direct stat. Both now apply it through the shared fileops.ApplyMode
helper; a stat failure during restore is treated as non-fatal, since this
is a best-effort path where failing the whole rollback over one missed
chmod would lose every other file it already restored.
The full mode round trip (commits 9ab15d9, df5c0a4, ea2a400, 761126e) is
client-side only until the server (WAPI/IBS) ships its half -- until then
the executable bit may still not survive into the built image, and that
failure surfaces much later, at container startup, pointing at the
runtime rather than the sync. hashEntries now reports which walked files
are locally executable; phase2Manifests warns once, naming an example
file, mirroring the existing shadow-ignore-file/lockfile warning shapes
in this same phase. Remove this warning once server support is confirmed.
@taras-pokornyy taras-pokornyy changed the title Raptor 19524 executable bit [RAPTOR-19524] Track executable bit for files Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant