--- id: "bug-1780" status: "fixed" status_updated: "2026-09-24" status_note: "" component: "apiserver" severity: "unknown" timestamp: "2026-09-07T07:55:00Z" last_seen: "2026-09-19T09:00:00Z" occurrences: 1 files: ["src/frontend/apiserver/backend/convert.go"] tags: ["apiserver", "convert.go", "execuser", "job-submit", "jobstartinfo", "nobody", "permission-denied", "worker", "xcecli"] related: [] legacy: true --- # bug-1780: User-reported (xcecli): 'xcecli jobs submit -n 2 --env /run/current-system/sw/bin/sleep 100'… ## Description User-reported (xcecli): 'xcecli jobs submit -n 2 --env /run/current-system/sw/bin/sleep 100' submits successfully but the job immediately fails with Status . xcecd.log shows: '[WARN][controller_connector.c:822] No execuser provided in startjob request, defaulting to nobody', then 'Starting worker as user nobody to process command...', then 'Worker 301734 exited with code 126'. ## Environment _Not recorded (migrated from buglog.json)._ ## Reproduction _Not recorded (migrated from buglog.json)._ ## Root cause modelJobToSubmit() (src/frontend/apiserver/backend/convert.go, converts a Go models.JobInfo into the C job_startinfo_t sent to the controller) populated si.cmdline/launch_type/job_pre_exec/job_post_exec but never set si.execuser, even though job_startinfo_t (src/include/xce/types.h) has an execuser field and models.JobInfo already has an ExecUser field (used only on the read/display path, in convert_pure.go's rawJobToModel). CreateJob() (controllers/jobs.go) also never populated JobInfo.ExecUser when building the job to submit -- there is no --exec-user flag on xcecli either, so the field was simply always empty at submission. The controller (src/controller/job.c) forwards job.submit->start_info verbatim to the worker without ever touching execuser. xcecd's controller_connector.c (lines 817-822) defaults an empty/NULL execuser to the literal Linux user 'nobody', then (lines 1037-1040) copies that into job_ctx->limits->user_name, which xcecd_dbus_manager.c uses as systemd-run's User= for the job's transient unit. 'nobody' has no home directory/login shell and typically cannot execute or access the real submitting user's binaries/cwd, so the transient unit fails with 'Permission denied' -- surfacing to xcecd_worker_utils.c as exit_code=126, which xcecli then reports as job Status . Every job submitted via xcecli/apiserver was silently running as 'nobody' regardless of who submitted it. ## Fix FIXED (2026-09-07), TDD RED->GREEN. Added resolveExecUser(execUser, submitUser string) string to convert_pure.go -- a pure-Go helper (no CGO types) that returns the caller-specified exec user if present, else defaults to the submitting user; this mirrors the existing pure/CGO split in this file (rawJobToModel etc. already keep pure field-mapping logic separate from the CGO struct-building in convert.go specifically so it's testable without CGO -- and because a _test.go file can never itself import "C" (golang/go#13938), this split is the only way to unit-test any part of modelJobToSubmit's logic). modelJobToSubmit() now sets si.execuser = C.CString(resolveExecUser(job.ExecUser, job.SubmitUser)); freeJobSubmit() now also frees si.execuser (previously would have leaked once set). New tests in convert_test.go: TestResolveExecUser_DefaultsToSubmitUserWhenEmpty, TestResolveExecUser_PrefersExplicitExecUser, TestResolveExecUser_EmptyWhenBothEmpty -- pass under CGO_ENABLED=0 go test ./backend/... (RED confirmed by the function not existing before this change). Verified with a full /xce-build apiserver asan --incremental: exit 0, all frontend Go suites pass including apiserver/backend (7.595s, real CGO build), fresh xce_apiserver/xcecli binaries produced. Live re-run of the exact reported repro (xcecli jobs submit ... /run/current-system/sw/bin/sleep 100) not yet performed in this session -- recommend a live smoke test to confirm the job now runs as the submitting user and reaches DONE/RUN instead of EXIT before closing. ## Verification _Not recorded (migrated from buglog.json)._ ## References _Not recorded (migrated from buglog.json)._